Index: README.pinefrog-rtcp =================================================================== --- README.pinefrog-rtcp (revision 0) +++ README.pinefrog-rtcp (revision 404591) @@ -0,0 +1,162 @@ +Olle E. Johansson +oej@edvina.net + +2013-03-14 + + + + + + +Pinefrog - RTCP cleanup and additions +------------------------------------- + +This branch is aimed at porting the code in pinefrog-1.4, which is now a few years old, +to Asterisk 1.8 and hopefully (with some help) to Asterisk trunk to be integrated. +The 1.4 code has been running in production for years in universities, call centers +and service providers. + +The 1.8 port of Pinefrog is supported by Nordicom, Norway (http://www.nordicom.no). +The 1.4 work was sponsored by several companies, including ClearIT AB, Sweden. + +Status of 11 port +------------------ +2013-03-13 Started + +Todo for 11 +------------ +DONE: - Add support of outbound and inbound SDES. The SDES includes a stream identifier, CNAME. +DONE: - Add support of outbound SDES end and goodbye +DONE: - Add manager events at end-of call +DONE: - Add realtime storage of RTCP reports +DONE: - Add time manager events (configured in sip.conf) +DONE: - Add more information to RTCP debug +DONE: - Add more data aggregation to ast_rtcp structure (from svn trunk really) +- Add RTCP for p2p RTP bridges. Needs to be tested and validated. + +Background +========== +RTCP, as defined in RFC 3550, is a protocol that co-exists with RTP, the protocol used +for realtime multimedia in VoIP. RTCP gives the endpoints a tool to exchange data about +the media streams exchanged. As a result, both ends can get informaiton about the +latency for data sent in both directions, packet loss and jitter for each media stream. + +A VoIP call is at least two media streams and they can have different properties in +regards of quality. A router or switch in the middle could have a lot of outbound traffic, +causing delays and possible packet loss. This might not affect inbound traffic. + +In Asterisk, the RTCP handler is part of the RTP module. The RTP module produces RTCP +report that can be added to channel variables, cdr logs or sent through AMI. + +In 1.4, the data used is mostly based on the latest report, it's not aggregated. This +is fixed in trunk. + +In both implementations (and the 1.6 releases in between) the RTCP support is not +very complete. + +- It doesn't handle RTCP SDES packets +- It doesn't send RTCP END packets at end of session +- It doesn't handle receiving END packets +- It doesn't handle re-invites in a good way. +- It seems to mix sender and receiver reports, thus mixing data from two streams + - when does this happen, if at all? + +RTCP and NAT +------------ +I suspect that RTCP doesn't traverse NAT very well in our implementation. For RTP, +we start with sending media to probe NAT. I've added emtpy RTCP RR+SDES CNAME packets +to start probing a NAT (if Asterisk is behind a NAT). I am afraid that very few devices +do that early on. +The idea is (like RTP) + - Send a few RTCP packets in the start of the session. + - The receiver can then apply symmetric RTCP and start sending to the NAT outside port + that we're sending from and we'll get their packets. + +Todo +---- +- When CNAME changes, we have a different stream and need to restart the stats. + Should we add ability to produce multiple RTCP reports for one "call" and aggregate them? + The different parts might have different properties. +- Document realtime storage format. Add missing fields. +- BUG: RTCP is halted during hold. It should not stop. +- During HOLD, send RTCP SR reports without report block, only the header and no chunks + +Open Issues +----------- +Bridged channel ID is hard to get. + +The CQR and the final manager report lacks the bridged channel identifiers in too many cases. One leg of +the call gets it, but not both. +This will affect realtime as well, so we need to copy the channel name to a stored variable while it exists. + +Do we have a counter of consecutive lost packets? How do we measure lost packets on inbound +stream? Gaps in seq numbers or just the sender reports from the other end compared with received +no of packets? + + + +Ideas and thoughts for the future +--------------------------------- +- Asterisk propagates jitter and packet loss over a bridge (especially the p2p RTP bridge). + If the call is transfered on the OTHER side of the bridge, we have a new call with new + properties. Maybe events like this should generate a new SDES and reset RTCP? + Part A of the call can have very different properties than part B. If I have a call with + someone internally, that then transfers me to a call with someone on the Internet, the + call quality (jitter etc) will change dramatically. This will require some sort of CONTROL + packet over the bridge, informing about changes on the other side of the bridge (masq). +- Can we have some sort of ring buffer for the latest RTCP reports for a device (peer) + and use that to determine the status of the connection to the peer? +- Can we use the RTCP APP packet for relaying events in joined bridges, like meetme? +- What should we use as CNAME? Currently SIP call ID. +- Separate on the IPs of different media servers. IE we can have one SIP peer with + multiple media IPs with different properties + +Scenarios to test +------------------ +- normal bridged call +- RTP p2p bridged call +- Nat traversal - Asterisk outside of NAT and inside (as client to external service) +- Call hold +- Call with music-on-hold + +Database structure +------------------- +Example database schema for MySQL: + +CREATE TABLE `astcqr` ( + `channel` varchar(50) NOT NULL, + `uniqueid` varchar(35) NOT NULL, + `bridgedchannel` varchar(50) NOT NULL, + `bridgeduniqueid` varchar(35) NOT NULL, + `pvtcallid` varchar(80) NOT NULL, + `rtpmedia` varchar(50) NOT NULL, + `localssrc` varchar(50) NOT NULL, + `remotessrc` varchar(50) NOT NULL, + `rtt` varchar(10) NOT NULL, + `localjitter` varchar(10) NOT NULL, + `remotejitter` varchar(10) NOT NULL, + `sendformat` varchar(10) NOT NULL, + `receiveformat` varchar(10) NOT NULL, + `rtcpstatus` varchar(10) NOT NULL, + `duration` varchar(10) NOT NULL, + `packetsent` varchar(30) NOT NULL, + `packetreceived` varchar(30) NOT NULL, + `packetlossin` varchar(30) NOT NULL, + `packetlossout` varchar(30) NOT NULL, + `rttmax` varchar(12) NOT NULL, + `rttmin` varchar(12) NOT NULL, + `writetranslator` varchar(15) NOT NULL, + `readtranslator` varchar(15) NOT NULL, + `writecost` varchar(10) NOT NULL, + `readcost` varchar(10) NOT NULL, + `remoteip` varchar(25) NOT NULL, + KEY `ChannelUnique` (`channel`,`uniqueid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='FOr pinefrog stats' + +Realtime configuration +======================== +In extconfig.conf add + +rtpcqr => mysql,asterisk,astcqr + + Property changes on: README.pinefrog-rtcp ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: main/logger.c =================================================================== --- main/logger.c (revision 383046) +++ main/logger.c (working copy) @@ -191,6 +191,7 @@ "ERROR", "VERBOSE", "DTMF", + "CQR", }; /*! \brief Colors used in the console for logging */ @@ -202,6 +203,7 @@ COLOR_RED, COLOR_GREEN, COLOR_BRGREEN, + COLOR_BRBLUE, 0, 0, 0, @@ -210,7 +212,6 @@ 0, 0, 0, - 0, COLOR_BRBLUE, COLOR_BRBLUE, COLOR_BRBLUE, Index: main/rtp_engine.c =================================================================== --- main/rtp_engine.c (revision 383046) +++ main/rtp_engine.c (working copy) @@ -21,6 +21,9 @@ * \brief Pluggable RTP Architecture * * \author Joshua Colp + * + * Improved RTCP support by + * \author Olle E. Johansson */ /*** MODULEINFO @@ -915,6 +918,13 @@ return instance->engine->qos ? instance->engine->qos(instance, tos, cos, desc) : -1; } +void ast_rtp_instance_hold(struct ast_rtp_instance *instance, int status) +{ + if (instance->engine->hold) { + instance->engine->hold(instance, status); + } +} + void ast_rtp_instance_stop(struct ast_rtp_instance *instance) { if (instance->engine->stop) { @@ -968,6 +978,7 @@ return AST_BRIDGE_FAILED_NOWARN; } + /* Now let go of the channel locks and be on our way */ ast_channel_unlock(c0); ast_channel_unlock(c1); @@ -976,6 +987,10 @@ ast_poll_channel_add(c0, c1); + /* Kick the RTCP stream going by sending one empty stupid little packet */ + ast_rtcp_write_empty(instance0); + ast_rtcp_write_empty(instance1); + /* Hop into a loop waiting for a frame from either channel */ cs[0] = c0; cs[1] = c1; @@ -2065,6 +2080,56 @@ return instance->srtp; } + +int ast_rtp_instance_isactive(struct ast_rtp_instance *instance) +{ + if (instance->engine->isactive) { + return instance->engine->isactive(instance); + } + return -1; +} + +int ast_rtcp_write_empty(struct ast_rtp_instance *instance) +{ + if (instance->engine->rtcp_write_empty) { + instance->engine->rtcp_write_empty(instance); + return 0; + } + return -1; +} + +int ast_rtp_instance_setcname(struct ast_rtp_instance *instance, const char *cname, size_t length) +{ + if (instance->engine->setcname) { + instance->engine->setcname(instance, cname, length); + return 0; + } + + return -1; /* Function does not exist */ +} + +int ast_rtp_instance_set_bridged_chan(struct ast_rtp_instance *instance, const char *channel, const char *uniqueid, const char *bridgedchan, const char *bridgeduniqueid) +{ + if (instance->engine->set_bridged_chan) { + instance->engine->set_bridged_chan(instance, channel, uniqueid, bridgedchan, bridgeduniqueid); + return 0; + } + + return -1; /* Function does not exist */ +} + + +int ast_rtp_instance_set_translator(struct ast_rtp_instance *instance, const char *readtranslator, const int readcost, const char *writetranslator, const int writecost) +{ + if (instance->engine->set_translator) { + instance->engine->set_translator(instance, readtranslator, readcost, writetranslator, writecost); + return 0; + } + + return -1; /* Function does not exist */ +} + + int ast_rtp_instance_sendcng(struct ast_rtp_instance *instance, int level) { if (instance->engine->sendcng) { Index: CREDITS =================================================================== --- CREDITS (revision 383046) +++ CREDITS (working copy) @@ -25,6 +25,9 @@ ClearIT AB for work with meetme, res_mutestream, RTCP, manager and tonezones +Nordicom Norge AS, Kristiansand, Norway, for funding work with RTCP support +and Call Quality Records. + === WISHLIST CONTRIBUTERS === Jeremy McNamara - SpeeX support Nick Seraphin - RDNIS support @@ -118,6 +121,7 @@ SIP presence support, SIP call state updates (dialog-info), QUEUE_EXISTS function, device state provider architecture, multiparking (together with mvanbaak), meetme and parking device states, + RTCP improvements, Call Quality Records, MiniVM - the small voicemail system, many documentation updates/corrections, and many bug fixes. oej(AT)edvina.net, http://edvina.net @@ -219,7 +223,8 @@ Viagenie, Canada - IPv6 support in socket layers and SIP implementation Developers: Marc Blanchet, Simon Perreault and Jean-Philippe Dionne -ClearIT AB, Sweden - res_mutestream, queue_exists and various other patches (developed by oej) +ClearIT AB, Sweden - res_mutestream, queue_exists, RTCP improvements and various + other patches (developed by oej) Despegar.com, Argentina - AstData API implementation, also sponsored by Google as part of the gsoc/2009 program (developed by Eliel) Index: include/asterisk/logger.h =================================================================== --- include/asterisk/logger.h (revision 383046) +++ include/asterisk/logger.h (working copy) @@ -209,8 +209,19 @@ #endif #define AST_LOG_DTMF __LOG_DTMF, _A_ -#define NUMLOGLEVELS 32 +#ifdef LOG_CQR +#undef LOG_CQR +#endif +#define __LOG_CQR 7 +#define LOG_CQR __LOG_CQR, _A_ +#ifdef AST_LOG_CQR +#undef AST_LOG_CQR +#endif +#define AST_LOG_CQR __LOG_CQR, _A_ + +#define NUMLOGLEVELS 64 /* Highest bit */ + /*! * \brief Get the debug level for a module * \param module the name of module Index: include/asterisk/rtp_engine.h =================================================================== --- include/asterisk/rtp_engine.h (revision 383046) +++ include/asterisk/rtp_engine.h (working copy) @@ -74,6 +74,7 @@ #include "asterisk/netsock2.h" #include "asterisk/sched.h" #include "asterisk/res_srtp.h" +#include "asterisk/channel.h" /* Maximum number of payloads supported */ #if defined(LOW_MEMORY) @@ -215,6 +216,14 @@ AST_RTP_INSTANCE_STAT_LOCAL_SSRC, /*! Retrieve remote SSRC */ AST_RTP_INSTANCE_STAT_REMOTE_SSRC, + /*! Retrieve local CNAME */ + AST_RTP_INSTANCE_STAT_LOCAL_CNAME, + /*! Retrieve remote SDES */ + AST_RTP_INSTANCE_STAT_REMOTE_CNAME, + /*! Retrieve start time */ + AST_RTP_INSTANCE_STAT_START, + /*! Retrieve IP Address */ + AST_RTP_INSTANCE_STAT_IP, }; /* Codes for RTP-specific data - not defined by our AST_FORMAT codes */ @@ -246,10 +255,12 @@ unsigned int txcount; /*! Number of packets received */ unsigned int rxcount; + /*! Jitter on transmitted packets */ double txjitter; /*! Jitter on received packets */ double rxjitter; + /*! Maximum jitter on remote side */ double remote_maxjitter; /*! Minimum jitter on remote side */ @@ -300,6 +311,32 @@ unsigned int local_ssrc; /*! Their SSRC */ unsigned int remote_ssrc; + + /* --- Pinefrog additions */ + /*! Remote: Number of packets transmitted */ + unsigned int remote_txcount; + /*! Remote: Number of packets received */ + unsigned int remote_rxcount; + char channel[AST_MAX_EXTENSION]; /*!< Name of channel */ + char uniqueid[AST_MAX_EXTENSION]; /*!< uniqueid of channel */ + char bridgedchannel[AST_MAX_EXTENSION]; /*!< Name of bridged channel */ + char bridgeduniqueid[AST_MAX_EXTENSION]; /*!< uniqueid of bridged channel */ + unsigned int numberofreports; /*!< Number of reports received from remote end */ + struct ast_format lasttxformat; /*!< Last used codec on transmitted stream */ + struct ast_format lastrxformat; /*!< Last used codec on received stream */ + struct sockaddr_in them; /*!< The IP address used for media by remote end */ + struct sockaddr_in us; /*!< The IP address used for media by our end */ + char ourcname[255]; /*!< Our SDES RTP session name (CNAME) */ + size_t ourcnamelength; /*!< Length of CNAME (utf8) */ + char theircname[255]; /*!< Their SDES RTP session name (CNAME) */ + size_t theircnamelength; /*!< Length of CNAME (utf8) */ + struct timeval start; /*!< When the stream started */ + struct timeval end; /*!< When the stream ended */ + char writetranslator[80]; /*!< Translator used when writing */ + char readtranslator[80]; /*!< Translator providing frames when reading */ + int writecost; /*!< Cost in milliseconds for encoding/decoding 1 second of outbound media */ + int readcost; /*!< Cost in milliseconds for encoding/decoding 1 second of inbound media */ + int mediatype; /*! Type of media */ }; #define AST_RTP_STAT_SET(current_stat, combined, placement, value) \ @@ -420,6 +457,9 @@ int (*destroy)(struct ast_rtp_instance *instance); /*! Callback for writing out a frame */ int (*write)(struct ast_rtp_instance *instance, struct ast_frame *frame); + /*! Callback for stopping the outbound RTP media for an instance, + but keeping the RTCP flow (and the RTP keepalives if needed) */ + void (*hold)(struct ast_rtp_instance *instance, int status); /*! Callback for stopping the RTP instance */ void (*stop)(struct ast_rtp_instance *instance); /*! Callback for starting RFC2833 DTMF transmission */ @@ -483,6 +523,15 @@ struct ast_rtp_engine_ice *ice; /*! Callback to pointer for optional DTLS SRTP support */ struct ast_rtp_engine_dtls *dtls; + /*! Callback to check if a media stram is active */ + int (*isactive)(struct ast_rtp_instance *instance); + /*! Callback to set CNAME in rtcp */ + void (*setcname)(struct ast_rtp_instance *instance, const char *cname, size_t length); + /*! Callback to set information about bridged channel for CQR record */ + void (*set_bridged_chan)(struct ast_rtp_instance *instance, const char *channel, const char *uniqueid, const char *bridgedchan, const char *bridgeduniqueid); + /*! Callback to set translation information for the CQR record */ + void (*set_translator) (struct ast_rtp_instance *instance, const char *readtranslator, const int readcost, const char *writetranslator, const int writecost); + int (*rtcp_write_empty)(struct ast_rtp_instance *instance); /*! Linked list information */ AST_RWLIST_ENTRY(ast_rtp_engine) entry; }; @@ -1508,6 +1557,26 @@ int ast_rtp_instance_set_qos(struct ast_rtp_instance *instance, int tos, int cos, const char *desc); /*! + * \brief Stop the RTP outbound media in a stream, but keep the RTCP flow going + * And propably RTP keepalives too. + * + * \param instance Instance that media is no longer going to at this time + * + * Example usage: + * + * \code + * ast_rtp_instance_stop(instance); + * \endcode + * + * This tells the RTP engine being used for the instance pointed to by instance + * that media is no longer going to it at this time, but may in the future. + * Keep the RTCP flow happy + * + * \since 1.42 + */ +void ast_rtp_instance_hold(struct ast_rtp_instance *instance, int status); + +/*! * \brief Stop an RTP instance * * \param instance Instance that media is no longer going to at this time @@ -2032,6 +2101,36 @@ int ast_rtp_instance_sendcng(struct ast_rtp_instance *instance, int level); /*! + * \brief Send empty RTCP report + * + * \param instance The RTP instance + * \param fd File descriptor to use + * + * \retval 0 Success + * \retval non-zero Failure + */ +int ast_rtcp_write_empty(struct ast_rtp_instance *instance); + + +/*! + * \brief Check if RTP stream is active + * + * \param instance The RTP instance + * + * \retval 0 Active (success) + * \retval -1 Not supported by RTP engine, 1 Not active + */ +int ast_rtp_instance_isactive(struct ast_rtp_instance *instance); + +/*! + * \brief Set the name of the RTP session (used in RTCP) + * \param cname Session name (UTF 8 possible) + * \param length Name of string (needed for UTF 8 always) + * + */ +int ast_rtp_instance_setcname(struct ast_rtp_instance *instance, const char *cname, size_t length); + +/*! * \brief Add or replace the SRTP policies for the given RTP instance * * \param instance the RTP instance @@ -2108,6 +2207,22 @@ */ void ast_rtp_dtls_cfg_free(struct ast_rtp_dtls_cfg *dtls_cfg); +/*! + * \brief set the channel information for the CQR records + * + * \retval 0 on success + * \retval -1 not implemented by RTP engine + */ +int ast_rtp_instance_set_bridged_chan(struct ast_rtp_instance *instance, const char *channel, const char *uniqueid, const char *bridgedchan, const char *bridgeduniqueid); + +/*! + * \brief set the channel translator information for the CQR records + * + * \retval 0 on success + * \retval -1 not implemented by RTP engine + */ +int ast_rtp_instance_set_translator(struct ast_rtp_instance *instance, const char *readtranslator, const int readcost, const char *writetranslator, const int writecost); + #if defined(__cplusplus) || defined(c_plusplus) } #endif Index: include/asterisk/translate.h =================================================================== --- include/asterisk/translate.h (revision 383046) +++ include/asterisk/translate.h (working copy) @@ -141,7 +141,7 @@ * on translation cost table. */ int comp_cost; /*!< Cost value associated with this translator based * on computation time. This cost value is computed based - * on the time required to translate sample data. */ + * on the time required to translate sample data. */ int (*newpvt)(struct ast_trans_pvt *); /*!< initialize private data * associated with the translator */ Index: configs/extconfig.conf.sample =================================================================== --- configs/extconfig.conf.sample (revision 383046) +++ configs/extconfig.conf.sample (working copy) @@ -82,6 +82,7 @@ ;acls => odbc,asterisk ;musiconhold => mysql,general ;queue_log => mysql,general +;rtpcqr => mysql,general,astcqr ; RTP Call Quality Records ; ; ; While most dynamic realtime engines are automatically used when defined in Index: res/res_rtp_asterisk.c =================================================================== --- res/res_rtp_asterisk.c (revision 383046) +++ res/res_rtp_asterisk.c (working copy) @@ -85,13 +85,33 @@ #define TURN_ALLOCATION_WAIT_TIME 2000 -#define RTCP_PT_FUR 192 -#define RTCP_PT_SR 200 -#define RTCP_PT_RR 201 -#define RTCP_PT_SDES 202 -#define RTCP_PT_BYE 203 -#define RTCP_PT_APP 204 +#define RTCP_PT_FUR 192 /*!< FIR - Full Intra-frame request (h.261) */ +#define RTCP_PT_NACK 193 /*!< NACK - Negative acknowledgement (h.261) */ +#define RTCP_PT_IJ 195 /*!< IJ - RFC 5450 Extended Inter-arrival jitter report */ +#define RTCP_PT_SR 200 /*!< SR - RFC 3550 Sender report */ +#define RTCP_PT_RR 201 /*!< RR - RFC 3550 Receiver report */ +#define RTCP_PT_SDES 202 /*!< SDES - Source Description */ +#define RTCP_PT_BYE 203 /*!< BYE - Goodbye */ +#define RTCP_PT_APP 204 /*!< APP - Application defined */ +#define RTCP_PT_RTPFB 205 /*!< RTPFB - Generic RTP feedback RFC 4585 */ +#define RTCP_PT_PSFB 206 /*!< PSFB - Payload specific data RFC 4585 */ +#define RTCP_PT_XR 207 /*!< XR - Extended report - RFC3611 */ +/*! \brief RFC 3550 RTCP SDES Item types */ +enum rtcp_sdes { + SDES_END = 0, /*!< End of SDES list */ + SDES_CNAME = 1, /*!< Canonical name */ + SDES_NAME = 2, /*!< User name */ + SDES_EMAIL = 3, /*!< User's e-mail address */ + SDES_PHONE = 4, /*!< User's phone number */ + SDES_LOC = 5, /*!< Geographic user location */ + SDES_TOOL = 6, /*!< Name of application or tool */ + SDES_NOTE = 7, /*!< Notice about the source */ + SDES_PRIV = 8, /*!< SDES Private extensions */ + SDES_H323_CADDR = 9, /*!< H.323 Callable address */ + SDES_APSI = 10, /*!< Application Specific Identifier (RFC 6776) */ +}; + #define RTP_MTU 1200 #define DEFAULT_DTMF_TIMEOUT (150 * (8000 / 1000)) /*!< samples */ @@ -122,7 +142,7 @@ static int rtpend = DEFAULT_RTP_END; /*!< Last port for RTP sessions (set in rtp.conf) */ static int rtpdebug; /*!< Are we debugging? */ static int rtcpdebug; /*!< Are we debugging RTCP? */ -static int rtcpstats; /*!< Are we debugging RTCP? */ +static int rtcpstats; /*!< Are we gathering stats? */ static int rtcpinterval = RTCP_DEFAULT_INTERVALMS; /*!< Time between rtcp reports in millisecs */ static struct ast_sockaddr rtpdebugaddr; /*!< Debug packets to/from this host */ static struct ast_sockaddr rtcpdebugaddr; /*!< Debug RTCP packets to/from this host */ @@ -164,6 +184,7 @@ #define FLAG_NAT_INACTIVE_NOWARN (1 << 1) #define FLAG_NEED_MARKER_BIT (1 << 3) #define FLAG_DTMF_COMPENSATE (1 << 4) +#define FLAG_HOLD (1 << 4) /* This RTP stream is put on hold by someone else, a:sendonly */ #define TRANSPORT_SOCKET_RTP 1 #define TRANSPORT_SOCKET_RTCP 2 @@ -219,20 +240,22 @@ unsigned int dtmf_timeout; /*!< When this timestamp is reached we consider END frame lost and forcibly abort digit */ unsigned int dtmfsamples; enum ast_rtp_dtmf_mode dtmfmode; /*!< The current DTMF mode of the RTP stream */ + /* DTMF Transmission Variables */ unsigned int lastdigitts; - char sending_digit; /*!< boolean - are we sending digits */ - char send_digit; /*!< digit we are sending */ + char sending_digit; /*!< boolean - are we sending digits */ + char send_digit; /*!< digit we are sending */ int send_payload; int send_duration; unsigned int flags; struct timeval rxcore; struct timeval txcore; double drxcore; /*!< The double representation of the first received packet */ + struct timeval start; /*!< When the stream started (we can't depend on CDRs) */ struct timeval lastrx; /*!< timeval when we last received a packet */ struct timeval dtmfmute; + struct timeval holdstart; /*!< When the stream was put on hold */ struct ast_smoother *smoother; - int *ioid; unsigned short seqno; /*!< Sequence number, RFC 3550, page 13. */ unsigned short rxseqno; struct ast_sched_context *sched; @@ -262,6 +285,7 @@ ast_cond_t cond; /*!< Condition for signaling */ unsigned int passthrough:1; /*!< Bit to indicate that the received packet should be passed through */ unsigned int ice_started:1; /*!< Bit to indicate ICE connectivity checks have started */ + unsigned int isactive:1; /*!< Whether the RTP stream is active or not */ char remote_ufrag[256]; /*!< The remote ICE username */ char remote_passwd[256]; /*!< The remote ICE password */ @@ -295,11 +319,18 @@ * this structure is analogous to ast_rtp, which tracks a RTP session, * it is logical to think of this as a RTCP session. * + * On the other hand, RTCP SDES defines the names for the actual + * RTP session so it's one session - RTP and RTCP together (OEJ) + * * RTCP packet is defined on page 9 of RFC 3550. * */ struct ast_rtcp { int rtcp_info; + char ourcname[255]; /*!< Our SDES RTP session name (CNAME) */ + size_t ourcnamelength; /*!< Length of CNAME (utf8) */ + char theircname[255]; /*!< Their SDES RTP session name (CNAME) */ + size_t theircnamelength; /*!< Length of CNAME (utf8) */ int s; /*!< Socket */ struct ast_sockaddr us; /*!< Socket representation of the local endpoint. */ struct ast_sockaddr them; /*!< Socket representation of the remote endpoint. */ @@ -313,17 +344,19 @@ int schedid; /*!< Schedid returned from ast_sched_add() to schedule RTCP-transmissions*/ unsigned int rr_count; /*!< number of RRs we've sent, not including report blocks in SR's */ unsigned int sr_count; /*!< number of SRs we've sent */ + unsigned int rec_rr_count; /*!< Number of RRs we've received */ + unsigned int rec_sr_count; /*!< Number of SRs we've received */ unsigned int lastsrtxcount; /*!< Transmit packet count when last SR sent */ double accumulated_transit; /*!< accumulated a-dlsr-lsr */ double rtt; /*!< Last reported rtt */ unsigned int reported_jitter; /*!< The contents of their last jitter entry in the RR */ unsigned int reported_lost; /*!< Reported lost packets in their RR */ - double reported_maxjitter; - double reported_minjitter; + double reported_maxjitter; /*!< The contents of their max jitter entry received by us */ + double reported_minjitter; /*!< The contents of their min jitter entry received by us */ double reported_normdev_jitter; double reported_stdev_jitter; - unsigned int reported_jitter_count; + unsigned int reported_jitter_count; /*! Number of reports received */ double reported_maxlost; double reported_minlost; @@ -335,25 +368,33 @@ double minrxlost; double normdev_rxlost; double stdev_rxlost; - unsigned int rxlost_count; + unsigned int rxlost_count; /*! Number of reports received */ double maxrxjitter; double minrxjitter; double normdev_rxjitter; double stdev_rxjitter; - unsigned int rxjitter_count; + unsigned int rxjitter_count; /*! Number of reports received */ double maxrtt; double minrtt; double normdevrtt; double stdevrtt; - unsigned int rtt_count; + unsigned int rtt_count; /*! Number of reports received */ + char bridgedchannel[AST_MAX_EXTENSION]; /*!< Bridged channel name */ + char bridgeduniqueid[AST_MAX_EXTENSION]; /*!< Bridged channel uniqueid */ + char channel[AST_MAX_EXTENSION]; /*!< Our channel name */ + char uniqueid[AST_MAX_EXTENSION]; /*!< Our channel uniqueid */ + char readtranslator[80]; /* Translation done on reading audio from PBX */ + char writetranslator[80]; /* Translation done on writing audio to PBX - bridged channel */ + int readcost; /* Delay in milliseconds for translation of 1 second of audio */ + int writecost; /* Delay in milliseconds for translation of 1 second of audio */ }; struct rtp_red { struct ast_frame t140; /*!< Primary data */ struct ast_frame t140red; /*!< Redundant t140*/ unsigned char pt[AST_RED_MAX_GENERATION]; /*!< Payload types for redundancy data */ - unsigned char ts[AST_RED_MAX_GENERATION]; /*!< Time stamps */ +unsigned char ts[AST_RED_MAX_GENERATION]; /*!< Time stamps */ unsigned char len[AST_RED_MAX_GENERATION]; /*!< length of each generation */ int num_gen; /*!< Number of generations */ int schedid; /*!< Timer id */ @@ -388,9 +429,18 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_instance_stats *stats, enum ast_rtp_instance_stat stat); static int ast_rtp_dtmf_compatible(struct ast_channel *chan0, struct ast_rtp_instance *instance0, struct ast_channel *chan1, struct ast_rtp_instance *instance1); static void ast_rtp_stun_request(struct ast_rtp_instance *instance, struct ast_sockaddr *suggestion, const char *username); +static void ast_rtp_hold(struct ast_rtp_instance *instance, int status); static void ast_rtp_stop(struct ast_rtp_instance *instance); static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos, const char* desc); static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level); +static int ast_rtcp_write(const void *data); +static void ast_rtcp_setcname(struct ast_rtp_instance *instance, const char *cname, size_t length); +static void ast_rtcp_set_bridged(struct ast_rtp_instance *instance, const char *channel, const char *uniqueid, const char *bridgedchan, const char *bridgeduniqueid); +void ast_rtcp_set_translator(struct ast_rtp_instance *instance, const char *readtranslator, const int readcost, const char *writetranslator, const int writecost); +static int ast_rtp_isactive(struct ast_rtp_instance *instance); +static int add_sdes_bodypart(struct ast_rtp *rtp, unsigned int *rtcp_packet, int len, int type); +static int add_sdes_header(struct ast_rtp *rtp, unsigned int *rtcp_packet, int len); +static int ast_rtcp_write_empty_frame(struct ast_rtp_instance *instance); #ifdef HAVE_OPENSSL_SRTP static int ast_rtp_activate(struct ast_rtp_instance *instance); @@ -1021,9 +1071,15 @@ .dtmf_compatible = ast_rtp_dtmf_compatible, .stun_request = ast_rtp_stun_request, .stop = ast_rtp_stop, + .hold = ast_rtp_hold, .qos = ast_rtp_qos_set, .sendcng = ast_rtp_sendcng, .ice = &ast_rtp_ice, + .setcname = ast_rtcp_setcname, + .set_bridged_chan = ast_rtcp_set_bridged, + .set_translator = ast_rtcp_set_translator, + .isactive = ast_rtp_isactive, + .rtcp_write_empty = ast_rtcp_write_empty_frame, #ifdef HAVE_OPENSSL_SRTP .dtls = &ast_rtp_dtls, .activate = ast_rtp_activate, @@ -1545,6 +1601,24 @@ return interval; } +/*! \brief Schedule RTCP transmissions for RTP channel */ +static void ast_rtcp_schedule(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + /* Do not schedule RR if RTCP isn't run */ + if (rtp->rtcp && !ast_sockaddr_isnull(&rtp->rtcp->them) && rtp->rtcp->schedid < 1) { + /* Schedule transmission of Receiver Report */ + ast_rtcp_write_empty(instance); + ao2_ref(instance, +1); + rtp->rtcp->schedid = ast_sched_add(rtp->sched, ast_rtcp_calc_interval(rtp), ast_rtcp_write, instance); + if (rtp->rtcp->schedid < 0) { + ao2_ref(instance, -1); + ast_log(LOG_WARNING, "scheduling RTCP transmission failed.\n"); + } + } +} + /*! \brief Calculate normal deviation */ static double normdev_compute(double normdev, double sample, unsigned int sample_count) { @@ -1791,6 +1865,9 @@ rtp->rekeyid = -1; #endif + gettimeofday(&rtp->start, NULL); + rtp->isactive = 1; + return 0; } @@ -2159,7 +2236,7 @@ } /*! \brief Send RTCP recipient's report */ -static int ast_rtcp_write_rr(struct ast_rtp_instance *instance) +static int ast_rtcp_write_rr(struct ast_rtp_instance *instance, int goodbye) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); int res; @@ -2171,7 +2248,7 @@ unsigned int received_interval; int lost_interval; struct timeval now; - unsigned int *rtcpheader; + unsigned int *rtcpheader, *start; char bdata[1024]; struct timeval dlsr; int fraction; @@ -2220,7 +2297,7 @@ fraction = (lost_interval << 8) / expected_interval; gettimeofday(&now, NULL); timersub(&now, &rtp->rtcp->rxlsr, &dlsr); - rtcpheader = (unsigned int *)bdata; + rtcpheader = (unsigned int *) bdata; rtcpheader[0] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_RR << 16) | ((len/4)-1)); rtcpheader[1] = htonl(rtp->ssrc); rtcpheader[2] = htonl(rtp->themssrc); @@ -2230,13 +2307,14 @@ rtcpheader[6] = htonl(rtp->rtcp->themrxlsr); rtcpheader[7] = htonl((((dlsr.tv_sec * 1000) + (dlsr.tv_usec / 1000)) * 65536) / 1000); - /*! \note Insert SDES here. Probably should make SDES text equal to mimetypes[code].type (not subtype 'cos - it can change mid call, and SDES can't) */ - rtcpheader[len/4] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | 2); - rtcpheader[(len/4)+1] = htonl(rtp->ssrc); /* Our SSRC */ - rtcpheader[(len/4)+2] = htonl(0x01 << 24); /* Empty for the moment */ - len += 12; + start = &rtcpheader[len/4]; + len +=8; /* SKip header for now */ + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_CNAME); + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_END); + /* Now, add header when we know the actual length */ + add_sdes_header(rtp, start, len); + ast_sockaddr_copy(&remote_address, &rtp->rtcp->them); res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &remote_address, &ice); @@ -2268,15 +2346,16 @@ } /*! \brief Send RTCP sender's report */ -static int ast_rtcp_write_sr(struct ast_rtp_instance *instance) +static int ast_rtcp_write_sr(struct ast_rtp_instance *instance, int goodbye) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); int res; - int len = 0; + int len = 0; /* Measured in chunks of four bytes */ + int srlen = 0; struct timeval now; unsigned int now_lsw; unsigned int now_msw; - unsigned int *rtcpheader; + unsigned int *rtcpheader, *start; unsigned int lost; unsigned int extended; unsigned int expected; @@ -2301,7 +2380,8 @@ } gettimeofday(&now, NULL); - timeval2ntp(now, &now_msw, &now_lsw); /* fill thses ones in from utils.c*/ + timeval2ntp(now, &now_msw, &now_lsw); /* fill these ones in from utils.c*/ + /* Set the header for sender's report */ rtcpheader = (unsigned int *)bdata; rtcpheader[1] = htonl(rtp->ssrc); /* Our SSRC */ rtcpheader[2] = htonl(now_msw); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/ @@ -2336,13 +2416,23 @@ rtcpheader[0] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SR << 16) | ((len/4)-1)); - /* Insert SDES here. Probably should make SDES text equal to mimetypes[code].type (not subtype 'cos */ - /* it can change mid call, and SDES can't) */ - rtcpheader[len/4] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | 2); - rtcpheader[(len/4)+1] = htonl(rtp->ssrc); /* Our SSRC */ - rtcpheader[(len/4)+2] = htonl(0x01 << 24); /* Empty for the moment */ - len += 12; + start = &rtcpheader[len/4]; + srlen = len; + len +=8; /* SKip header for now */ + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_CNAME); + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_END); + /* Now, add header when we know the actual length */ + add_sdes_header(rtp, start, len - srlen); + + if (goodbye) { + /* An additional RTCP block */ + rtcpheader[len/4] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_BYE << 16) | 1); + len += 4; + rtcpheader[len/4] = htonl(rtp->ssrc); /* Our SSRC */ + len += 4; + } + ast_sockaddr_copy(&remote_address, &rtp->rtcp->them); res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &remote_address, &ice); @@ -2369,11 +2459,11 @@ ast_verbose(" Sent packets: %u\n", rtp->txcount); ast_verbose(" Sent octets: %u\n", rtp->txoctetcount); ast_verbose(" Report block:\n"); - ast_verbose(" Fraction lost: %u\n", fraction); - ast_verbose(" Cumulative loss: %u\n", lost); - ast_verbose(" IA jitter: %.4f\n", rtp->rxjitter); - ast_verbose(" Their last SR: %u\n", rtp->rtcp->themrxlsr); - ast_verbose(" DLSR: %4.4f (sec)\n\n", (double)(ntohl(rtcpheader[12])/65536.0)); + ast_verbose(" Fraction lost (since last report): %u\n", fraction); + ast_verbose(" Cumulative loss: %u\n", lost); + ast_verbose(" IA jitter: %.4f\n", rtp->rxjitter); + ast_verbose(" Their last SR: %u\n", rtp->rtcp->themrxlsr); + ast_verbose(" Delay since last SR (DLSR): %4.4f (sec)\n\n", (double)(ntohl(rtcpheader[12])/65536.0)); } manager_event(EVENT_FLAG_REPORTING, "RTCPSent", "To: %s\r\n" "OurSSRC: %u\r\n" @@ -2416,9 +2506,9 @@ } if (rtp->txcount > rtp->rtcp->lastsrtxcount) { - res = ast_rtcp_write_sr(instance); + res = ast_rtcp_write_sr(instance, 0); } else { - res = ast_rtcp_write_rr(instance); + res = ast_rtcp_write_rr(instance, 0); } if (!res) { @@ -2537,15 +2627,7 @@ rtp->txcount++; rtp->txoctetcount += (res - hdrlen); - if (rtp->rtcp && rtp->rtcp->schedid < 1) { - ast_debug(1, "Starting RTCP transmission on RTP instance '%p'\n", instance); - ao2_ref(instance, +1); - rtp->rtcp->schedid = ast_sched_add(rtp->sched, ast_rtcp_calc_interval(rtp), ast_rtcp_write, instance); - if (rtp->rtcp->schedid < 0) { - ao2_ref(instance, -1); - ast_log(LOG_WARNING, "scheduling RTCP transmission failed.\n"); - } - } + ast_rtcp_schedule(instance); } update_address_with_ice_candidate(rtp, COMPONENT_RTP, &remote_address); @@ -2607,6 +2689,12 @@ struct ast_format subclass; int codec; + if (ast_test_flag(rtp, FLAG_HOLD)) { + /* This stream is on hold, just keep on happily and don't do anything */ + ast_debug(1, "** Frame muted since we're on hold. \n"); + return 0; + } + ast_rtp_instance_get_remote_address(instance, &remote_address); /* If we don't actually know the remote address don't even bother doing anything */ @@ -2755,20 +2843,23 @@ d=-d; rtp->rxjitter += (1./16.) * (d - rtp->rxjitter); - if (rtp->rtcp) { - if (rtp->rxjitter > rtp->rtcp->maxrxjitter) - rtp->rtcp->maxrxjitter = rtp->rxjitter; - if (rtp->rtcp->rxjitter_count == 1) - rtp->rtcp->minrxjitter = rtp->rxjitter; - if (rtp->rtcp && rtp->rxjitter < rtp->rtcp->minrxjitter) - rtp->rtcp->minrxjitter = rtp->rxjitter; + if (!rtp->rtcp) { + return; + } + if (rtp->rxjitter > rtp->rtcp->maxrxjitter) + rtp->rtcp->maxrxjitter = rtp->rxjitter; + if (rtp->rtcp->rxjitter_count == 1) { + rtp->rtcp->minrxjitter = rtp->rxjitter; + } + if (rtp->rtcp && rtp->rxjitter < rtp->rtcp->minrxjitter) { + rtp->rtcp->minrxjitter = rtp->rxjitter; + } - normdev_rxjitter_current = normdev_compute(rtp->rtcp->normdev_rxjitter,rtp->rxjitter,rtp->rtcp->rxjitter_count); - rtp->rtcp->stdev_rxjitter = stddev_compute(rtp->rtcp->stdev_rxjitter,rtp->rxjitter,rtp->rtcp->normdev_rxjitter,normdev_rxjitter_current,rtp->rtcp->rxjitter_count); + normdev_rxjitter_current = normdev_compute(rtp->rtcp->normdev_rxjitter,rtp->rxjitter,rtp->rtcp->rxjitter_count); + rtp->rtcp->stdev_rxjitter = stddev_compute(rtp->rtcp->stdev_rxjitter,rtp->rxjitter,rtp->rtcp->normdev_rxjitter,normdev_rxjitter_current,rtp->rtcp->rxjitter_count); - rtp->rtcp->normdev_rxjitter = normdev_rxjitter_current; - rtp->rtcp->rxjitter_count++; - } + rtp->rtcp->normdev_rxjitter = normdev_rxjitter_current; + rtp->rtcp->rxjitter_count++; } static struct ast_frame *create_dtmf_frame(struct ast_rtp_instance *instance, enum ast_frame_type type, int compensate) @@ -3065,8 +3156,10 @@ struct ast_sockaddr addr; unsigned char rtcpdata[8192 + AST_FRIENDLY_OFFSET]; unsigned int *rtcpheader = (unsigned int *)(rtcpdata + AST_FRIENDLY_OFFSET); - int res, packetwords, position = 0; struct ast_frame *f = &ast_null_frame; + int j, res, packetwords, position = 0; + char *sdes; + unsigned int sdeslength, sdestype; /* Read in RTCP data from the socket */ if ((res = rtcp_recvfrom(instance, rtcpdata + AST_FRIENDLY_OFFSET, @@ -3108,7 +3201,7 @@ return &ast_null_frame; } - packetwords = res / 4; + packetwords = res / 4; /* Each RTCP segment is 32 bits */ if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) { /* Send to whoever sent to us */ @@ -3120,8 +3213,15 @@ } } - ast_debug(1, "Got RTCP report of %d bytes\n", res); + if (rtcp_debug_test_addr(&addr)) { + ast_debug(1, "Got RTCP report of %d bytes - %d messages\n", res, packetwords); + } + /* Process a compound packet + - A compound packet should start with a sender or receiver report. BYE can start as well + (seen in implementations) + - Packet length should be a multiple of four bytes + */ while (position < packetwords) { int i, pt, rc; unsigned int length, dlsr, lsr, msw, lsw, comp; @@ -3129,24 +3229,33 @@ double rttsec, reported_jitter, reported_normdev_jitter_current, normdevrtt_current, reported_lost, reported_normdev_lost_current; uint64_t rtt = 0; - i = position; - length = ntohl(rtcpheader[i]); - pt = (length & 0xff0000) >> 16; - rc = (length & 0x1f000000) >> 24; - length &= 0xffff; + i = position; + if (rtcp_debug_test_addr(&addr)) { + ast_debug(3, "***** Debug - position = %d\n", position ); + } + length = ntohl(rtcpheader[i]); + pt = (length & 0xff0000) >> 16; /* Packet type */ + rc = (length & 0x1f000000) >> 24; /* Number of chunks, i.e. streams reported */ + length &= 0xffff; + + if ((i + length) > packetwords) { + if (option_debug || rtpdebug) { + ast_log(LOG_WARNING, "RTCP Read too short - packet type %d position %d\n", pt, i); + } + return f; + } + - if ((i + length) > packetwords) { - if (rtpdebug) - ast_debug(1, "RTCP Read too short\n"); - return &ast_null_frame; - } - if (rtcp_debug_test_addr(&addr)) { ast_verbose("\n\nGot RTCP from %s\n", ast_sockaddr_stringify(&addr)); ast_verbose("PT: %d(%s)\n", pt, (pt == 200) ? "Sender Report" : (pt == 201) ? "Receiver Report" : (pt == 192) ? "H.261 FUR" : "Unknown"); ast_verbose("Reception reports: %d\n", rc); - ast_verbose("SSRC of sender: %u\n", rtcpheader[i + 1]); + ast_verbose(" SSRC of packet sender: %u (%x)", ntohl(rtcpheader[i + 1]), ntohl(rtcpheader[i + 1])); + ast_verbose(" (Position %d of %d)\n", i, packetwords); + if (rc == 0) { + ast_verbose(" Empty - no reports! \n"); + } } i += 2; /* Advance past header and ssrc */ @@ -3155,27 +3264,46 @@ continue; } + + if (rc == 0 && pt == RTCP_PT_RR) { /* We're receiving a receiver report with no reports, which is ok */ + position += (length + 1); + continue; + } + if (pt == RTCP_PT_SR) { + rtp->rtcp->rec_sr_count++; + } else if (pt == RTCP_PT_RR) { + rtp->rtcp->rec_rr_count++; + } switch (pt) { - case RTCP_PT_SR: - gettimeofday(&rtp->rtcp->rxlsr,NULL); /* To be able to populate the dlsr */ - rtp->rtcp->spc = ntohl(rtcpheader[i+3]); - rtp->rtcp->soc = ntohl(rtcpheader[i + 4]); - rtp->rtcp->themrxlsr = ((ntohl(rtcpheader[i]) & 0x0000ffff) << 16) | ((ntohl(rtcpheader[i + 1]) & 0xffff0000) >> 16); /* Going to LSR in RR*/ + case RTCP_PT_SR: /* Sender's report - about what they have sent us */ + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" - RTCP SR (sender report) from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } + /* Don't handle multiple reception reports (rc > 1) yet */ + gettimeofday(&rtp->rtcp->rxlsr, NULL); /* To be able to populate the dlsr */ + rtp->rtcp->spc = ntohl(rtcpheader[i + 3]); /* Sender packet count */ + rtp->rtcp->soc = ntohl(rtcpheader[i + 4]); /* Sender octet count */ + rtp->rtcp->themrxlsr = ((ntohl(rtcpheader[i]) & 0x0000ffff) << 16) | ((ntohl(rtcpheader[i + 1]) & 0xffff0000) >> 16); /* Going to LSR in RR*/ + + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" NTP timestamp: %lu.%010lu\n", (unsigned long) ntohl(rtcpheader[i]), (unsigned long) ntohl(rtcpheader[i + 1]) * 4096); + ast_verbose(" RTP timestamp: %lu\n", (unsigned long) ntohl(rtcpheader[i + 2])); + ast_verbose(" SPC: %lu\tSOC: %lu\n", (unsigned long) ntohl(rtcpheader[i + 3]), (unsigned long) ntohl(rtcpheader[i + 4])); + ast_verbose(" RC (number of reports) %d\n", rc); + } + i += 5; /* Sender's info report is five bytes */ + if (rc < 1) + break; + + case RTCP_PT_RR: /* Receiver report - data about what we have sent to them */ if (rtcp_debug_test_addr(&addr)) { - ast_verbose("NTP timestamp: %lu.%010lu\n", (unsigned long) ntohl(rtcpheader[i]), (unsigned long) ntohl(rtcpheader[i + 1]) * 4096); - ast_verbose("RTP timestamp: %lu\n", (unsigned long) ntohl(rtcpheader[i + 2])); - ast_verbose("SPC: %lu\tSOC: %lu\n", (unsigned long) ntohl(rtcpheader[i + 3]), (unsigned long) ntohl(rtcpheader[i + 4])); + ast_verbose("Received a RTCP RR (receiver report) from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); } - i += 5; - if (rc < 1) - break; - /* Intentional fall through */ - case RTCP_PT_RR: - /* Don't handle multiple reception reports (rc > 1) yet */ /* Calculate RTT per RFC */ gettimeofday(&now, NULL); timeval2ntp(now, &msw, &lsw); + /* Get timing */ if (ntohl(rtcpheader[i + 4]) && ntohl(rtcpheader[i + 5])) { /* We must have the LSR && DLSR */ comp = ((msw & 0xffff) << 16) | ((lsw & 0xffff0000) >> 16); lsr = ntohl(rtcpheader[i + 4]); @@ -3197,13 +3325,16 @@ if (comp - dlsr >= lsr) { rtp->rtcp->accumulated_transit += rttsec; - if (rtp->rtcp->rtt_count == 0) + if (rtp->rtcp->rtt_count == 0) { rtp->rtcp->minrtt = rttsec; + } - if (rtp->rtcp->maxrttrtcp->maxrttrtcp->maxrtt = rttsec; - if (rtp->rtcp->minrtt>rttsec) + } + if (rtp->rtcp->minrtt > rttsec || rtp->rtcp->minrtt == 0) { rtp->rtcp->minrtt = rttsec; + } normdevrtt_current = normdev_compute(rtp->rtcp->normdevrtt, rttsec, rtp->rtcp->rtt_count); @@ -3324,8 +3455,9 @@ } break; case RTCP_PT_FUR: - if (rtcp_debug_test_addr(&addr)) - ast_verbose("Received an RTCP Fast Update Request\n"); + if (rtcp_debug_test_addr(&addr)) { + ast_verbose("Received an RTCP Fast Update Request from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } rtp->f.frametype = AST_FRAME_CONTROL; rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE; rtp->f.datalen = 0; @@ -3335,23 +3467,142 @@ f = &rtp->f; break; case RTCP_PT_SDES: - if (rtcp_debug_test_addr(&addr)) - ast_verbose("Received an SDES from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + /* SDES messages are divided into chunks, each one containing one or + several items. Each chunk is for a different CSRC, so it is not really + relevant in most cases of voip calls - unless you have an advanced + mixer in the network that separates the different streams with CSRC + + A chunk starts with SSRC/CSRC (four bytes), then SDES items + In the SDES message, there can be several items, ending with SDES_END + The length of the all items is length - header + Chunk starts on a 32-bit boundary and needs padding by 0's + + the "rc" variable contains the number of chunks + When we start, we're beyond the SSRC and starts with SDES items in the + first chunk. + + an SDES item is one byte of type, one byte of length then data + (no null termination). Text is UTF-8. + the last item is a zero (END) type with no length indication. + */ + + j = i * 4; + sdes = (char *) &rtcpheader[i]; + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received an SDES from %s - Total length %d (%d bytes)\n", ast_sockaddr_stringify(&rtp->rtcp->them), length-i, ((length-i)*4) - 6); + } + while (j < length * 4) { + sdestype = (uint8_t) *sdes; + sdes++; + sdeslength = (uint8_t) *sdes; + sdes++; + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES Type %u, Length %u Curj %d)\n", sdestype, sdeslength, j); + } + switch (sdestype) { + case SDES_CNAME: + if (!ast_strlen_zero(rtp->rtcp->theircname)) { + if (sdeslength > sizeof(rtp->rtcp->theircname)) { + sdeslength = sizeof(rtp->rtcp->theircname) - 1; + } + if (strncmp(rtp->rtcp->theircname, sdes, sdeslength)) { + ast_log(LOG_WARNING, "New RTP stream received (new RTCP CNAME for session. Old name: %s\n", rtp->rtcp->theircname); + } + } + strncpy(rtp->rtcp->theircname, sdes, sdeslength); + rtp->rtcp->theircname[sdeslength + 1] = '\0'; + rtp->rtcp->theircnamelength = sdeslength; + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES CNAME (utf8) %s\n", rtp->rtcp->theircname); + } + break; + case SDES_TOOL: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES TOOL \n"); + } + break; + case SDES_NAME: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES NAME \n"); + } + break; + case SDES_EMAIL: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES EMAIL \n"); + } + break; + case SDES_PHONE: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES PHONE \n"); + } + break; + case SDES_LOC: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES LOC \n"); + } + break; + case SDES_NOTE: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES NOTE \n"); + } + break; + case SDES_PRIV: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES PRIV \n"); + } + break; + case SDES_END: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES END \n"); + } + break; + case SDES_APSI: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" --- SDES APSI \n"); + } + break; + } + j += 2 + sdeslength; /* Header (1 byte) + length */ + sdes += sdeslength; + if (sdestype == SDES_END) { + break; /* The while loop */ + } + } + break; - case RTCP_PT_BYE: - if (rtcp_debug_test_addr(&addr)) - ast_verbose("Received a BYE from %s\n", - ast_sockaddr_stringify(&rtp->rtcp->them)); + case RTCP_PT_NACK: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received a RTCP NACK from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } break; + case RTCP_PT_BYE: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received a RTCP BYE from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } + break; + case RTCP_PT_XR: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received a RTCP Extended Report (XR) packet from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } + break; + case RTCP_PT_APP: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received a RTCP APP packet from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } + break; + case RTCP_PT_IJ: + if (rtcp_debug_test_addr(&addr)) { + ast_verbose(" Received a RTCP IJ from %s\n", ast_sockaddr_stringify(&rtp->rtcp->them)); + } + break; default: - ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n", - pt, ast_sockaddr_stringify(&rtp->rtcp->them)); + ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n", pt, ast_sockaddr_stringify(&rtp->rtcp->them)); break; } position += (length + 1); - } + } /* While loop */ + /* OEJ CHECK next line */ rtp->rtcp->rtcp_info = 1; return f; @@ -3366,6 +3617,10 @@ int reconstruct = ntohl(rtpheader[0]); struct ast_sockaddr remote_address = { {0,} }; int ice; + struct timeval rxtime; + unsigned int timestamp; + int rate; + unsigned int ms; /* Get fields from packet */ payload = (reconstruct & 0x7f0000) >> 16; @@ -3394,12 +3649,31 @@ ast_clear_flag(rtp, FLAG_NEED_MARKER_BIT); } + /* Calculate timestamp for reception of the packet */ + timestamp = ntohl(rtpheader[1]); + calc_rxstamp(&rxtime, rtp, timestamp, mark); + + ast_format_copy(&bridged->lasttxformat, ast_rtp_codecs_get_payload_format(ast_rtp_instance_get_codecs(instance1), bridged_payload)); + ast_format_copy(&rtp->lastrxformat, ast_rtp_codecs_get_payload_format(ast_rtp_instance_get_codecs(instance1), bridged_payload)); + + rate = rtp_get_rate(&rtp->lastrxformat) / 1000; + + /* Now, calculate tx timestamp */ + ms = calc_txstamp(rtp, &rxtime); + if (bridged_payload == AST_FRAME_VOICE) { + bridged->lastts = bridged->lastts + ms * rate; + } else if (bridged_payload == AST_FRAME_VIDEO) { + bridged->lastts = bridged->lastts + ms * 90; + /* This is not exact, but a best effort example that can be improved */ + } + /* Reconstruct part of the packet */ reconstruct &= 0xFF80FFFF; reconstruct |= (bridged_payload << 16); reconstruct |= (mark << 23); rtpheader[0] = htonl(reconstruct); + ast_rtp_instance_get_remote_address(instance1, &remote_address); if (ast_sockaddr_isnull(&remote_address)) { @@ -3427,6 +3701,9 @@ return 0; } + bridged->txcount++; + bridged->txoctetcount += (res - hdrlen); + update_address_with_ice_candidate(rtp, COMPONENT_RTP, &remote_address); if (rtp_debug_test_addr(&remote_address)) { @@ -3482,7 +3759,7 @@ /* Make sure the data that was read in is actually enough to make up an RTP packet */ if (res < hdrlen) { - ast_log(LOG_WARNING, "RTP Read too short\n"); + ast_log(LOG_WARNING, "RTP Read too short (%d expecting %d)\n", res, hdrlen); return &ast_null_frame; } @@ -3572,10 +3849,6 @@ } } - /* If we are directly bridged to another instance send the audio directly out */ - if (ast_rtp_instance_get_bridged(instance) && !bridge_p2p_rtp_write(instance, rtpheader, res, hdrlen)) { - return &ast_null_frame; - } /* If the version is not what we expected by this point then just drop the packet */ if (version != 2) { @@ -3616,6 +3889,15 @@ rtp->rxssrc = ssrc; + /* Schedule RTCP report transmissions if possible */ + ast_rtcp_schedule(instance); + + /* This needs to be after RTCP calculations to get more RTCP data */ + /* If we are directly bridged to another instance send the audio directly out */ + if (ast_rtp_instance_get_bridged(instance) && !bridge_p2p_rtp_write(instance, rtpheader, res, hdrlen)) { + return &ast_null_frame; + } + /* Remove any padding bytes that may be present */ if (padding) { res -= rtp->rawdata[AST_FRIENDLY_OFFSET + res - 1]; @@ -4064,6 +4346,45 @@ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_SSRC, -1, stats->local_ssrc, rtp->ssrc); AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_SSRC, -1, stats->remote_ssrc, rtp->themssrc); + AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_START, -1, stats->start, rtp->start); + if (stat == AST_RTP_INSTANCE_STAT_IP || stat == AST_RTP_INSTANCE_STAT_ALL) { + memcpy(&stats->them, &rtp->rtcp->them, sizeof(stats->them)); + } + if (stat == AST_RTP_INSTANCE_STAT_LOCAL_CNAME || stat == AST_RTP_INSTANCE_STAT_ALL) { + memcpy(&stats->ourcname, &rtp->rtcp->ourcname, rtp->rtcp->ourcnamelength); /* UTF8 safe */ + stats->ourcnamelength = rtp->rtcp->ourcnamelength; + } + if (stat == AST_RTP_INSTANCE_STAT_REMOTE_CNAME || stat == AST_RTP_INSTANCE_STAT_ALL) { + memcpy(&stats->theircname, &rtp->rtcp->theircname, rtp->rtcp->theircnamelength); /* UTF8 safe */ + stats->theircnamelength = rtp->rtcp->theircnamelength; + } + + /* To fix */ + stats->readcost = rtp->rtcp->readcost; + stats->writecost = rtp->rtcp->writecost; + stats->lasttxformat = rtp->lasttxformat; + stats->lastrxformat = rtp->lastrxformat; + if (!ast_strlen_zero(rtp->rtcp->readtranslator)) { + ast_copy_string(stats->readtranslator, rtp->rtcp->readtranslator, sizeof(stats->readtranslator)); + } + if (!ast_strlen_zero(rtp->rtcp->writetranslator)) { + ast_copy_string(stats->writetranslator, rtp->rtcp->writetranslator, sizeof(stats->writetranslator)); + } + if (!ast_strlen_zero(rtp->rtcp->readtranslator)) { + ast_copy_string(stats->readtranslator, rtp->rtcp->readtranslator, sizeof(stats->readtranslator)); + } + if (!ast_strlen_zero(rtp->rtcp->channel)) { + ast_copy_string(stats->channel, rtp->rtcp->channel, sizeof(stats->channel)); + } + if (!ast_strlen_zero(rtp->rtcp->bridgedchannel)) { + ast_copy_string(stats->bridgedchannel, rtp->rtcp->bridgedchannel, sizeof(stats->bridgedchannel)); + } + if (!ast_strlen_zero(rtp->rtcp->uniqueid)) { + ast_copy_string(stats->uniqueid, rtp->rtcp->uniqueid, sizeof(stats->uniqueid)); + } + if (!ast_strlen_zero(rtp->rtcp->bridgeduniqueid)) { + ast_copy_string(stats->bridgeduniqueid, rtp->rtcp->bridgeduniqueid, sizeof(stats->bridgeduniqueid)); + } return 0; } @@ -4093,11 +4414,36 @@ ast_sockaddr_from_sin(suggestion, &suggestion_tmp); } +/* \brief Put stream on/off hold, mute outbound RTP but keep + RTP keepalives and RTCP going + */ +static void ast_rtp_hold(struct ast_rtp_instance *instance, int status) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + if (status) { + ast_debug(3, "##### HOLDING RTCP, Have a nice silent day \n"); + ast_set_flag(rtp, FLAG_HOLD); + } else { + ast_debug(3, "##### UNHOLDING RTCP, You will get audio now. \n"); + ast_clear_flag(rtp, FLAG_HOLD); + } +} + +/*! \brief STOP the media streams, END the session. + Note: This is not hold. This is shutting down all the fun. +*/ static void ast_rtp_stop(struct ast_rtp_instance *instance) { struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_sockaddr addr = { {0,} }; + ast_debug(3, "##### Stopping RTP, Sending good bye and cleaning up.\n"); + + /* Send RTCP goodbye packet */ + if (rtp->isactive && rtp->rtcp) { + ast_rtcp_write_sr(instance, 1); + } + #ifdef HAVE_OPENSSL_SRTP AST_SCHED_DEL_UNREF(rtp->sched, rtp->rekeyid, ao2_ref(instance, -1)); #endif @@ -4122,6 +4468,7 @@ } ast_set_flag(rtp, FLAG_NEED_MARKER_BIT); + rtp->isactive = 0; } static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos, const char *desc) @@ -4131,6 +4478,91 @@ return ast_set_qos(rtp->s, tos, cos, desc); } + +/*! \brief set RTP cname used to describe session in RTCP sdes messages */ +void ast_rtcp_setcname(struct ast_rtp_instance *instance, const char *cname, size_t length) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (!rtp || !rtp->rtcp) { + return; + } + if (length > 255) { + length=255; + } + ast_copy_string(rtp->rtcp->ourcname, cname, length+1); + rtp->rtcp->ourcnamelength = length; + if (option_debug > 3) { + ast_log(LOG_DEBUG, "--- Copied CNAME %s to RTCP structure (length %d)\n", cname, (int) length); + } +} + +/*! \brief set the name of the bridged channel + +At the time when we write the report there might not be a bridge, so we need +to store this so we can correlate the reports. If a channel changes bridge, +it can be reset by first setting it to an empty string, then setting to +a new name +*/ +void ast_rtcp_set_bridged(struct ast_rtp_instance *instance, const char *channel, const char *uniqueid, const char *bridgedchan, const char *bridgeduniqueid) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + + if (!rtp) { /* For some reason, there's no RTP */ + ast_debug(1, "??????????????? NO RTP \n"); + return; + } + if (!rtp->rtcp) { /* No RTCP? Strange */ + ast_debug(1, "??????????????? NO RTCP \n"); + return; + } + /* If we already have data, don't replace it. + NOTE: Should we replace it at a masquerade or something? Hmm. + */ + if (!ast_strlen_zero(channel) && !rtp->rtcp->channel[0]) { + ast_debug(1, "!!!!!! Setting channel name to %s\n", channel); + ast_copy_string(rtp->rtcp->channel, channel, sizeof(rtp->rtcp->channel)); + } + if (!ast_strlen_zero(uniqueid) && !rtp->rtcp->uniqueid[0]) { + ast_debug(1, "!!!!!! Setting unique id to %s\n", uniqueid); + ast_copy_string(rtp->rtcp->uniqueid, uniqueid, sizeof(rtp->rtcp->uniqueid)); + } + if (!ast_strlen_zero(bridgedchan)) { + ast_debug(1, "!!!!!! Setting bridged channel name to %s\n", bridgedchan); + ast_copy_string(rtp->rtcp->bridgedchannel, bridgedchan, sizeof(rtp->rtcp->bridgedchannel)); + } else { + if(rtp->rtcp->bridgedchannel[0] != '\0') { + ast_debug(1, "!!!!!! Keeping bridged channel name %s\n", rtp->rtcp->bridgedchannel); + } + //rtp->rtcp->bridgedchan[0] = '\0'; + } + if (!ast_strlen_zero(bridgeduniqueid)) { + ast_debug(1, "!!!!!! Setting bridged unique id to %s\n", bridgeduniqueid); + ast_copy_string(rtp->rtcp->bridgeduniqueid, bridgeduniqueid, sizeof(rtp->rtcp->bridgeduniqueid)); + } else { + if(rtp->rtcp->bridgeduniqueid[0] != '\0') { + ast_debug(1, "!!!!!! Keeping bridged unique id \n"); + } + } +} + + +/*! \brief Set the transcoding variables for the QoS reports */ +void ast_rtcp_set_translator(struct ast_rtp_instance *instance, const char *readtranslator, const int readcost, const char *writetranslator, const int writecost) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + if (!rtp || !rtp->rtcp) { + return; + } + ast_copy_string(rtp->rtcp->readtranslator, S_OR(readtranslator,""), sizeof(rtp->rtcp->readtranslator)); + ast_copy_string(rtp->rtcp->writetranslator, S_OR(writetranslator,""), sizeof(rtp->rtcp->writetranslator)); + rtp->rtcp->readcost = readcost; + rtp->rtcp->writecost = writecost; + +} + + + /*! \brief generate comfort noice (CNG) */ static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level) { @@ -4198,6 +4630,120 @@ } #endif +/*! \brief Check if rtp stream is active */ +static int ast_rtp_isactive(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + return rtp->isactive ? 0 : 1; +} + +/*! \brief Basically add SSRC */ +static int add_sdes_header(struct ast_rtp *rtp, unsigned int *rtcp_packet, int len) +{ + /* 2 is version, 1 is number of chunks, then RTCP packet type (SDES) and length */ + *rtcp_packet = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | ((len/4)-1)); + + rtcp_packet++; /* Move 32 bits ahead for the header */ + *rtcp_packet = htonl(rtp->ssrc); /* Our SSRC */ + rtcp_packet ++; + + /* Header + SSRC */ + return len + 8; +} + +static int add_sdes_bodypart(struct ast_rtp *rtp, unsigned int *rtcp_packet, int len, int type) +{ + int cnamelen; + int sdeslen = 0; + char *sdes; + + sdes = (char *) rtcp_packet; + switch (type) { + case SDES_CNAME: + cnamelen = (int) rtp->rtcp->ourcnamelength; + + *sdes = SDES_CNAME; + sdes++; + *sdes = (char) cnamelen; + sdes++; + strncpy(sdes, rtp->rtcp->ourcname, cnamelen); /* NO terminating 0 */ + + /* THere must be a multiple of four bytes in the packet */ + sdeslen = cnamelen; + break; + case SDES_END: + *sdes = SDES_END; + sdes++; + *sdes = (char) 0; + sdes++; + sdeslen = 2; + } + len += sdeslen + (sdeslen % 4 == 0 ? 0 : 4 - (sdeslen % 4)) ; + + return len; +} + +/*! \brief Send emtpy RTCP receiver's report and SDES message + Mainly used to open NAT sessions */ +static int ast_rtcp_write_empty_frame(struct ast_rtp_instance *instance) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + char bdata[512]; + unsigned int *rtcpheader, *start; + int fd, len, res; + + if (!rtp || !rtp->rtcp) { + return 0; + } + ast_debug(1, "************ ---- About to send empty RTCP packet\n"); + fd = rtp->rtcp->s; + + if (fd == -1) { + ast_debug(1, "--- No file descriptor to use \n"); + } + + if (!ast_sockaddr_isnull(&rtp->rtcp->them)) { /* This'll stop rtcp for this rtp session */ + ast_verbose("RTCP SR transmission error, rtcp halted\n"); + AST_SCHED_DEL(rtp->sched, rtp->rtcp->schedid); + return 0; + } + if (rtcp_debug_test_addr(&rtp->rtcp->them)) { + ast_debug(1, "---- About to send empty RTCP packet\n"); + } + rtcpheader = (unsigned int *)bdata; + /* Add a RR header with no reports (chunks = 0) - The RFC says that it's always needed + first in a compound packet. + */ + rtcpheader[0] = htonl((2 << 30) | (0 << 24) | (RTCP_PT_RR << 16) | 1); + rtcpheader[1] = htonl(rtp->ssrc); + len = 8; + start = &rtcpheader[len/4]; + len +=8; /* SKip header for now */ + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_CNAME); + len = add_sdes_bodypart(rtp, &rtcpheader[len/4], len, SDES_END); + /* Now, add header when we know the actual length */ + add_sdes_header(rtp, start, len); + + res = sendto(fd, (unsigned int *)rtcpheader, len, 0, (struct sockaddr *)&rtp->rtcp->them, sizeof(rtp->rtcp->them)); + + if (res < 0) { + ast_log(LOG_ERROR, "RTCP RR transmission error, rtcp halted: %s\n",strerror(errno)); + /* Remove the scheduler */ + AST_SCHED_DEL(rtp->sched, rtp->rtcp->schedid); + return 0; + } + + rtp->rtcp->rr_count++; + + if (rtcp_debug_test_addr(&rtp->rtcp->them)) { + ast_verbose("\n* Sending Empty RTCP RR to %s Our SSRC: %u\n", + ast_sockaddr_stringify(&rtp->rtcp->them), + rtp->ssrc); + } + + return res; +} + static char *rtp_do_debug_ip(struct ast_cli_args *a) { char *arg = ast_strdupa(a->argv[4]); @@ -4209,8 +4755,7 @@ return CLI_FAILURE; } rtpdebugport = (!ast_strlen_zero(debugport) && debugport[0] != '0'); - ast_cli(a->fd, "RTP Debugging Enabled for address: %s\n", - ast_sockaddr_stringify(&rtpdebugaddr)); + ast_cli(a->fd, "RTP Debugging Enabled for address: %s\n", ast_sockaddr_stringify(&rtpdebugaddr)); rtpdebug = 1; return CLI_SUCCESS; } Index: channels/sip/rtcp.c =================================================================== --- channels/sip/rtcp.c (revision 0) +++ channels/sip/rtcp.c (revision 404591) @@ -0,0 +1,382 @@ +/* +* Asterisk -- An open source telephony toolkit. +* +* Copyright (C) 2013 Olle E. Johansson, Edvina AB +* +* See http://www.asterisk.org for more information about +* the Asterisk project. Please do not directly contact +* any of the maintainers of this project for assistance; +* the project provides a web site, mailing lists and IRC +* channels for your use. +* +* This program is free software, distributed under the terms of +* the GNU General Public License Version 2. See the LICENSE file +* at the top of the source tree. +*/ + +/*! \file rtcp.c +* +* \brief RTCP additional functions +* +* \author Olle E. Johansson +*/ + +/*** MODULEINFO + core +***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/utils.h" +#include "asterisk/manager.h" +#include "asterisk/logger.h" +#include "asterisk/translate.h" +#include "asterisk/rtp_engine.h" +#include "include/sip.h" +#include "include/rtcp.h" + +/*! \brief Set various data items in the RTP structure, like channel identifier. + */ +void sip_rtcp_set_data(struct sip_pvt *dialog, struct ast_rtp_instance *instance, enum media_type type) +{ + + if (dialog && dialog->owner) { + struct ast_channel *bridgepeer = ast_bridged_channel(dialog->owner); + + if (bridgepeer) { + /* Store the bridged peer data while we have it */ + ast_rtp_instance_set_bridged_chan(instance, ast_channel_name(dialog->owner), ast_channel_uniqueid(dialog->owner), S_OR(ast_channel_name(bridgepeer), ""), S_OR(ast_channel_uniqueid(bridgepeer), "")); + ast_debug(1, "---- Setting bridged peer name to %s\n", ast_channel_name(bridgepeer)); + } else { + ast_rtp_instance_set_bridged_chan(instance, ast_channel_name(dialog->owner), ast_channel_uniqueid(dialog->owner), NULL, NULL); + } + ast_debug(1, "---- Setting channel name to %s\n", ast_channel_name(dialog->owner)); + + /* Try to find out if there's active transcoding */ + /* Currently, the only media stream that has translation is the audio stream. At some point + we might have transcoding for other types of media. */ + if (type == SDP_AUDIO) { + const char *rtname = NULL, *wtname = NULL; + struct ast_trans_pvt *rt, *wt; + + /* if we have a translator, the bridge delay is increased, which affects the QoS of the call. */ + rt = ast_channel_readtrans(dialog->owner); + wt = ast_channel_writetrans(dialog->owner); + if (rt) { + rtname = rt->t->name; + } + if (wt) { + wtname = wt->t->name; + } + if (rt || wt) { + ast_rtp_instance_set_translator(instance, rtname, rt ? rt->t->comp_cost : (const int) 0, + wtname, wt ? wt->t->comp_cost : (const int) 0); + } + + if (option_debug > 1) { + if (rt && rt->t) { + ast_debug(1, "--- Audio Read translator: %s Cost %d\n", rt->t->name, rt->t->comp_cost); + } + if (wt && wt->t) { + ast_debug(1, "--- Audio Write translator: %s Cost %d\n", wt->t->name, wt->t->comp_cost); + } + } + } + + } else { + ast_debug(1, "######## Not setting rtcp media data. Dialog %s Dialog owner %s \n", dialog ? "set" : "unset", dialog->owner ? "set" : "unset"); + } +} + +/*! \brief send manager report of RTCP + reporttype = 0 means report during call (if configured) + reporttype = 1 means endof-call (hangup) report + reporttype = 10 means report at end of call leg (like transfer) +*/ +void sip_rtcp_report(struct sip_pvt *dialog, struct ast_rtp_instance *instance, enum media_type media, int reporttype) +{ + struct ast_rtp_instance_stats qual; + //char *rtpqstring = NULL; + //int qosrealtime = ast_check_realtime("rtpcqr"); + unsigned int duration; /* Duration in secs */ + memset(&qual, 0, sizeof(qual)); + + sip_rtcp_set_data(dialog, instance, media); + + if (ast_rtp_instance_get_stats(instance, &qual, AST_RTP_INSTANCE_STAT_ALL)) { + ast_debug(1, "######## Did not get any statistics... bad, bad, RTP instance\n"); + /* Houston, we got a problem */ + return; + } + + if (dialog->sip_cfg->rtcpevents) { + /* + If numberofreports == 0 we have no incoming RTCP active, thus we can't + get any reliable data to handle packet loss or any RTT timing. + */ + + duration = (unsigned int)(ast_tvdiff_ms(ast_tvnow(), qual.start) / 1000); + manager_event(EVENT_FLAG_CALL, "RTPQuality", + "Channel: %s\r\n" /* AST_CHANNEL for this call */ + "Uniqueid: %s\r\n" /* AST_CHANNEL for this call */ + "BridgedChannel: %s\r\n" + "BridgedUniqueid: %s\r\n" + "RTPreporttype: %s\r\n" + "RTPrtcpstatus: %s\r\n" + "Duration: %u\r\n" /* used in cdr_manager */ + "PvtCallid: %s\r\n" /* ??? Generic PVT identifier */ + "RTPipaddress: %s\r\n" + "RTPmedia: %s\r\n" /* Audio, video, text */ + "RTPsendformat: %s\r\n" + "RTPrecvformat: %s\r\n" + "RTPlocalssrc: %u\r\n" + "RTPremotessrc: %u\r\n" + "RTPrtt: %f\r\n" + "RTPrttMax: %f\r\n" + "RTPrttMin: %f\r\n" + "RTPLocalJitter: %f\r\n" + "RTPRemoteJitter: %f\r\n" + "RTPInPacketLoss: %d\r\n" + "RTPInLocalPlPercent: %5.2f\r\n" + "RTPOutPacketLoss: %d\r\n" + "RTPOutPlPercent: %5.2f\r\n" + "TranslateRead: %s\r\n" + "TranslateReadCost: %d\r\n" + "TranslateWrite: %s\r\n" + "TranslateWriteCost: %d\r\n" + "\r\n", + dialog->owner ? ast_channel_name(dialog->owner) : "", + dialog->owner ? ast_channel_uniqueid(dialog->owner) : "", + qual.bridgedchannel[0] ? qual.bridgedchannel : "" , + qual.bridgeduniqueid[0] ? qual.bridgeduniqueid : "", + reporttype == 1 ? "Final" : "Update", + qual.numberofreports == 0 ? "Inactive" : "Active", + duration, + dialog->callid, + ast_inet_ntoa(qual.them.sin_addr), + media == SDP_AUDIO ? "audio" : (media == SDP_VIDEO ? "video" : "fax") , + ast_getformatname(&qual.lasttxformat), + ast_getformatname(&qual.lastrxformat), + qual.local_ssrc, + qual.remote_ssrc, + qual.rtt, + qual.maxrtt, + qual.minrtt, + qual.rxjitter, + qual.txjitter, + qual.rxploss, + /* The local counter of lost packets in inbound stream divided with received packets plus lost packets */ + (qual.remote_txcount + qual.rxploss) > 0 ? (double) qual.rxploss / (qual.remote_txcount + qual.rxploss) * 100 : 0, + qual.txploss, + /* The remote counter of lost packets (if we got the reports) + divided with our counter of sent packets + */ + (qual.rxcount + qual.txploss) > 0 ? (double) qual.txploss / qual.rxcount * 100 : 0, + qual.readtranslator, qual.readcost, + qual.writetranslator, qual.writecost + ); + } + + /* CDR records are not reliable when it comes to near-death-of-channel events, so we need to store the RTCP + report in realtime when we have it. + Tests have proven that storing to realtime from the call thread is NOT a good thing. Therefore, we just save + the quality report structure in the PVT and let the function that kills the pvt store the stuff in the + monitor thread instead. + */ + if (reporttype == 1) { + ast_log(LOG_DEBUG, "---- Activation qual structure in dialog \n"); + qual.end = ast_tvnow(); + qual.mediatype = media; + if (media == SDP_AUDIO) { /* Audio */ + dialog->audioqual = ast_calloc(1, sizeof(struct ast_rtp_instance_stats)); + (* dialog->audioqual) = qual; + } else if (media == SDP_VIDEO) { /* Video */ + dialog->videoqual = ast_calloc(1,sizeof(struct ast_rtp_instance_stats)); + (* dialog->videoqual) = qual; + } + } +} + +/*! \brief Write quality report to realtime storage */ +void qos_write_realtime(struct sip_pvt *dialog, struct ast_rtp_instance_stats *qual) +{ + unsigned int duration; /* Duration in secs */ + char buf_duration[10], buf_lssrc[30], buf_rssrc[30]; + char buf_rtt[10], buf_rttmin[10], buf_rttmax[10]; + char localjitter[10], remotejitter[10]; + char buf_readcost[5], buf_writecost[5]; + char buf_mediatype[10]; + char buf_remoteip[25]; + char buf_inpacketloss[25], buf_outpacketloss[25]; + char buf_outpackets[25], buf_inpackets[25]; + int qosrealtime = ast_check_realtime("rtpcqr"); + + + if (!qual) { + ast_log(LOG_ERROR, "No CQR data provided \n"); + return; + } + + /* Since the CDR is already gone, we need to calculate our own duration. + The CDR duration is the definitive resource for billing, this is + the RTP stream duration which may include early media (ringing and + provider messages). Only useful for measurements. + */ + if (!ast_tvzero(qual->end) && !ast_tvzero(qual->start)) { + duration = (unsigned int)(ast_tvdiff_ms(qual->end, qual->start) / 1000); + } else { + ast_debug(2, "**** What? No duration? What type of call is THAT? \n"); + duration = 0; + } + + /* Realtime is based on strings, so let's make strings */ + sprintf(localjitter, "%f", qual->rxjitter); + sprintf(remotejitter, "%f", qual->txjitter); + sprintf(buf_lssrc, "%u", qual->local_ssrc); + sprintf(buf_rssrc, "%u", qual->remote_ssrc); + sprintf(buf_rtt, "%.0f", qual->rtt); + sprintf(buf_rttmax, "%.0f", qual->maxrtt); + sprintf(buf_rttmin, "%.0f", qual->minrtt); + sprintf(buf_duration, "%u", duration); + sprintf(buf_readcost, "%d", qual->readcost); + sprintf(buf_writecost, "%d", qual->writecost); + sprintf(buf_mediatype,"%s", qual->mediatype == SDP_AUDIO ? "audio" : (qual->mediatype == SDP_VIDEO ? "video" : "fax") ); + sprintf(buf_remoteip,"%s", ast_inet_ntoa(qual->them.sin_addr)); + sprintf(buf_inpacketloss, "%d", qual->rxploss); + sprintf(buf_outpacketloss, "%d", qual->txploss); + sprintf(buf_inpackets, "%d", qual->rxcount); /* Silly value. Need to check this */ + sprintf(buf_outpackets, "%d", qual->txcount); + //sprintf(buf_inpackets, "%d", qual->remote_count); /* Do check again */ + //sprintf(buf_outpackets, "%d", qual->local_count); + + ast_log(LOG_CQR, "CQR Channel: %s Uid %s Bch %s Buid %s Pvt %s Media %s Lssrc %s Rssrc %s Rip %s Rtt %s:%s:%s Ljitter %s Rjitter %s Rtcpstatus %s Dur %s Pout %s Plossout %s Pin %s Plossin %s\n", + qual->channel[0] ? qual->channel : "", + qual->uniqueid[0] ? qual->uniqueid : "", + qual->bridgedchannel[0] ? qual->bridgedchannel : "" , + qual->bridgeduniqueid[0] ? qual->bridgeduniqueid : "", + dialog->callid, + buf_mediatype, + buf_lssrc, + buf_rssrc, + buf_remoteip, + buf_rtt, buf_rttmax, buf_rttmin, + localjitter, + remotejitter, + qual->numberofreports == 0 ? "Inactive" : "Active", + buf_duration, + buf_outpackets, + buf_outpacketloss, + buf_inpackets, + buf_inpacketloss); + + if (!qosrealtime) { + return; + } +/* Example database schema for MySQL: +CREATE TABLE `astcqr` ( + `channel` varchar(50) NOT NULL, + `uniqueid` varchar(35) NOT NULL, + `bridgedchannel` varchar(50) NOT NULL, + `bridgeduniqueid` varchar(35) NOT NULL, + `pvtcallid` varchar(80) NOT NULL, + `rtpmedia` varchar(50) NOT NULL, + `localssrc` varchar(50) NOT NULL, + `remotessrc` varchar(50) NOT NULL, + `rtt` varchar(10) NOT NULL, + `localjitter` varchar(10) NOT NULL, + `remotejitter` varchar(10) NOT NULL, + `sendformat` varchar(10) NOT NULL, + `receiveformat` varchar(10) NOT NULL, + `rtcpstatus` varchar(10) NOT NULL, + `duration` varchar(10) NOT NULL, + `packetsent` varchar(30) NOT NULL, + `packetreceived` varchar(30) NOT NULL, + `packetlossin` varchar(30) NOT NULL, + `packetlossout` varchar(30) NOT NULL, + `rttmax` varchar(12) NOT NULL, + `rttmin` varchar(12) NOT NULL, + `writetranslator` varchar(15) NOT NULL, + `readtranslator` varchar(15) NOT NULL, + `writecost` varchar(10) NOT NULL, + `readcost` varchar(10) NOT NULL, + `remoteip` varchar(25) NOT NULL, + KEY `ChannelUnique` (`channel`,`uniqueid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='FOr pinefrog stats' +*/ + + ast_store_realtime("rtpcqr", + "channel", qual->channel[0] ? qual->channel : "--no channel--", + "uniqueid", qual->uniqueid[0] ? qual->uniqueid : "--no uniqueid --", + "bridgedchannel", qual->bridgedchannel[0] ? qual->bridgedchannel : "" , + "bridgeduniqueid", qual->bridgeduniqueid[0] ? qual->bridgeduniqueid : "", + "pvtcallid", dialog->callid, + "rtpmedia", buf_mediatype, + "localssrc", buf_lssrc, + "remotessrc", buf_rssrc, + "remoteip", buf_remoteip, + "rtt", buf_rtt, + "rttmax", buf_rttmax, + "rttmin", buf_rttmin, + "localjitter", localjitter, + "remotejitter", remotejitter, + "sendformat", ast_getformatname(&qual->lasttxformat), + "receiveformat", ast_getformatname(&qual->lastrxformat), + "rtcpstatus", qual->numberofreports == 0 ? "Inactive" : "Active", + "duration", buf_duration, + "writetranslator", qual->writetranslator[0] ? qual->writetranslator : "", + "writecost", buf_writecost, + "readtranslator", qual->readtranslator[0] ? qual->readtranslator : "", + "readcost", buf_readcost, + "packetlossin", buf_inpacketloss, + "packetlossout", buf_outpacketloss, + "packetsent", buf_outpackets, + "packetreceived", buf_inpackets, + NULL); +} + +/*! \brief Send RTCP manager events */ +int send_rtcp_events(const void *data) +{ + struct sip_pvt *dialog = (struct sip_pvt *) data; + ast_log(LOG_DEBUG, "***** SENDING RTCP EVENT \n"); + + if (dialog->rtp && !ast_rtp_instance_isactive(dialog->rtp)) { + ast_debug(1, " ***** Activating RTCP report \n"); + sip_rtcp_report(dialog, dialog->rtp, SDP_AUDIO, FALSE); + } else { + ast_debug(1, " ***** NOT Activating RTCP report \n"); + } + if (dialog->vrtp && !ast_rtp_instance_isactive(dialog->vrtp)) { + sip_rtcp_report(dialog, dialog->vrtp, SDP_VIDEO, FALSE); + } + return (dialog->sip_cfg ? dialog->sip_cfg->rtcptimer : 0); +} + +/*! \brief Activate RTCP events at start of call */ +void start_rtcp_events(struct sip_pvt *dialog, struct ast_sched_context *sched) +{ + ast_debug(2, "***** STARTING SENDING RTCP EVENT \n"); + /* Check if it's already active */ + + if (dialog->rtp && !ast_rtp_instance_isactive(dialog->rtp)) { + sip_rtcp_set_data(dialog, dialog->rtp, SDP_AUDIO); + } + if (dialog->vrtp && !ast_rtp_instance_isactive(dialog->vrtp)) { + sip_rtcp_set_data(dialog, dialog->vrtp, SDP_VIDEO); + } + + if (!dialog->sip_cfg->rtcpevents || !dialog->sip_cfg->rtcptimer) { + ast_debug(2, "***** NOT SENDING RTCP EVENTS \n"); + return; + } + + if (dialog->rtcpeventid != -1) { + return; + } + + + /*! \brief Schedule events */ + dialog->rtcpeventid = ast_sched_add(sched, dialog->sip_cfg->rtcptimer * 1000, send_rtcp_events, dialog); +} Property changes on: channels/sip/rtcp.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Index: channels/sip/dialplan_functions.c =================================================================== --- channels/sip/dialplan_functions.c (revision 383046) +++ channels/sip/dialplan_functions.c (working copy) @@ -162,6 +162,11 @@ if (!ast_rtp_instance_get_quality(rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf))) { return -1; } +#ifdef OEJ + if (!ast_rtp_instance_get_qualdata(rtp, ???, &qos)) { + this_needs_some_love; + } +#endif ast_copy_string(buf, quality_buf, buflen); return res; Index: channels/sip/include/rtcp.h =================================================================== --- channels/sip/include/rtcp.h (revision 0) +++ channels/sip/include/rtcp.h (revision 404591) @@ -0,0 +1,50 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013 Olle E. Johansson, Edvina AB + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file rtcp.h + * + * \brief RTCP additional functions + * + * \author Olle E. Johansson + */ + +#include "asterisk.h" + +#include "asterisk/utils.h" +#include "asterisk/rtp_engine.h" +#include "sip.h" + +#ifndef _SIP_RTCP_H +#define _SIP_RTCP_H + +/*! \brief Set various data items in the RTP structure, like channel identifier. + */ +void sip_rtcp_set_data(struct sip_pvt *dialog, struct ast_rtp_instance *instance, enum media_type type); + +int send_rtcp_events(const void *data); +void start_rtcp_events(struct sip_pvt *dialog, struct ast_sched_context *sched); +/* +# For 1.4: +# static void sip_rtcp_report(struct sip_pvt *p, struct ast_rtp *rtp, enum media_type type, int reporttype); +*/ + +void sip_rtcp_report(struct sip_pvt *dialog, struct ast_rtp_instance *instance, enum media_type type, int reporttype); +//void qos_write_realtime(struct sip_pvt *dialog, struct ast_rtp_quality *qual); +void qos_write_realtime(struct sip_pvt *dialog, struct ast_rtp_instance_stats *qual); + + + +#endif /* _SIP_RTCP_H */ Property changes on: channels/sip/include/rtcp.h ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: channels/sip/include/sip.h =================================================================== --- channels/sip/include/sip.h (revision 383046) +++ channels/sip/include/sip.h (working copy) @@ -750,6 +750,8 @@ int compactheaders; /*!< send compact sip headers */ int allow_external_domains; /*!< Accept calls to external SIP domains? */ int callevents; /*!< Whether we send manager events or not */ + int rtcpevents; /*!< Whether we send manager RTCP events or not */ + int rtcptimer; /*!< How often, during a call, to report RTCP stats */ int regextenonqualify; /*!< Whether to add/remove regexten when qualifying peers */ int legacy_useroption_parsing; /*!< Whether to strip useroptions in URI via semicolons */ int send_diversion; /*!< Whether to Send SIP Diversion headers */ @@ -1159,6 +1161,7 @@ int waitid; /*!< Wait ID for scheduler after 491 or other delays */ int reinviteid; /*!< Reinvite in case of provisional, but no final response */ int autokillid; /*!< Auto-kill ID (scheduler) */ + int rtcpeventid; /*!< Scheduler ID for RTCP Events */ int t38id; /*!< T.38 Response ID */ struct sip_refer *refer; /*!< REFER: SIP transfer data structure */ enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */ @@ -1188,6 +1191,9 @@ struct sip_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */ struct sip_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */ struct sip_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */ + struct ast_rtp_instance_stats *audioqual; /*!< Audio: The latest quality report, for realtime storage */ + struct ast_rtp_instance_stats *videoqual; /*!< Video: The latest quality report, for realtime storage */ + struct sip_settings *sip_cfg; /*! Which sip_cfg is associated with this dialog */ int red; /*!< T.140 RTP Redundancy */ int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 383046) +++ channels/chan_sip.c (working copy) @@ -281,6 +281,7 @@ #include "sip/include/dialog.h" #include "sip/include/dialplan_functions.h" #include "sip/include/security_events.h" +#include "sip/include/rtcp.h" #include "asterisk/sip_api.h" /*** DOCUMENTATION @@ -724,7 +725,6 @@ { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"}, }; - /*! \name DefaultSettings Default setttings are used as a channel setting and as a default when configuring devices @@ -6513,6 +6513,22 @@ if (dumphistory) sip_dump_history(p); + AST_SCHED_DEL(sched, p->rtcpeventid); + + if (p->audioqual) { + /* We have a quality report to write to realtime before we leave this world. */ + qos_write_realtime(p, p->audioqual); + free(p->audioqual); + p->audioqual = NULL; + } + if (p->videoqual) { + /* We have a quality report to write to realtime before we leave this world. */ + qos_write_realtime(p, p->videoqual); + free(p->videoqual); + p->videoqual = NULL; + } + + if (p->options) { if (p->options->outboundproxy) { ao2_ref(p->options->outboundproxy, -1); @@ -7257,6 +7273,7 @@ ast_rtp_instance_update_source(p->rtp); res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE, TRUE); ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + start_rtcp_events(p, sched); } sip_pvt_unlock(p); return res; @@ -8639,8 +8656,10 @@ set_socket_transport(&p->socket, SIP_TRANSPORT_UDP); } + p->sip_cfg = &sip_cfg; p->socket.fd = -1; p->method = intended_method; + p->rtcpeventid = -1; p->initid = -1; p->waitid = -1; p->reinviteid = -1; @@ -8702,6 +8721,14 @@ build_callid_pvt(p); else ast_string_field_set(p, callid, callid); + + /* Set cnames for the RTCP SDES */ + if (p->rtp) { + ast_rtp_instance_setcname(p->rtp, p->callid, strlen(p->callid)); + } + if (p->vrtp) { + ast_rtp_instance_setcname(p->vrtp, p->callid, strlen(p->callid)); + } /* Assign default music on hold class */ ast_string_field_set(p, mohinterpret, default_mohinterpret); ast_string_field_set(p, mohsuggest, default_mohsuggest); @@ -9744,6 +9771,7 @@ ast_channel_uniqueid(dialog->owner)); append_history(dialog, holdstate ? "Hold" : "Unhold", "%s", ast_str_buffer(req->data)); if (!holdstate) { /* Put off remote hold */ + ast_rtp_instance_hold(dialog->rtp, 0); /* Turn off RTP hold */ ast_clear_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD); /* Clear both flags */ return; } @@ -10713,9 +10741,9 @@ ast_queue_control_data(p->owner, AST_CONTROL_HOLD, S_OR(p->mohsuggest, NULL), !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0); - if (sendonly) - ast_rtp_instance_stop(p->rtp); - /* RTCP needs to go ahead, even if we're on hold!!! */ + if (sendonly == 1 || sendonly == 2) { /* sendonly (from the other side) or inactive */ + ast_rtp_instance_hold(p->rtp, 1); + } /* Activate a re-invite */ ast_queue_frame(p->owner, &ast_null_frame); change_hold_state(p, req, TRUE, sendonly); @@ -19727,8 +19755,10 @@ int x = 0, load_realtime; struct ast_format codec; int realtimepeers; + int realtimertpqos = FALSE; realtimepeers = ast_check_realtime("sippeers"); + realtimertpqos = ast_check_realtime("rtpcqr"); if (argc < 4) return CLI_SHOWUSAGE; @@ -20371,10 +20401,12 @@ { int realtimepeers; int realtimeregs; + int realtimertpqos; char codec_buf[SIPBUFSIZE]; const char *msg; /* temporary msg pointer */ struct sip_auth_container *credentials; + switch (cmd) { case CLI_INIT: e->command = "sip show settings"; @@ -20391,6 +20423,7 @@ realtimepeers = ast_check_realtime("sippeers"); realtimeregs = ast_check_realtime("sipregs"); + realtimertpqos = ast_check_realtime("rtpcqr"); ast_mutex_lock(&authl_lock); credentials = authl; @@ -20463,6 +20496,8 @@ } ast_cli(a->fd, " Record SIP history: %s\n", AST_CLI_ONOFF(recordhistory)); ast_cli(a->fd, " Call Events: %s\n", AST_CLI_ONOFF(sip_cfg.callevents)); + ast_cli(a->fd, " RTCP Events: %s\n", AST_CLI_ONOFF(sip_cfg.rtcpevents)); + ast_cli(a->fd, " RTCP Event timer: %d\n", sip_cfg.rtcptimer); ast_cli(a->fd, " Auth. Failure Events: %s\n", AST_CLI_ONOFF(global_authfailureevents)); ast_cli(a->fd, " T.38 support: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT))); @@ -20472,6 +20507,7 @@ ast_cli(a->fd, " SIP realtime: Disabled\n" ); else ast_cli(a->fd, " SIP realtime: Enabled\n" ); + ast_cli(a->fd, " QoS realtime reports: %s\n", realtimertpqos ? "Enabled" : "Disabled" ); ast_cli(a->fd, " Qualify Freq : %d ms\n", global_qualifyfreq); ast_cli(a->fd, " Q.850 Reason header: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_Q850_REASON))); ast_cli(a->fd, " Store SIP_CAUSE: %s\n", AST_CLI_YESNO(global_store_sip_cause)); @@ -22491,6 +22527,7 @@ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } check_pendings(p); + start_rtcp_events(p, sched); break; case 180: /* 180 Ringing */ @@ -22778,6 +22815,7 @@ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE); check_pendings(p); + start_rtcp_events(p, sched); break; case 407: /* Proxy authentication */ @@ -23487,16 +23525,21 @@ } } -/*! \brief Immediately stop RTP, VRTP and UDPTL as applicable */ +/*! \brief Immediately stop RTP, VRTP, TEXT and UDPTL as applicable */ static void stop_media_flows(struct sip_pvt *p) { /* Immediately stop RTP, VRTP and UDPTL as applicable */ - if (p->rtp) + if (p->rtp && !ast_rtp_instance_isactive(p->rtp)) { + sip_rtcp_report(p, p->rtp, SDP_AUDIO, TRUE); ast_rtp_instance_stop(p->rtp); - if (p->vrtp) + } + if (p->vrtp && !ast_rtp_instance_isactive(p->vrtp)) { + sip_rtcp_report(p, p->vrtp, SDP_VIDEO, TRUE); ast_rtp_instance_stop(p->vrtp); - if (p->trtp) + } + if (p->trtp && !ast_rtp_instance_isactive(p->trtp)) { ast_rtp_instance_stop(p->trtp); + } if (p->udptl) ast_udptl_stop(p->udptl); } @@ -26514,6 +26557,7 @@ if (p->rtp && (quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { + sip_rtcp_report(p, p->rtp, SDP_AUDIO, TRUE); if (p->do_history) { append_history(p, "RTCPaudio", "Quality:%s", quality); @@ -26544,6 +26588,7 @@ } if (p->vrtp && (quality = ast_rtp_instance_get_quality(p->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { + sip_rtcp_report(p, p->rtp, SDP_VIDEO, TRUE); if (p->do_history) { append_history(p, "RTCPvideo", "Quality:%s", quality); } @@ -31262,6 +31307,8 @@ /* Misc settings for the channel */ global_relaxdtmf = FALSE; sip_cfg.callevents = DEFAULT_CALLEVENTS; + sip_cfg.rtcpevents = FALSE; + sip_cfg.rtcptimer = 0; /* Only report at end of call (default) */ global_authfailureevents = FALSE; global_t1 = DEFAULT_TIMER_T1; global_timer_b = 64 * DEFAULT_TIMER_T1; @@ -31738,6 +31785,13 @@ ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d of %s\n", v->value, v->lineno, config); global_qualifyfreq = DEFAULT_QUALIFYFREQ; } + } else if (!strcasecmp(v->name, "rtcpevents")) { + sip_cfg.rtcpevents = ast_true(v->value); + } else if (!strcasecmp(v->name, "rtcpeventtimer")) { + if (sscanf(v->value, "%30d", &sip_cfg.rtcptimer) != 1) { + ast_log(LOG_WARNING, "RTCP event timer needs to be value (seconds between reports) at line %d of sip.conf\n", v->lineno); + sip_cfg.rtcptimer = 0; + } } else if (!strcasecmp(v->name, "callevents")) { sip_cfg.callevents = ast_true(v->value); } else if (!strcasecmp(v->name, "authfailureevents")) { Index: . =================================================================== --- . (revision 383046) +++ . (working copy) Property changes on: . ___________________________________________________________________ Added: svnmerge-integrated ## -0,0 +1 ## +/branches/11:1-382828 \ No newline at end of property