/* * Copyright (C) 2012 Doubango Telecom * * Contact: Mamadou Diop * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. * */ /**@file tdav_session_av.c * @brief Audio/Video base Session plugin * * @author Mamadou Diop */ #include "tinydav/tdav_session_av.h" #include "tinydav/codecs/dtmf/tdav_codec_dtmf.h" #include "tinydav/codecs/fec/tdav_codec_red.h" #include "tinydav/codecs/fec/tdav_codec_ulpfec.h" #include "tinyrtp/trtp_manager.h" #include "tinyrtp/rtp/trtp_rtp_packet.h" #include "ice/tnet_ice_ctx.h" #include "ice/tnet_ice_candidate.h" #include "tinymedia/tmedia_consumer.h" #include "tinymedia/tmedia_producer.h" #include "tinymedia/tmedia_defaults.h" #define TDAV_IS_DTMF_CODEC(codec) (TMEDIA_CODEC((codec))->plugin == tdav_codec_dtmf_plugin_def_t) #define TDAV_IS_ULPFEC_CODEC(codec) (TMEDIA_CODEC((codec))->plugin == tdav_codec_ulpfec_plugin_def_t) #define TDAV_IS_RED_CODEC(codec) (TMEDIA_CODEC((codec))->plugin == tdav_codec_red_plugin_def_t) int tdav_session_av_init(tdav_session_av_t* self, tsk_bool_t is_audio) { uint64_t session_id; tmedia_profile_t profile = tmedia_defaults_get_profile(); // default profile, will be updated by the SIP session if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* base::init(): called by tmedia_session_create() */ self->media_type = is_audio ? tmedia_audio : tmedia_video; self->use_rtcp = tmedia_defaults_get_rtcp_enabled(); self->use_rtcpmux = tmedia_defaults_get_rtcpmux_enabled(); self->use_avpf = (profile == tmedia_profile_rtcweb); // negotiate if not RTCWeb profile tsk_safeobj_init(self); // session id if(!(session_id = TMEDIA_SESSION(self)->id)){ // set the session id if not already done TMEDIA_SESSION(self)->id = session_id = tmedia_session_get_unique_id(); } // consumer TSK_OBJECT_SAFE_FREE(self->consumer); if(!(self->consumer = tmedia_consumer_create(self->media_type, session_id))){ TSK_DEBUG_ERROR("Failed to create %s consumer", is_audio ? "audio" : "video"); } // producer TSK_OBJECT_SAFE_FREE(self->producer); if(!(self->producer = tmedia_producer_create(self->media_type, session_id))){ TSK_DEBUG_ERROR("Failed to create %s producer", is_audio ? "audio" : "video"); } #if HAVE_SRTP // This is the default value and can be updated by the user using "session_set('srtp-mode', mode_e)" self->srtp_mode = (profile == tmedia_profile_rtcweb) ? tmedia_srtp_mode_mandatory : tmedia_defaults_get_srtp_mode(); self->use_srtp = (self->srtp_mode == tmedia_srtp_mode_mandatory); // if optional -> negotiate #endif return 0; } tsk_bool_t tdav_session_av_set(tdav_session_av_t* self, const tmedia_param_t* param) { if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_false; } if(param->plugin_type == tmedia_ppt_consumer && self->consumer){ return (tmedia_consumer_set(self->consumer, param) == 0); } else if(param->plugin_type == tmedia_ppt_producer && self->producer){ return tmedia_producer_set(self->producer, param); } else if(param->plugin_type == tmedia_ppt_session){ if(param->value_type == tmedia_pvt_pchar){ if(tsk_striequals(param->key, "remote-ip")){ if(param->value){ tsk_strupdate(&self->remote_ip, param->value); return tsk_true; } } else if(tsk_striequals(param->key, "local-ip")){ tsk_strupdate(&self->local_ip, param->value); return tsk_true; } else if(tsk_striequals(param->key, "local-ipver")){ self->use_ipv6 = tsk_striequals(param->value, "ipv6"); return tsk_true; } } else if(param->value_type == tmedia_pvt_int32){ if(tsk_striequals(param->key, "srtp-mode")){ #if HAVE_SRTP self->srtp_mode = (tmedia_srtp_mode_t)TSK_TO_INT32((uint8_t*)param->value); return tsk_true; #endif } else if(tsk_striequals(param->key, "rtcp-enabled")){ self->use_rtcp = (TSK_TO_INT32((uint8_t*)param->value) != 0); return tsk_true; } else if(tsk_striequals(param->key, "rtcpmux-enabled")){ self->use_rtcpmux = (TSK_TO_INT32((uint8_t*)param->value) != 0); return tsk_true; } else if(tsk_striequals(param->key, "avpf-enabled")){ self->use_avpf = (TSK_TO_INT32((uint8_t*)param->value) != 0); return tsk_true; } } else if(param->value_type == tmedia_pvt_pobject){ if(tsk_striequals(param->key, "natt-ctx")){ TSK_OBJECT_SAFE_FREE(self->natt_ctx); self->natt_ctx = tsk_object_ref(param->value); return tsk_true; } else if(tsk_striequals(param->key, "ice-ctx")){ TSK_OBJECT_SAFE_FREE(self->ice_ctx); self->ice_ctx = tsk_object_ref(param->value); if(self->rtp_manager){ trtp_manager_set_ice_ctx(self->rtp_manager, self->ice_ctx); } return tsk_true; } } } return tsk_false; } tsk_bool_t tdav_session_av_get(tdav_session_av_t* self, tmedia_param_t* param) { if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_false; } if(param->plugin_type == tmedia_ppt_session){ if(param->value_type == tmedia_pvt_int32){ if(tsk_striequals(param->key, "srtp-enabled")){ #if HAVE_SRTP if(self->rtp_manager){ ((int8_t*)param->value)[0] = self->use_srtp ? 1 : 0; return tsk_true; } #endif } } } return tsk_false; } int tdav_session_av_prepare(tdav_session_av_t* self) { int ret = 0; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* set local port */ if(!self->rtp_manager){ self->rtp_manager = self->ice_ctx ? trtp_manager_create_2(self->ice_ctx) : trtp_manager_create(self->use_rtcp, self->local_ip, self->use_ipv6); if(self->rtp_manager){ ret = trtp_manager_set_port_range(self->rtp_manager, tmedia_defaults_get_rtp_port_range_start(), tmedia_defaults_get_rtp_port_range_stop()); self->rtp_manager->use_rtcp = self->use_rtcp; ret = trtp_manager_prepare(self->rtp_manager); if(self->natt_ctx){ ret = trtp_manager_set_natt_ctx(self->rtp_manager, self->natt_ctx); } } } /* SRTP */ #if HAVE_SRTP { if(self->remote_srtp_neg.pending){ char* str = tsk_null; self->remote_srtp_neg.pending = tsk_false; tsk_sprintf(&str, "%d %s inline:%s", self->remote_srtp_neg.tag, trtp_srtp_crypto_type_strings[self->remote_srtp_neg.crypto_type], self->remote_srtp_neg.key); trtp_srtp_set_remote(self->rtp_manager, str); TSK_FREE(str); } } #endif /* Consumer will be prepared in tdav_session_audio_start() */ /* Producer will be prepared in tdav_session_audio_start() */ return ret; } int tdav_session_av_start(tdav_session_av_t* self, const tmedia_codec_t* best_codec) { if(!self || !best_codec){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } if(self->rtp_manager){ int ret; /* RTP/RTCP manager: use latest information. */ // these information will be updated when the RTP manager starts if ICE is enabled ret = trtp_manager_set_rtp_remote(self->rtp_manager, self->remote_ip, self->remote_port); ret = trtp_manager_set_payload_type(self->rtp_manager, best_codec->neg_format ? atoi(best_codec->neg_format) : atoi(best_codec->format)); self->rtp_manager->use_rtcpmux = self->use_rtcpmux; ret = trtp_manager_start(self->rtp_manager); // because of AudioUnit under iOS => prepare both consumer and producer then start() at the same time /* prepare consumer and producer */ if(self->producer) ret = tmedia_producer_prepare(self->producer, best_codec); if(self->consumer) ret = tmedia_consumer_prepare(self->consumer, best_codec); /* start consumer and producer */ if(self->consumer) ret = tmedia_consumer_start(self->consumer); if(self->producer) ret = tmedia_producer_start(self->producer); // not that the RTP manager is activated check that SRTP is correctly activated #if HAVE_SRTP self->use_srtp = trtp_srtp_is_active(self->rtp_manager); #endif return ret; } else{ TSK_DEBUG_ERROR("Invalid RTP/RTCP manager"); return -3; } return 0; } int tdav_session_av_stop(tdav_session_av_t* self) { tmedia_codec_t* codec; tsk_list_item_t* item; int ret = 0; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* Consumer */ if(self->consumer){ ret = tmedia_consumer_stop(self->consumer); } /* Producer */ if(self->producer){ ret = tmedia_producer_stop(self->producer); } /* RTP/RTCP manager */ if(self->rtp_manager){ ret = trtp_manager_stop(self->rtp_manager); } /* close codecs to force open() for next start (e.g SIP UPDATE with SDP) */ if(TMEDIA_SESSION(self)->neg_codecs){ tsk_list_foreach(item, TMEDIA_SESSION(self)->neg_codecs){ if(!(codec = TMEDIA_CODEC(item->data))){ continue; } ret = tmedia_codec_close(codec); } } return ret; } int tdav_session_av_pause(tdav_session_av_t* self) { int ret = 0; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* Consumer */ if(self->consumer){ ret = tmedia_consumer_pause(self->consumer); } /* Producer */ if(self->producer){ ret = tmedia_producer_pause(self->producer); } return ret; } const tsdp_header_M_t* tdav_session_av_get_lo(tdav_session_av_t* self, tsk_bool_t *updated) { tmedia_session_t* base = TMEDIA_SESSION(self); tsk_bool_t have_libsrtp = tsk_false; if(!base || !base->plugin || !updated){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_null; } *updated = tsk_false; #if HAVE_SRTP have_libsrtp = tsk_true; #endif if(!self->rtp_manager || (!self->ice_ctx && !self->rtp_manager->transport)){ if(self->rtp_manager && (!self->ice_ctx && !self->rtp_manager->transport)){ // reINVITE or UPDATE (manager was destroyed when stoppped) if(trtp_manager_prepare(self->rtp_manager)){ TSK_DEBUG_ERROR("Failed to prepare transport"); return tsk_null; } } else{ TSK_DEBUG_ERROR("RTP/RTCP manager in invalid"); return tsk_null; } } if(base->ro_changed && base->M.lo){ /* Codecs */ tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "fmtp"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "rtpmap"); tsk_list_clear_items(base->M.lo->FMTs); /* QoS */ tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "curr"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "des"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "conf"); /* SRTP */ tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "crypto"); /* ICE */ tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "candidate"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "ice-ufrag"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "ice-pwd"); /* SDPCapNeg */ tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "tcap"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "acap"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "pcfg"); // Others tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "mid"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "rtcp-mux"); tsdp_header_A_removeAll_by_field(base->M.lo->Attributes, "ssrc"); } *updated = (base->ro_changed || !base->M.lo); if(!base->M.lo){ if((base->M.lo = tsdp_header_M_create(base->plugin->media, self->rtp_manager->rtp.public_port, "RTP/AVP"))){ /* If NATT is active, do not rely on the global IP address Connection line */ if(self->natt_ctx){ tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_C_VA_ARGS("IN", self->use_ipv6 ? "IP6" : "IP4", self->rtp_manager->rtp.public_ip), tsk_null); } /* 3GPP TS 24.229 - 6.1.1 General In order to support accurate bandwidth calculations, the UE may include the "a=ptime" attribute for all "audio" media lines as described in RFC 4566 [39]. If a UE receives an "audio" media line with "a=ptime" specified, the UE should transmit at the specified packetization rate. If a UE receives an "audio" media line which does not have "a=ptime" specified or the UE does not support the "a=ptime" attribute, the UE should transmit at the default codec packetization rate as defined in RFC 3551 [55A]. The UE will transmit consistent with the resources available from the network. For "video" and "audio" media types that utilize the RTP/RTCP, the UE shall specify the proposed bandwidth for each media stream utilizing the "b=" media descriptor and the "AS" bandwidth modifier in the SDP. The UE shall include the MIME subtype "telephone-event" in the "m=" media descriptor in the SDP for audio media flows that support both audio codec and DTMF payloads in RTP packets as described in RFC 4733 [23]. */ if(self->media_type == tmedia_audio){ tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ptime", "20"), tsk_null); // the "telephone-event" fmt/rtpmap is added below } } else{ TSK_DEBUG_ERROR("Failed to create lo"); return tsk_null; } } if(*updated){ tmedia_codecs_L_t* neg_codecs = tsk_null; if(base->M.ro){ TSK_OBJECT_SAFE_FREE(base->neg_codecs); /* update negociated codecs */ if((neg_codecs = tmedia_session_match_codec(base, base->M.ro))){ base->neg_codecs = neg_codecs; } /* from codecs to sdp */ if(TSK_LIST_IS_EMPTY(base->neg_codecs) || ((base->neg_codecs->tail == base->neg_codecs->head) && TDAV_IS_DTMF_CODEC(TSK_LIST_FIRST_DATA(base->neg_codecs)))){ base->M.lo->port = 0; /* Keep the RTP transport and reuse it when we receive a reINVITE or UPDATE request */ goto DONE; } else{ tmedia_codec_to_sdp(base->neg_codecs, base->M.lo); } } else{ /* from codecs to sdp */ tmedia_codec_to_sdp(base->codecs, base->M.lo); } /* SDPCapNeg: RFC 5939 */ { if(base->M.ro){ self->use_avpf = (tsk_striequals(base->M.ro->proto, "RTP/AVPF") || tsk_striequals(base->M.ro->proto, "RTP/SAVPF")); if(!self->use_avpf){ if(self->sdp_neg.remote_best_pcfg.t_proto){ self->use_avpf = (tsk_striequals(self->sdp_neg.remote_best_pcfg.t_proto, "RTP/AVPF") || tsk_striequals(self->sdp_neg.remote_best_pcfg.t_proto, "RTP/SAVPF")); } } if(self->sdp_neg.remote_best_pcfg.t_proto){ // Any supported proto -> response with "a=acfg" if(self->use_avpf/* AVPF,SAVPF */ || tsk_striequals(self->sdp_neg.remote_best_pcfg.t_proto, "RTP/AVP") || tsk_striequals(self->sdp_neg.remote_best_pcfg.t_proto, "RTP/SAVP")){ char *str_acfg = tsk_null; tsk_sprintf(&str_acfg, "%d t=%d", self->sdp_neg.remote_best_pcfg.tag, self->sdp_neg.remote_best_pcfg.t_tag); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("acfg", str_acfg), tsk_null); TSK_FREE(str_acfg); } } } else{ if(!self->use_avpf){ // only negotiate if not already using AVPF #if HAVE_SRTP tsk_bool_t enable_srtp = (have_libsrtp && (self->srtp_mode == tmedia_srtp_mode_mandatory || self->srtp_mode == tmedia_srtp_mode_optional)); // "a=acap:1 crypto" is not included because most of SIP client don't support RFC 5939 // "a=crypto" is always used to indicate optional support for SRTP tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("tcap", enable_srtp ? "1 RTP/SAVPF" : "1 RTP/AVPF"), TSDP_HEADER_A_VA_ARGS("pcfg", "1 t=1"), tsk_null); #endif } } } /* Hold/Resume */ tsdp_header_M_set_holdresume_att(base->M.lo, base->lo_held, base->ro_held); /* SRTP */ #if HAVE_SRTP { tsk_bool_t is_srtp_remote_mandatory = (base->M.ro && (tsk_striequals(base->M.ro->proto, "RTP/SAVP") || tsk_striequals(base->M.ro->proto, "RTP/SAVPF"))); tsk_bool_t is_srtp_remote_optional = (base->M.ro && (tsdp_header_M_findA(base->M.ro, "crypto") != tsk_null)); if((self->srtp_mode == tmedia_srtp_mode_optional && (is_srtp_remote_optional || is_srtp_remote_mandatory || !base->M.ro)) || self->srtp_mode == tmedia_srtp_mode_mandatory){ const trtp_srtp_ctx_xt *ctx = tsk_null; tsk_size_t ctx_count = 0, ctx_idx; char* str = tsk_null; // local trtp_srtp_get_ctx_local(self->rtp_manager, &ctx, &ctx_count); for(ctx_idx = 0; ctx_idx < ctx_count; ++ctx_idx){ tsk_sprintf(&str, "%d %s inline:%s", ctx[ctx_idx].tag, trtp_srtp_crypto_type_strings[ctx[ctx_idx].crypto_type], ctx[ctx_idx].key_str); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("crypto", str), tsk_null); TSK_FREE(str); } } if(is_srtp_remote_mandatory || (self->srtp_mode == tmedia_srtp_mode_mandatory) || trtp_srtp_is_initialized(self->rtp_manager)){ self->use_srtp = tsk_true; } } #endif /* Update Proto*/ tsk_strupdate(&base->M.lo->proto, self->use_srtp ? (self->use_avpf ? "RTP/SAVPF" : "RTP/SAVP") : (self->use_avpf ? "RTP/AVPF" : "RTP/AVP") ); // RFC 5761: RTCP/RTP muxing if(self->use_rtcpmux){ tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("rtcp-mux", tsk_null), tsk_null); } // draft-lennox-mmusic-sdp-source-attributes-01 { char* str = tsk_null; tsk_sprintf(&str, "%u cname:%s", self->rtp_manager->rtp.ssrc, "ldjWoB60jbyQlR6e"); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); tsk_sprintf(&str, "%u mslabel:%s", self->rtp_manager->rtp.ssrc, "6994f7d1-6ce9-4fbd-acfd-84e5131ca2e2"); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); tsk_sprintf(&str, "%u label:%s", self->rtp_manager->rtp.ssrc, "Doubango"); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ssrc", str), tsk_null); TSK_FREE(str); } /* ICE */ if(self->ice_ctx){ tsk_size_t index = 0; const tnet_ice_candidate_t* candidate; tsk_bool_t remote_use_rtcpmux = (base->M.ro && (tsdp_header_M_findA(base->M.ro, "rtcp-mux") != tsk_null)); // FIXME: for RTCP, use "RFC 3605"in addition to "rtcp-mux" // "a=ice-mismatch" if "C=" line is not included in the candidates if((candidate = tnet_ice_ctx_get_local_candidate_at(self->ice_ctx, 0))){ // at least one candidate base->M.lo->port = candidate->socket->port; tsdp_header_M_remove(base->M.lo, tsdp_htype_C); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_C_VA_ARGS("IN", TNET_SOCKET_TYPE_IS_IPV6(candidate->socket->type) ? "IP6" : "IP4", candidate->socket->ip), tsk_null); tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("ice-ufrag", candidate->ufrag), TSDP_HEADER_A_VA_ARGS("ice-pwd", candidate->pwd), tsk_null); // RTCWeb tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("mid", self->media_type == tmedia_audio ? "audio" : "video"), tsk_null); while((candidate = tnet_ice_ctx_get_local_candidate_at(self->ice_ctx, index++))){ if(self->use_rtcpmux && remote_use_rtcpmux && candidate->comp_id == TNET_ICE_CANDIDATE_COMPID_RTCP){ continue; // do not add RTCP candidates if RTCP-MUX is activated (local + remote) } tsdp_header_M_add_headers(base->M.lo, TSDP_HEADER_A_VA_ARGS("candidate", tnet_ice_candidate_tostring((tnet_ice_candidate_t*)candidate)), tsk_null); } } } else{ if(base->M.lo->C){ tsk_strupdate(&base->M.lo->C->addr, self->rtp_manager->rtp.public_ip); tsk_strupdate(&base->M.lo->C->addrtype, (self->use_ipv6 ? "IP6" : "IP4")); } base->M.lo->port = self->rtp_manager->rtp.public_port; } if(self->media_type == tmedia_audio){ ///* 3GPP TS 24.229 - 6.1.1 General // The UE shall include the MIME subtype "telephone-event" in the "m=" media descriptor in the SDP for audio media // flows that support both audio codec and DTMF payloads in RTP packets as described in RFC 4733 [23]. //*/ //tsdp_header_M_add_fmt(base->M.lo, TMEDIA_CODEC_FORMAT_DTMF); //tsdp_header_M_add_headers(base->M.lo, // TSDP_HEADER_A_VA_ARGS("fmtp", TMEDIA_CODEC_FORMAT_DTMF" 0-15"), // tsk_null); //tsdp_header_M_add_headers(base->M.lo, // TSDP_HEADER_A_VA_ARGS("rtpmap", TMEDIA_CODEC_FORMAT_DTMF" telephone-event/8000"), // tsk_null); } /* QoS */ if(base->qos){ tmedia_qos_tline_t* ro_tline; if(base->M.ro && (ro_tline = tmedia_qos_tline_from_sdp(base->M.ro))){ tmedia_qos_tline_set_ro(base->qos, ro_tline); TSK_OBJECT_SAFE_FREE(ro_tline); } tmedia_qos_tline_to_sdp(base->qos, base->M.lo); } DONE:; } // updated return base->M.lo; } int tdav_session_av_set_ro(tdav_session_av_t* self, const struct tsdp_header_M_s* m, tsk_bool_t *updated) { tmedia_codecs_L_t* neg_codecs; tsk_bool_t is_srtp_remote_mandatory; tsk_bool_t crypto_matched = tsk_false; tmedia_session_t* base = TMEDIA_SESSION(self); if(!base || !m || !updated){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* update remote offer */ TSK_OBJECT_SAFE_FREE(base->M.ro); base->M.ro = tsk_object_ref((void*)m); *updated = tsk_false; is_srtp_remote_mandatory = (tsk_striequals(m->proto, "RTP/SAVP") || tsk_striequals(m->proto, "RTP/SAVPF")); if(base->M.lo){ if((neg_codecs = tmedia_session_match_codec(base, m))){ /* update negociated codecs */ TSK_OBJECT_SAFE_FREE(base->neg_codecs); base->neg_codecs = neg_codecs; *updated = tsk_true; } else{ TSK_DEBUG_ERROR("None Match"); return -1; } /* QoS */ if(base->qos){ tmedia_qos_tline_t* ro_tline; if(base->M.ro && (ro_tline = tmedia_qos_tline_from_sdp(base->M.ro))){ tmedia_qos_tline_set_ro(base->qos, ro_tline); TSK_OBJECT_SAFE_FREE(ro_tline); } } /* AVPF */ if(tsk_striequals(base->M.lo->proto, "RTP/AVPF") || tsk_striequals(base->M.lo->proto, "RTP/SAVPF")){ self->use_avpf = tsk_true; } } /* get connection associated to this media line * If the connnection is global, then the manager will call tdav_session_audio_set() */ if(m->C && m->C->addr){ tsk_strupdate(&self->remote_ip, m->C->addr); self->use_ipv6 = tsk_striequals(m->C->addrtype, "IP6"); } /* set remote port */ self->remote_port = m->port; /* RTCP-MUX */ self->use_rtcpmux = (tsdp_header_M_findA(m, "rtcp-mux") != tsk_null); /* SDPCapNeg: RFC 5939 */ { const tsdp_header_A_t *A_pcfg, *A_tcap; tsk_size_t i, j; char c_pcfg; int tag_pcfg, tag_pcfg_t; i = 0; TSK_FREE(self->sdp_neg.remote_best_pcfg.t_proto); while((A_pcfg = tsdp_header_M_findA_at(base->M.ro, "pcfg", i++))){ char* v_pcfg = strtok((char*)A_pcfg->value, " "); tag_pcfg = atoi(v_pcfg); if(v_pcfg && (v_pcfg = strtok(tsk_null, " "))){ do{ if(sscanf(v_pcfg, "%c=%d", &c_pcfg, &tag_pcfg_t) >= 2){ j = 0; if(c_pcfg == 't'){ while((A_tcap = tsdp_header_M_findA_at(base->M.ro, "tcap", j++))){ char* v_tcap = strtok((char*)A_tcap->value, " "); if((v_tcap && atoi(v_tcap) == tag_pcfg_t)){ if((v_tcap = strtok(tsk_null, " "))){ // for now only get the best proto self->sdp_neg.remote_best_pcfg.tag = tag_pcfg; self->sdp_neg.remote_best_pcfg.t_tag = tag_pcfg_t; tsk_strupdate(&self->sdp_neg.remote_best_pcfg.t_proto, v_tcap); goto SDPCapNegDone; } } } } } } while((v_pcfg = strtok(tsk_null, " "))); } } SDPCapNegDone:; } /* SRTP */ #if HAVE_SRTP if(self->srtp_mode == tmedia_srtp_mode_optional || self->srtp_mode == tmedia_srtp_mode_mandatory){ tsk_size_t i = 0; const tsdp_header_A_t* A; int ret; while((A = tsdp_header_M_findA_at(m, "crypto", i++))){ if(self->rtp_manager){ if((ret = trtp_srtp_set_remote(self->rtp_manager, A->value)) == 0){ crypto_matched = tsk_true; break; } } else{ if((ret = trtp_srtp_match_line(A->value, &self->remote_srtp_neg.tag, (int32_t*)&self->remote_srtp_neg.crypto_type, self->remote_srtp_neg.key, (sizeof(self->remote_srtp_neg.key) - 1))) == 0){ crypto_matched = tsk_true; self->remote_srtp_neg.pending = tsk_true; break; } } } if((self->srtp_mode == tmedia_srtp_mode_mandatory) && !crypto_matched){// local require but none match TSK_DEBUG_ERROR("SRTP negotiation failed"); return -3; } } self->use_srtp = trtp_srtp_is_initialized(self->rtp_manager); #endif if(is_srtp_remote_mandatory && !crypto_matched){// remote require but none match TSK_DEBUG_ERROR("SRTP negotiation failed"); return -4; } return 0; } const tmedia_codec_t* tdav_session_av_get_best_neg_codec(const tdav_session_av_t* self) { const tsk_list_item_t* item; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_null; } tsk_list_foreach(item, TMEDIA_SESSION(self)->neg_codecs){ // exclude DTMF, RED and ULPFEC if(!TDAV_IS_DTMF_CODEC(item->data) && !TDAV_IS_ULPFEC_CODEC(item->data) && !TDAV_IS_RED_CODEC(item->data) && TMEDIA_CODEC(item->data)->plugin && TMEDIA_CODEC(item->data)->plugin->encode && TMEDIA_CODEC(item->data)->plugin->decode){ return TMEDIA_CODEC(item->data); } } return tsk_null; } const tmedia_codec_t* tdav_session_av_get_red_codec(const tdav_session_av_t* self) { const tsk_list_item_t* item; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_null; } tsk_list_foreach(item, TMEDIA_SESSION(self)->neg_codecs){ if(TDAV_IS_RED_CODEC(item->data)){ return TMEDIA_CODEC(item->data); } } return tsk_null; } const tmedia_codec_t* tdav_session_av_get_ulpfec_codec(const tdav_session_av_t* self) { const tsk_list_item_t* item; if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return tsk_null; } tsk_list_foreach(item, TMEDIA_SESSION(self)->neg_codecs){ if(TDAV_IS_ULPFEC_CODEC(item->data)){ return TMEDIA_CODEC(item->data); } } return tsk_null; } int tdav_session_av_deinit(tdav_session_av_t* self) { if(!self){ TSK_DEBUG_ERROR("Invalid parameter"); return -1; } /* deinit self (rtp manager should be destroyed after the producer) */ TSK_OBJECT_SAFE_FREE(self->consumer); TSK_OBJECT_SAFE_FREE(self->producer); TSK_OBJECT_SAFE_FREE(self->rtp_manager); TSK_FREE(self->sdp_neg.remote_best_pcfg.t_proto); TSK_FREE(self->remote_ip); TSK_FREE(self->local_ip); /* NAT Traversal context */ TSK_OBJECT_SAFE_FREE(self->natt_ctx); TSK_OBJECT_SAFE_FREE(self->ice_ctx); tsk_safeobj_deinit(self); /* deinit base */ tmedia_session_deinit(TMEDIA_SESSION(self)); return 0; }