From 2110281d2e8208453e353895ca7634731450ed62 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 28 May 2014 11:18:35 +0200 Subject: [PATCH] Handle AVPF and SAVPF profiles. --- coreapi/bellesip_sal/sal_op_call.c | 2 +- coreapi/bellesip_sal/sal_sdp.c | 18 +++--- coreapi/callbacks.c | 40 +++++++------ coreapi/linphonecall.c | 90 +++++++++++++----------------- coreapi/linphonecore.c | 2 +- coreapi/misc.c | 7 +++ coreapi/offeranswer.c | 9 +-- coreapi/private.h | 3 + coreapi/sal.c | 35 ++++++++++++ include/sal/sal.h | 6 ++ 10 files changed, 132 insertions(+), 80 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 9a2474b37..2a433b7f6 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -70,7 +70,7 @@ static void sdp_process(SalOp *h){ strcpy(h->result->streams[i].rtcp_addr,h->base.remote_media->streams[i].rtcp_addr); h->result->streams[i].rtcp_port=h->base.remote_media->streams[i].rtcp_port; - if (h->result->streams[i].proto == SalProtoRtpSavp) { + if ((h->result->streams[i].proto == SalProtoRtpSavpf) || (h->result->streams[i].proto == SalProtoRtpSavp)) { h->result->streams[i].crypto[0] = h->base.remote_media->streams[i].crypto[0]; } } diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 23d0d8be4..0cf94bed3 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -143,7 +143,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if ( stream->bandwidth>0 ) belle_sdp_media_description_set_bandwidth ( media_desc,"AS",stream->bandwidth ); - if ( stream->proto == SalProtoRtpSavp ) { + if ((stream->proto == SalProtoRtpSavpf) || (stream->proto == SalProtoRtpSavp)) { /* add crypto lines */ for ( j=0; jproto=SalProtoOther; if ( proto ) { - if ( strcasecmp ( proto,"RTP/AVP" ) ==0 ) - stream->proto=SalProtoRtpAvp; - else if ( strcasecmp ( proto,"RTP/SAVP" ) ==0 ) { - stream->proto=SalProtoRtpSavp; - }else{ + if (strcasecmp(proto, "RTP/AVP") == 0) { + stream->proto = SalProtoRtpAvp; + } else if (strcasecmp(proto, "RTP/SAVP") == 0) { + stream->proto = SalProtoRtpSavp; + } else if (strcasecmp(proto, "RTP/AVPF") == 0) { + stream->proto = SalProtoRtpAvpf; + } else if (strcasecmp(proto, "RTP/SAVPF") == 0) { + stream->proto = SalProtoRtpSavpf; + } else { strncpy(stream->proto_other,proto,sizeof(stream->proto_other)-1); } } @@ -532,7 +536,7 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, } /* Read crypto lines if any */ - if ( stream->proto == SalProtoRtpSavp ) { + if ((stream->proto == SalProtoRtpSavpf) || (stream->proto == SalProtoRtpSavp)) { sdp_parse_media_crypto_parameters(media_desc, stream); } diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index cc981d9a6..64f17f411 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -670,24 +670,32 @@ static void call_failure(SalOp *op){ break; case SalReasonUnsupportedContent: /*params.media_encryption == LinphoneMediaEncryptionSRTP && - !linphone_core_is_media_encryption_mandatory(lc)) { + ms_message("Outgoing call [%p] failed with SRTP and/or AVPF enabled", call); + if ((call->state == LinphoneCallOutgoingInit) + || (call->state == LinphoneCallOutgoingProgress) + || (call->state == LinphoneCallOutgoingRinging) /* Push notification case */ + || (call->state == LinphoneCallOutgoingEarlyMedia)) { int i; - ms_message("Outgoing call [%p] failed with SRTP (SAVP) enabled",call); - if (call->state==LinphoneCallOutgoingInit - || call->state==LinphoneCallOutgoingProgress - || call->state==LinphoneCallOutgoingRinging /*push case*/ - || call->state==LinphoneCallOutgoingEarlyMedia){ - ms_message("Retrying call [%p] with AVP",call); - /* clear SRTP local params */ - call->params.media_encryption = LinphoneMediaEncryptionNone; - for(i=0; ilocaldesc->n_active_streams; i++) { - call->localdesc->streams[i].proto = SalProtoRtpAvp; - memset(call->localdesc->streams[i].crypto, 0, sizeof(call->localdesc->streams[i].crypto)); + for (i = 0; i < call->localdesc->n_active_streams; i++) { + if (call->params.media_encryption == LinphoneMediaEncryptionSRTP) { + if (call->params.avpf_enabled == TRUE) { + if (i == 0) ms_message("Retrying call [%p] with SAVP", call); + call->params.avpf_enabled = FALSE; + linphone_core_restart_invite(lc, call); + return; + } else if (!linphone_core_is_media_encryption_mandatory(lc)) { + if (i == 0) ms_message("Retrying call [%p] with AVP", call); + call->params.media_encryption = LinphoneMediaEncryptionNone; + memset(call->localdesc->streams[i].crypto, 0, sizeof(call->localdesc->streams[i].crypto)); + linphone_core_restart_invite(lc, call); + return; + } + } else if (call->params.avpf_enabled == TRUE) { + if (i == 0) ms_message("Retrying call [%p] with AVP", call); + call->params.avpf_enabled = FALSE; + linphone_core_restart_invite(lc, call); + return; } - linphone_core_restart_invite(lc, call); - return; } } msg=_("Incompatible media parameters."); diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 3f473ce53..1a962ef3e 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -265,8 +265,8 @@ static void setup_encryption_keys(LinphoneCall *call, SalMediaDescription *md){ bool_t keep_srtp_keys=lp_config_get_int(lc->config,"sip","keep_srtp_keys",1); for(i=0; in_active_streams; i++) { - if (md->streams[i].proto == SalProtoRtpSavp) { - if (keep_srtp_keys && old_md && old_md->streams[i].proto==SalProtoRtpSavp){ + if (is_encryption_active(&md->streams[i]) == TRUE) { + if (keep_srtp_keys && old_md && is_encryption_active(&old_md->streams[i]) == TRUE){ int j; ms_message("Keeping same crypto keys."); for(j=0;jsession_ver++; } +static SalMediaProto get_proto_from_call_params(LinphoneCallParams *params) { + if ((params->media_encryption == LinphoneMediaEncryptionSRTP) && params->avpf_enabled) return SalProtoRtpSavpf; + if (params->media_encryption == LinphoneMediaEncryptionSRTP) return SalProtoRtpSavp; + if (params->avpf_enabled) return SalProtoRtpAvpf; + return SalProtoRtpAvp; +} + void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall *call){ MSList *l; PayloadType *pt; @@ -349,8 +356,7 @@ void linphone_call_make_local_media_description(LinphoneCore *lc, LinphoneCall * strncpy(md->streams[0].name,"Audio",sizeof(md->streams[0].name)-1); md->streams[0].rtp_port=call->media_ports[0].rtp_port; md->streams[0].rtcp_port=call->media_ports[0].rtcp_port; - md->streams[0].proto=(call->params.media_encryption == LinphoneMediaEncryptionSRTP) ? - SalProtoRtpSavp : SalProtoRtpAvp; + md->streams[0].proto=get_proto_from_call_params(&call->params); md->streams[0].type=SalAudio; if (call->params.down_ptime) md->streams[0].ptime=call->params.down_ptime; @@ -961,10 +967,6 @@ const LinphoneCallParams * linphone_call_get_current_params(LinphoneCall *call){ return &call->current_params; } -static bool_t is_video_active(const SalStreamDescription *sd){ - return sd->rtp_port!=0 && sd->dir!=SalStreamInactive; -} - /** * Returns call parameters proposed by remote. * @@ -976,19 +978,20 @@ const LinphoneCallParams * linphone_call_get_remote_params(LinphoneCall *call){ memset(cp,0,sizeof(*cp)); if (call->op){ SalMediaDescription *md=sal_call_get_remote_media_description(call->op); - if (md){ - SalStreamDescription *asd,*vsd,*secure_asd,*secure_vsd; + if (md) { + SalStreamDescription *sd; + unsigned int i; + unsigned int nb_audio_streams = sal_media_description_nb_active_streams_of_type(md, SalAudio); + unsigned int nb_video_streams = sal_media_description_nb_active_streams_of_type(md, SalVideo); - asd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalAudio); - vsd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalVideo); - secure_asd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalAudio); - secure_vsd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalVideo); - if (secure_vsd){ - cp->has_video=is_video_active(secure_vsd); - if (secure_asd || asd==NULL) - cp->media_encryption=LinphoneMediaEncryptionSRTP; - }else if (vsd){ - cp->has_video=is_video_active(vsd); + for (i = 0; i < nb_video_streams; i++) { + sd = sal_media_description_get_active_stream_of_type(md, SalVideo, i); + if (is_video_active(sd) == TRUE) cp->has_video = TRUE; + if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; + } + for (i = 0; i < nb_audio_streams; i++) { + sd = sal_media_description_get_active_stream_of_type(md, SalAudio, i); + if (is_encryption_active(sd) == TRUE) cp->media_encryption = LinphoneMediaEncryptionSRTP; } if (!cp->has_video){ if (md->bandwidth>0 && md->bandwidth<=linphone_core_get_edge_bw(call->core)){ @@ -1861,12 +1864,10 @@ static void configure_rtp_session_for_rtcp_xr(LinphoneCore *lc, LinphoneCall *ca const SalStreamDescription *localstream; const SalStreamDescription *remotestream; - localstream = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, type); - if (!localstream) localstream = sal_media_description_find_stream(call->localdesc, SalProtoRtpAvp, type); + localstream = sal_media_description_find_best_stream(call->localdesc, type); if (!localstream) return; localconfig = &localstream->rtcp_xr; - remotestream = sal_media_description_find_stream(sal_call_get_remote_media_description(call->op), SalProtoRtpSavp, type); - if (!remotestream) remotestream = sal_media_description_find_stream(sal_call_get_remote_media_description(call->op), SalProtoRtpAvp, type); + remotestream = sal_media_description_find_best_stream(sal_call_get_remote_media_description(call->op), type); if (!remotestream) return; remoteconfig = &remotestream->rtcp_xr; @@ -1902,14 +1903,8 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna int crypto_idx; snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); - /* look for savp stream first */ - stream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpSavp,SalAudio); - /* no savp audio stream, use avp */ - if (!stream) - stream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalAudio); + stream = sal_media_description_find_best_stream(call->resultdesc, SalAudio); if (stream && stream->dir!=SalStreamInactive && stream->rtp_port!=0){ playcard=lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; @@ -1965,8 +1960,8 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna call->current_params.record_file=ms_strdup(call->params.record_file); } /* valid local tags are > 0 */ - if (stream->proto == SalProtoRtpSavp) { - local_st_desc=sal_media_description_find_stream(call->localdesc,SalProtoRtpSavp,SalAudio); + if (is_encryption_active(stream) == TRUE) { + local_st_desc=sal_media_description_find_stream(call->localdesc,stream->proto,SalAudio); crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, stream->crypto_local_tag); if (crypto_idx >= 0) { @@ -2020,17 +2015,10 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna #ifdef VIDEO_ENABLED LinphoneCore *lc=call->core; int used_pt=-1; - - /* look for savp stream first */ - const SalStreamDescription *vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpSavp,SalVideo); char rtcp_tool[128]={0}; - snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); + const SalStreamDescription *vstream; - /* no savp audio stream, use avp */ - if (!vstream) - vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalVideo); + snprintf(rtcp_tool,sizeof(rtcp_tool)-1,"%s-%s",linphone_core_get_user_agent_name(),linphone_core_get_user_agent_version()); /* shutdown preview */ if (lc->previewstream!=NULL) { @@ -2038,6 +2026,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna lc->previewstream=NULL; } + vstream = sal_media_description_find_best_stream(call->resultdesc, SalVideo); if (vstream!=NULL && vstream->dir!=SalStreamInactive && vstream->rtp_port!=0) { const char *rtp_addr=vstream->rtp_addr[0]!='\0' ? vstream->rtp_addr : call->resultdesc->addr; const char *rtcp_addr=vstream->rtcp_addr[0]!='\0' ? vstream->rtcp_addr : call->resultdesc->addr; @@ -2088,7 +2077,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna cam=get_nowebcam_device(); } if (!is_inactive){ - if (vstream->proto == SalProtoRtpSavp) { + if (is_encryption_active(vstream) == TRUE) { int crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, vstream->crypto_local_tag); if (crypto_idx >= 0) { media_stream_set_srtp_recv_key(&call->videostream->ms,vstream->crypto[0].algo,vstream->crypto[0].master_key); @@ -2121,8 +2110,7 @@ void linphone_call_start_media_streams(LinphoneCall *call, bool_t all_inputs_mut char *cname; bool_t use_arc=linphone_core_adaptive_rate_control_enabled(lc); #ifdef VIDEO_ENABLED - const SalStreamDescription *vstream=sal_media_description_find_stream(call->resultdesc, - SalProtoRtpAvp,SalVideo); + const SalStreamDescription *vstream=sal_media_description_find_best_stream(call->resultdesc,SalVideo); #endif call->current_params.audio_codec = NULL; @@ -2209,17 +2197,17 @@ void linphone_call_update_crypto_parameters(LinphoneCall *call, SalMediaDescript SalStreamDescription *new_stream; const SalStreamDescription *local_st_desc; - local_st_desc = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, SalAudio); - old_stream = sal_media_description_find_stream(old_md, SalProtoRtpSavp, SalAudio); - new_stream = sal_media_description_find_stream(new_md, SalProtoRtpSavp, SalAudio); + local_st_desc = sal_media_description_find_secure_stream_of_type(call->localdesc, SalAudio); + old_stream = sal_media_description_find_secure_stream_of_type(old_md, SalAudio); + new_stream = sal_media_description_find_secure_stream_of_type(new_md, SalAudio); if (call->audiostream && local_st_desc && old_stream && new_stream && update_stream_crypto_params(call,local_st_desc,old_stream,new_stream,&call->audiostream->ms)){ } #ifdef VIDEO_ENABLED - local_st_desc = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, SalVideo); - old_stream = sal_media_description_find_stream(old_md, SalProtoRtpSavp, SalVideo); - new_stream = sal_media_description_find_stream(new_md, SalProtoRtpSavp, SalVideo); + local_st_desc = sal_media_description_find_secure_stream_of_type(call->localdesc, SalVideo); + old_stream = sal_media_description_find_secure_stream_of_type(old_md, SalVideo); + new_stream = sal_media_description_find_secure_stream_of_type(new_md, SalVideo); if (call->videostream && local_st_desc && old_stream && new_stream && update_stream_crypto_params(call,local_st_desc,old_stream,new_stream,&call->videostream->ms)){ } diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 7cbb5b955..15f7945cf 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2972,7 +2972,7 @@ bool_t linphone_core_media_description_has_srtp(const SalMediaDescription *md){ for(i=0;in_active_streams;i++){ const SalStreamDescription *sd=&md->streams[i]; - if (sd->proto!=SalProtoRtpSavp){ + if (is_encryption_active(sd) != TRUE){ return FALSE; } } diff --git a/coreapi/misc.c b/coreapi/misc.c index c27147107..f8fae4210 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -1520,3 +1520,10 @@ const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){ return result; } +bool_t is_video_active(const SalStreamDescription *sd) { + return (sd->rtp_port != 0) && (sd->dir != SalStreamInactive); +} + +bool_t is_encryption_active(const SalStreamDescription *sd) { + return ((sd->proto == SalProtoRtpSavpf) || (sd->proto == SalProtoRtpSavp)); +} diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index fd99f421c..be8998168 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -233,7 +233,7 @@ static void initiate_outgoing(const SalStreamDescription *local_offer, }else{ result->rtp_port=0; } - if (result->proto == SalProtoRtpSavp) { + if (is_encryption_active(result) == TRUE) { /* verify crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_offer->crypto, remote_answer->crypto, &result->crypto[0], &result->crypto_local_tag, FALSE)) @@ -259,7 +259,7 @@ static void initiate_incoming(const SalStreamDescription *local_cap, }else{ result->rtp_port=0; } - if (result->proto == SalProtoRtpSavp) { + if (is_encryption_active(result) == TRUE) { /* select crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_cap->crypto, remote_offer->crypto, &result->crypto[0], &result->crypto_local_tag, TRUE)) @@ -323,10 +323,11 @@ static bool_t local_stream_not_already_used(const SalMediaDescription *result, c return TRUE; } -/*in answering mode, we consider that if we are able to make SAVP, then we can do AVP as well*/ +/*in answering mode, we consider that if we are able to make AVPF/SAVP/SAVPF, then we can do AVP as well*/ static bool_t proto_compatible(SalMediaProto local, SalMediaProto remote){ if (local==remote) return TRUE; - if (remote==SalProtoRtpAvp && local==SalProtoRtpSavp) return TRUE; + if ((remote==SalProtoRtpAvp) && ((local==SalProtoRtpSavp) || (local==SalProtoRtpAvpf) || (local==SalProtoRtpSavpf))) + return TRUE; return FALSE; } diff --git a/coreapi/private.h b/coreapi/private.h index 5ebccdf35..dc9a02e80 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -95,6 +95,7 @@ struct _LinphoneCallParams{ char *session_name; SalCustomHeader *custom_headers; bool_t has_video; + bool_t avpf_enabled; /* RTCP feedback messages are enabled */ bool_t real_early_media; /*send real media even during early media (for outgoing calls)*/ bool_t in_conference; /*in conference mode */ bool_t low_bandwidth; @@ -389,6 +390,8 @@ bool_t linphone_core_rtcp_enabled(const LinphoneCore *lc); LinphoneCall * is_a_linphone_call(void *user_pointer); LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer); +bool_t is_video_active(const SalStreamDescription *sd); +bool_t is_encryption_active(const SalStreamDescription *sd); void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description); diff --git a/coreapi/sal.c b/coreapi/sal.c index cb94d675c..74d087139 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -91,6 +91,39 @@ SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md, return NULL; } +unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription *md, SalStreamType type) { + unsigned int i; + unsigned int nb = 0; + for (i = 0; i < md->n_active_streams; ++i) { + if (md->streams[i].type == type) nb++; + } + return nb; +} + +SalStreamDescription * sal_media_description_get_active_stream_of_type(SalMediaDescription *md, SalStreamType type, unsigned int idx) { + unsigned int i; + for (i = 0; i < md->n_active_streams; ++i) { + if (md->streams[i].type == type) { + if (idx-- == 0) return &md->streams[i]; + } + } + return NULL; +} + +SalStreamDescription * sal_media_description_find_secure_stream_of_type(SalMediaDescription *md, SalStreamType type) { + SalStreamDescription *desc = sal_media_description_find_stream(md, SalProtoRtpSavpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpSavp, type); + return desc; +} + +SalStreamDescription * sal_media_description_find_best_stream(SalMediaDescription *md, SalStreamType type) { + SalStreamDescription *desc = sal_media_description_find_stream(md, SalProtoRtpSavpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpSavp, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpAvpf, type); + if (desc == NULL) desc = sal_media_description_find_stream(md, SalProtoRtpAvp, type); + return desc; +} + bool_t sal_media_description_empty(const SalMediaDescription *md){ if (md->n_active_streams > 0) return FALSE; return TRUE; @@ -515,6 +548,8 @@ const char* sal_media_proto_to_string(SalMediaProto type) { switch (type) { case SalProtoRtpAvp:return "RTP/AVP"; case SalProtoRtpSavp:return "RTP/SAVP"; + case SalProtoRtpAvpf:return "RTP/AVPF"; + case SalProtoRtpSavpf:return "RTP/SAVPF"; default: return "unknown"; } } diff --git a/include/sal/sal.h b/include/sal/sal.h index fbeaf2e14..d5712c09d 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -123,6 +123,8 @@ const char* sal_stream_type_to_string(SalStreamType type); typedef enum{ SalProtoRtpAvp, SalProtoRtpSavp, + SalProtoRtpAvpf, + SalProtoRtpSavpf, SalProtoOther }SalMediaProto; const char* sal_media_proto_to_string(SalMediaProto type); @@ -251,6 +253,10 @@ int sal_media_description_equals(const SalMediaDescription *md1, const SalMediaD bool_t sal_media_description_has_dir(const SalMediaDescription *md, SalStreamDir dir); SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md, SalMediaProto proto, SalStreamType type); +unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription *md, SalStreamType type); +SalStreamDescription * sal_media_description_get_active_stream_of_type(SalMediaDescription *md, SalStreamType type, unsigned int idx); +SalStreamDescription * sal_media_description_find_secure_stream_of_type(SalMediaDescription *md, SalStreamType type); +SalStreamDescription * sal_media_description_find_best_stream(SalMediaDescription *md, SalStreamType type); void sal_media_description_set_dir(SalMediaDescription *md, SalStreamDir stream_dir);