807 lines
27 KiB
C
807 lines
27 KiB
C
/*
|
|
* Copyright (C) 2012 Doubango Telecom <http://www.doubango.org>
|
|
*
|
|
* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]org>
|
|
*
|
|
* 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 <diopmamadou(at)doubango[dot]org>
|
|
*/
|
|
#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;
|
|
} |