From 1c378ed83b5b32a00368bbfc3ce2ee7687691abe Mon Sep 17 00:00:00 2001 From: zhihuang Date: Thu, 17 Aug 2017 14:10:50 -0700 Subject: [PATCH] Relanding: Adding support for Unified Plan offer/answer negotiation to the mediasession layer. This layer takes in a simplified "options" struct and the current local description, and generates a new offer/answer. Previously the options struct assumed there would only be one media description per media type (audio/video), but it now supports N number of audio/video descriptions. The |add_legacy_stream| options is removed from the mediasession.cc/.h in this CL. The next step is to add the ability for PeerConnection/WebRtcSession to create "options" to represent multiple RtpTransceivers, and apply the Unified Plan descriptions correctly. Right now, only Plan B descriptions will be generated in unit tests. BUG=chromium:465349 Review-Url: https://codereview.webrtc.org/2991693002 Cr-Original-Commit-Position: refs/heads/master@{#19343} Committed: https://chromium.googlesource.com/external/webrtc/+/a77e6bbd30276bdc5b30f2cbc1e92ca181ae76f0 Review-Url: https://codereview.webrtc.org/2991693002 Cr-Commit-Position: refs/heads/master@{#19394} --- webrtc/pc/mediasession.cc | 1332 ++++++++++------- webrtc/pc/mediasession.h | 222 +-- webrtc/pc/mediasession_unittest.cc | 1161 +++++++++----- webrtc/pc/peerconnection.cc | 545 ++++--- webrtc/pc/peerconnection.h | 49 +- webrtc/pc/peerconnectioninterface_unittest.cc | 443 +++--- webrtc/pc/webrtcsession_unittest.cc | 541 +++++-- webrtc/pc/webrtcsessiondescriptionfactory.cc | 90 +- 8 files changed, 2727 insertions(+), 1656 deletions(-) diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc index de8f72c926..4d4744aeb0 100644 --- a/webrtc/pc/mediasession.cc +++ b/webrtc/pc/mediasession.cc @@ -188,11 +188,14 @@ bool CreateMediaCryptos(const std::vector& crypto_suites, return true; } -const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) { - if (!media) { - return NULL; +const CryptoParamsVec* GetCryptos(const ContentInfo* content) { + if (!content) { + return nullptr; } - return &media->cryptos(); + + RTC_DCHECK(IsMediaContent(content)); + return &(static_cast(content->description) + ->cryptos()); } bool FindMatchingCrypto(const CryptoParamsVec& cryptos, @@ -428,15 +431,15 @@ class UsedRtpHeaderExtensionIds : public UsedIds { private: }; -// Adds a StreamParams for each Stream in Streams with media type -// media_type to content_description. +// Adds a StreamParams for each SenderOptions in |sender_options| to +// content_description. // |current_params| - All currently known StreamParams of any media type. template -static bool AddStreamParams(MediaType media_type, - const MediaSessionOptions& options, - StreamParamsVec* current_streams, - MediaContentDescriptionImpl* content_description, - const bool add_legacy_stream) { +static bool AddStreamParams( + const std::vector& sender_options, + const std::string& rtcp_cname, + StreamParamsVec* current_streams, + MediaContentDescriptionImpl* content_description) { // SCTP streams are not negotiated using SDP/ContentDescriptions. if (IsSctp(content_description->protocol())) { return true; @@ -445,44 +448,26 @@ static bool AddStreamParams(MediaType media_type, const bool include_rtx_streams = ContainsRtxCodec(content_description->codecs()); - const MediaSessionOptions::Streams& streams = options.streams; - if (streams.empty() && add_legacy_stream) { - // TODO(perkj): Remove this legacy stream when all apps use StreamParams. - std::vector ssrcs; - int num_ssrcs = include_rtx_streams ? 2 : 1; - GenerateSsrcs(*current_streams, num_ssrcs, &ssrcs); - if (include_rtx_streams) { - content_description->AddLegacyStream(ssrcs[0], ssrcs[1]); - content_description->set_multistream(true); - } else { - content_description->AddLegacyStream(ssrcs[0]); - } - return true; - } const bool include_flexfec_stream = ContainsFlexfecCodec(content_description->codecs()); - MediaSessionOptions::Streams::const_iterator stream_it; - for (stream_it = streams.begin(); - stream_it != streams.end(); ++stream_it) { - if (stream_it->type != media_type) - continue; // Wrong media type. - - StreamParams* param = GetStreamByIds(*current_streams, "", stream_it->id); + for (const SenderOptions& sender : sender_options) { // groupid is empty for StreamParams generated using // MediaSessionDescriptionFactory. + StreamParams* param = + GetStreamByIds(*current_streams, "" /*group_id*/, sender.track_id); if (!param) { - // This is a new stream. + // This is a new sender. std::vector ssrcs; - GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs); + GenerateSsrcs(*current_streams, sender.num_sim_layers, &ssrcs); StreamParams stream_param; - stream_param.id = stream_it->id; + stream_param.id = sender.track_id; // Add the generated ssrc. for (size_t i = 0; i < ssrcs.size(); ++i) { stream_param.ssrcs.push_back(ssrcs[i]); } - if (stream_it->num_sim_layers > 1) { + if (sender.num_sim_layers > 1) { SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs); stream_param.ssrc_groups.push_back(group); } @@ -512,8 +497,8 @@ static bool AddStreamParams(MediaType media_type, << "media streams however, so no FlexFEC SSRC will be generated."; } } - stream_param.cname = options.rtcp_cname; - stream_param.sync_label = stream_it->sync_label; + stream_param.cname = rtcp_cname; + stream_param.sync_label = sender.stream_id; content_description->AddStream(stream_param); // Store the new StreamParams in current_streams. @@ -523,7 +508,7 @@ static bool AddStreamParams(MediaType media_type, // Use existing generated SSRCs/groups, but update the sync_label if // necessary. This may be needed if a MediaStreamTrack was moved from one // MediaStream to another. - param->sync_label = stream_it->sync_label; + param->sync_label = sender.stream_id; content_description->AddStream(*param); } } @@ -735,46 +720,34 @@ static bool IsFlexfecCodec(const C& codec) { return STR_CASE_CMP(codec.name.c_str(), kFlexfecCodecName) == 0; } -static TransportOptions GetTransportOptions(const MediaSessionOptions& options, - const std::string& content_name) { - TransportOptions transport_options; - auto it = options.transport_options.find(content_name); - if (it != options.transport_options.end()) { - transport_options = it->second; - } - transport_options.enable_ice_renomination = options.enable_ice_renomination; - return transport_options; -} - -// Create a media content to be offered in a session-initiate, -// according to the given options.rtcp_mux, options.is_muc, -// options.streams, codecs, secure_transport, crypto, and streams. If we don't -// currently have crypto (in current_cryptos) and it is enabled (in -// secure_policy), crypto is created (according to crypto_suites). If -// add_legacy_stream is true, and current_streams is empty, a legacy -// stream is created. The created content is added to the offer. +// Create a media content to be offered for the given |sender_options|, +// according to the given options.rtcp_mux, session_options.is_muc, codecs, +// secure_transport, crypto, and current_streams. If we don't currently have +// crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is +// created (according to crypto_suites). The created content is added to the +// offer. template static bool CreateMediaContentOffer( - const MediaSessionOptions& options, + const std::vector& sender_options, + const MediaSessionOptions& session_options, const std::vector& codecs, const SecurePolicy& secure_policy, const CryptoParamsVec* current_cryptos, const std::vector& crypto_suites, const RtpHeaderExtensions& rtp_extensions, - bool add_legacy_stream, StreamParamsVec* current_streams, MediaContentDescriptionImpl* offer) { offer->AddCodecs(codecs); - offer->set_rtcp_mux(options.rtcp_mux_enabled); + offer->set_rtcp_mux(session_options.rtcp_mux_enabled); if (offer->type() == cricket::MEDIA_TYPE_VIDEO) { offer->set_rtcp_reduced_size(true); } - offer->set_multistream(options.is_muc); + offer->set_multistream(session_options.is_muc); offer->set_rtp_header_extensions(rtp_extensions); - if (!AddStreamParams(offer->type(), options, current_streams, offer, - add_legacy_stream)) { + if (!AddStreamParams(sender_options, session_options.rtcp_cname, + current_streams, offer)) { return false; } @@ -882,15 +855,42 @@ static bool FindMatchingCodec(const std::vector& codecs1, return false; } -// Adds all codecs from |reference_codecs| to |offered_codecs| that dont' +// Find the codec in |codec_list| that |rtx_codec| is associated with. +template +static const C* GetAssociatedCodec(const std::vector& codec_list, + const C& rtx_codec) { + std::string associated_pt_str; + if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_pt_str)) { + LOG(LS_WARNING) << "RTX codec " << rtx_codec.name + << " is missing an associated payload type."; + return nullptr; + } + + int associated_pt; + if (!rtc::FromString(associated_pt_str, &associated_pt)) { + LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str + << " of RTX codec " << rtx_codec.name << " to an integer."; + return nullptr; + } + + // Find the associated reference codec for the reference RTX codec. + const C* associated_codec = FindCodecById(codec_list, associated_pt); + if (!associated_codec) { + LOG(LS_WARNING) << "Couldn't find associated codec with payload type " + << associated_pt << " for RTX codec " << rtx_codec.name + << "."; + } + return associated_codec; +} + +// Adds all codecs from |reference_codecs| to |offered_codecs| that don't // already exist in |offered_codecs| and ensure the payload types don't // collide. template -static void FindCodecsToOffer( - const std::vector& reference_codecs, - std::vector* offered_codecs, - UsedPayloadTypes* used_pltypes) { - +static void MergeCodecs(const std::vector& reference_codecs, + std::vector* offered_codecs, + UsedPayloadTypes* used_pltypes) { // Add all new codecs that are not RTX codecs. for (const C& reference_codec : reference_codecs) { if (!IsRtxCodec(reference_codec) && @@ -908,33 +908,11 @@ static void FindCodecsToOffer( !FindMatchingCodec(reference_codecs, *offered_codecs, reference_codec, nullptr)) { C rtx_codec = reference_codec; - - std::string associated_pt_str; - if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType, - &associated_pt_str)) { - LOG(LS_WARNING) << "RTX codec " << rtx_codec.name - << " is missing an associated payload type."; - continue; - } - - int associated_pt; - if (!rtc::FromString(associated_pt_str, &associated_pt)) { - LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str - << " of RTX codec " << rtx_codec.name - << " to an integer."; - continue; - } - - // Find the associated reference codec for the reference RTX codec. const C* associated_codec = - FindCodecById(reference_codecs, associated_pt); + GetAssociatedCodec(reference_codecs, rtx_codec); if (!associated_codec) { - LOG(LS_WARNING) << "Couldn't find associated codec with payload type " - << associated_pt << " for RTX codec " << rtx_codec.name - << "."; continue; } - // Find a codec in the offered list that matches the reference codec. // Its payload type may be different than the reference codec. C matching_codec; @@ -953,6 +931,22 @@ static void FindCodecsToOffer( } } +static bool FindByUriAndEncryption(const RtpHeaderExtensions& extensions, + const webrtc::RtpExtension& ext_to_match, + webrtc::RtpExtension* found_extension) { + for (RtpHeaderExtensions::const_iterator it = extensions.begin(); + it != extensions.end(); ++it) { + // We assume that all URIs are given in a canonical format. + if (it->uri == ext_to_match.uri && it->encrypt == ext_to_match.encrypt) { + if (found_extension) { + *found_extension = *it; + } + return true; + } + } + return false; +} + static bool FindByUri(const RtpHeaderExtensions& extensions, const webrtc::RtpExtension& ext_to_match, webrtc::RtpExtension* found_extension) { @@ -996,50 +990,41 @@ static bool FindByUriWithEncryptionPreference( return false; } -// Iterates through |offered_extensions|, adding each one to -// |regular_extensions| (or |encrypted_extensions| if encrypted) and |used_ids|, -// and resolving ID conflicts. -// If an offered extension has the same URI as one in |regular_extensions| or -// |encrypted_extensions|, it will re-use the same ID and won't be treated as -// a conflict. -static void FindAndSetRtpHdrExtUsed(RtpHeaderExtensions* offered_extensions, - RtpHeaderExtensions* regular_extensions, - RtpHeaderExtensions* encrypted_extensions, - UsedRtpHeaderExtensionIds* used_ids) { - for (auto& extension : *offered_extensions) { - webrtc::RtpExtension existing; - if ((extension.encrypt && - FindByUri(*encrypted_extensions, extension, &existing)) || - (!extension.encrypt && - FindByUri(*regular_extensions, extension, &existing))) { - extension.id = existing.id; - } else { - used_ids->FindAndSetIdUsed(&extension); - if (extension.encrypt) { - encrypted_extensions->push_back(extension); - } else { - regular_extensions->push_back(extension); - } - } - } -} - -// Adds |reference_extensions| to |offered_extensions|, while updating -// |all_extensions| and |used_ids|. -static void FindRtpHdrExtsToOffer( - const RtpHeaderExtensions& reference_extensions, - RtpHeaderExtensions* offered_extensions, - RtpHeaderExtensions* all_extensions, - UsedRtpHeaderExtensionIds* used_ids) { +// Adds all extensions from |reference_extensions| to |offered_extensions| that +// don't already exist in |offered_extensions| and ensure the IDs don't +// collide. If an extension is added, it's also added to |regular_extensions| or +// |encrypted_extensions|, and if the extension is in |regular_extensions| or +// |encrypted_extensions|, its ID is marked as used in |used_ids|. +// |offered_extensions| is for either audio or video while |regular_extensions| +// and |encrypted_extensions| are used for both audio and video. There could be +// overlap between audio extensions and video extensions. +static void MergeRtpHdrExts(const RtpHeaderExtensions& reference_extensions, + RtpHeaderExtensions* offered_extensions, + RtpHeaderExtensions* regular_extensions, + RtpHeaderExtensions* encrypted_extensions, + UsedRtpHeaderExtensionIds* used_ids) { for (auto reference_extension : reference_extensions) { - if (!FindByUri(*offered_extensions, reference_extension, NULL)) { + if (!FindByUriAndEncryption(*offered_extensions, reference_extension, + nullptr)) { webrtc::RtpExtension existing; - if (FindByUri(*all_extensions, reference_extension, &existing)) { - offered_extensions->push_back(existing); + if (reference_extension.encrypt) { + if (FindByUriAndEncryption(*encrypted_extensions, reference_extension, + &existing)) { + offered_extensions->push_back(existing); + } else { + used_ids->FindAndSetIdUsed(&reference_extension); + encrypted_extensions->push_back(reference_extension); + offered_extensions->push_back(reference_extension); + } } else { - used_ids->FindAndSetIdUsed(&reference_extension); - all_extensions->push_back(reference_extension); - offered_extensions->push_back(reference_extension); + if (FindByUriAndEncryption(*regular_extensions, reference_extension, + &existing)) { + offered_extensions->push_back(existing); + } else { + used_ids->FindAndSetIdUsed(&reference_extension); + regular_extensions->push_back(reference_extension); + offered_extensions->push_back(reference_extension); + } } } } @@ -1103,26 +1088,24 @@ static void StripCNCodecs(AudioCodecs* audio_codecs) { } } -// Create a media content to be answered in a session-accept, -// according to the given options.rtcp_mux, options.streams, codecs, -// crypto, and streams. If we don't currently have crypto (in -// current_cryptos) and it is enabled (in secure_policy), crypto is -// created (according to crypto_suites). If add_legacy_stream is -// true, and current_streams is empty, a legacy stream is created. -// The codecs, rtcp_mux, and crypto are all negotiated with the offer -// from the incoming session-initiate. If the negotiation fails, this -// method returns false. The created content is added to the offer. +// Create a media content to be answered for the given |sender_options| +// according to the given session_options.rtcp_mux, session_options.streams, +// codecs, crypto, and current_streams. If we don't currently have crypto (in +// current_cryptos) and it is enabled (in secure_policy), crypto is created +// (according to crypto_suites). The codecs, rtcp_mux, and crypto are all +// negotiated with the offer. If the negotiation fails, this method returns +// false. The created content is added to the offer. template static bool CreateMediaContentAnswer( const MediaContentDescriptionImpl* offer, - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, const std::vector& local_codecs, const SecurePolicy& sdes_policy, const CryptoParamsVec* current_cryptos, const RtpHeaderExtensions& local_rtp_extenstions, bool enable_encrypted_rtp_header_extensions, StreamParamsVec* current_streams, - bool add_legacy_stream, bool bundle_enabled, MediaContentDescriptionImpl* answer) { std::vector negotiated_codecs; @@ -1136,14 +1119,15 @@ static bool CreateMediaContentAnswer( &negotiated_rtp_extensions); answer->set_rtp_header_extensions(negotiated_rtp_extensions); - answer->set_rtcp_mux(options.rtcp_mux_enabled && offer->rtcp_mux()); + answer->set_rtcp_mux(session_options.rtcp_mux_enabled && offer->rtcp_mux()); if (answer->type() == cricket::MEDIA_TYPE_VIDEO) { answer->set_rtcp_reduced_size(offer->rtcp_reduced_size()); } if (sdes_policy != SEC_DISABLED) { CryptoParams crypto; - if (SelectCrypto(offer, bundle_enabled, options.crypto_options, &crypto)) { + if (SelectCrypto(offer, bundle_enabled, session_options.crypto_options, + &crypto)) { if (current_cryptos) { FindMatchingCrypto(*current_cryptos, crypto, &crypto); } @@ -1155,29 +1139,17 @@ static bool CreateMediaContentAnswer( return false; } - if (!AddStreamParams(answer->type(), options, current_streams, answer, - add_legacy_stream)) { + if (!AddStreamParams(media_description_options.sender_options, + session_options.rtcp_cname, current_streams, answer)) { return false; // Something went seriously wrong. } - // Make sure the answer media content direction is per default set as - // described in RFC3264 section 6.1. - const bool is_data = !IsRtpProtocol(answer->protocol()); - const bool has_send_streams = !answer->streams().empty(); - const bool wants_send = has_send_streams || is_data; - const bool recv_audio = - answer->type() == cricket::MEDIA_TYPE_AUDIO && options.recv_audio; - const bool recv_video = - answer->type() == cricket::MEDIA_TYPE_VIDEO && options.recv_video; - const bool recv_data = - answer->type() == cricket::MEDIA_TYPE_DATA; - const bool wants_receive = recv_audio || recv_video || recv_data; - auto offer_rtd = RtpTransceiverDirection::FromMediaContentDirection(offer->direction()); - auto wants_rtd = RtpTransceiverDirection(wants_send, wants_receive); - answer->set_direction(NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd) - .ToMediaContentDirection()); + + answer->set_direction(NegotiateRtpTransceiverDirection( + offer_rtd, media_description_options.direction) + .ToMediaContentDirection()); return true; } @@ -1239,23 +1211,20 @@ static const TransportDescription* GetTransportDescription( } // Gets the current DTLS state from the transport description. -static bool IsDtlsActive( - const std::string& content_name, - const SessionDescription* current_description) { - if (!current_description) +static bool IsDtlsActive(const ContentInfo* content, + const SessionDescription* current_description) { + if (!content) { return false; + } - const ContentInfo* content = - current_description->GetContentByName(content_name); - if (!content) + size_t msection_index = content - ¤t_description->contents()[0]; + + if (current_description->transport_infos().size() <= msection_index) { return false; + } - const TransportDescription* current_tdesc = - GetTransportDescription(content_name, current_description); - if (!current_tdesc) - return false; - - return current_tdesc->secure(); + return current_description->transport_infos()[msection_index] + .description.secure(); } std::string MediaContentDirectionToString(MediaContentDirection direction) { @@ -1281,75 +1250,54 @@ std::string MediaContentDirectionToString(MediaContentDirection direction) { return dir_str; } -void MediaSessionOptions::AddSendStream(MediaType type, - const std::string& id, - const std::string& sync_label) { - AddSendStreamInternal(type, id, sync_label, 1); +void MediaDescriptionOptions::AddAudioSender(const std::string& track_id, + const std::string& stream_id) { + RTC_DCHECK(type == MEDIA_TYPE_AUDIO); + AddSenderInternal(track_id, stream_id, 1); } -void MediaSessionOptions::AddSendVideoStream( - const std::string& id, - const std::string& sync_label, - int num_sim_layers) { - AddSendStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers); +void MediaDescriptionOptions::AddVideoSender(const std::string& track_id, + const std::string& stream_id, + int num_sim_layers) { + RTC_DCHECK(type == MEDIA_TYPE_VIDEO); + AddSenderInternal(track_id, stream_id, num_sim_layers); } -void MediaSessionOptions::AddSendStreamInternal( - MediaType type, - const std::string& id, - const std::string& sync_label, - int num_sim_layers) { - streams.push_back(Stream(type, id, sync_label, num_sim_layers)); - - // If we haven't already set the data_channel_type, and we add a - // stream, we assume it's an RTP data stream. - if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE) - data_channel_type = DCT_RTP; +void MediaDescriptionOptions::AddRtpDataChannel(const std::string& track_id, + const std::string& stream_id) { + RTC_DCHECK(type == MEDIA_TYPE_DATA); + AddSenderInternal(track_id, stream_id, 1); } -void MediaSessionOptions::RemoveSendStream(MediaType type, - const std::string& id) { - Streams::iterator stream_it = streams.begin(); - for (; stream_it != streams.end(); ++stream_it) { - if (stream_it->type == type && stream_it->id == id) { - streams.erase(stream_it); - return; - } - } - RTC_NOTREACHED(); +void MediaDescriptionOptions::AddSenderInternal(const std::string& track_id, + const std::string& stream_id, + int num_sim_layers) { + sender_options.push_back(SenderOptions{track_id, stream_id, num_sim_layers}); } -bool MediaSessionOptions::HasSendMediaStream(MediaType type) const { - Streams::const_iterator stream_it = streams.begin(); - for (; stream_it != streams.end(); ++stream_it) { - if (stream_it->type == type) { - return true; - } - } - return false; +bool MediaSessionOptions::HasMediaDescription(MediaType type) const { + return std::find_if(media_description_options.begin(), + media_description_options.end(), + [type](const MediaDescriptionOptions& t) { + return t.type == type; + }) != media_description_options.end(); } MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( const TransportDescriptionFactory* transport_desc_factory) - : secure_(SEC_DISABLED), - add_legacy_(true), - transport_desc_factory_(transport_desc_factory) { -} + : transport_desc_factory_(transport_desc_factory) {} MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( ChannelManager* channel_manager, const TransportDescriptionFactory* transport_desc_factory) - : secure_(SEC_DISABLED), - add_legacy_(true), - transport_desc_factory_(transport_desc_factory) { + : transport_desc_factory_(transport_desc_factory) { channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_); channel_manager->GetSupportedAudioReceiveCodecs(&audio_recv_codecs_); channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_); channel_manager->GetSupportedVideoCodecs(&video_codecs_); channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_); channel_manager->GetSupportedDataCodecs(&data_codecs_); - NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_, - &audio_sendrecv_codecs_); + ComputeAudioCodecsIntersectionAndUnion(); } const AudioCodecs& MediaSessionDescriptionFactory::audio_sendrecv_codecs() @@ -1369,129 +1317,114 @@ void MediaSessionDescriptionFactory::set_audio_codecs( const AudioCodecs& send_codecs, const AudioCodecs& recv_codecs) { audio_send_codecs_ = send_codecs; audio_recv_codecs_ = recv_codecs; - audio_sendrecv_codecs_.clear(); - // Use NegotiateCodecs to merge our codec lists, since the operation is - // essentially the same. Put send_codecs as the offered_codecs, which is the - // order we'd like to follow. The reasoning is that encoding is usually more - // expensive than decoding, and prioritizing a codec in the send list probably - // means it's a codec we can handle efficiently. - NegotiateCodecs(recv_codecs, send_codecs, &audio_sendrecv_codecs_); + ComputeAudioCodecsIntersectionAndUnion(); } SessionDescription* MediaSessionDescriptionFactory::CreateOffer( - const MediaSessionOptions& options, + const MediaSessionOptions& session_options, const SessionDescription* current_description) const { std::unique_ptr offer(new SessionDescription()); StreamParamsVec current_streams; GetCurrentStreamParams(current_description, ¤t_streams); - const bool wants_send = - options.HasSendMediaStream(MEDIA_TYPE_AUDIO) || add_legacy_; - const AudioCodecs& supported_audio_codecs = - GetAudioCodecsForOffer({wants_send, options.recv_audio}); + AudioCodecs offer_audio_codecs; + VideoCodecs offer_video_codecs; + DataCodecs offer_data_codecs; + GetCodecsForOffer(current_description, &offer_audio_codecs, + &offer_video_codecs, &offer_data_codecs); - AudioCodecs audio_codecs; - VideoCodecs video_codecs; - DataCodecs data_codecs; - GetCodecsToOffer(current_description, supported_audio_codecs, - video_codecs_, data_codecs_, - &audio_codecs, &video_codecs, &data_codecs); - - if (!options.vad_enabled) { + if (!session_options.vad_enabled) { // If application doesn't want CN codecs in offer. - StripCNCodecs(&audio_codecs); + StripCNCodecs(&offer_audio_codecs); } + FilterDataCodecs(&offer_data_codecs, + session_options.data_channel_type == DCT_SCTP); RtpHeaderExtensions audio_rtp_extensions; RtpHeaderExtensions video_rtp_extensions; GetRtpHdrExtsToOffer(current_description, &audio_rtp_extensions, &video_rtp_extensions); - bool audio_added = false; - bool video_added = false; - bool data_added = false; - - // Iterate through the contents of |current_description| to maintain the order - // of the m-lines in the new offer. + // Must have options for each existing section. if (current_description) { - ContentInfos::const_iterator it = current_description->contents().begin(); - for (; it != current_description->contents().end(); ++it) { - if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { - if (!AddAudioContentForOffer(options, current_description, - audio_rtp_extensions, audio_codecs, - ¤t_streams, offer.get())) { - return NULL; - } - audio_added = true; - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { - if (!AddVideoContentForOffer(options, current_description, - video_rtp_extensions, video_codecs, - ¤t_streams, offer.get())) { - return NULL; - } - video_added = true; - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) { - MediaSessionOptions options_copy(options); - if (IsSctp(static_cast(it->description) - ->protocol())) { - options_copy.data_channel_type = DCT_SCTP; - } - if (!AddDataContentForOffer(options_copy, current_description, - &data_codecs, ¤t_streams, - offer.get())) { - return NULL; - } - data_added = true; - } else { - RTC_NOTREACHED(); - } - } + RTC_DCHECK(current_description->contents().size() <= + session_options.media_description_options.size()); } - // Append contents that are not in |current_description|. - if (!audio_added && options.has_audio() && - !AddAudioContentForOffer(options, current_description, - audio_rtp_extensions, audio_codecs, - ¤t_streams, offer.get())) { - return NULL; - } - if (!video_added && options.has_video() && - !AddVideoContentForOffer(options, current_description, - video_rtp_extensions, video_codecs, - ¤t_streams, offer.get())) { - return NULL; - } - if (!data_added && options.has_data() && - !AddDataContentForOffer(options, current_description, &data_codecs, - ¤t_streams, offer.get())) { - return NULL; + // Iterate through the media description options, matching with existing media + // descriptions in |current_description|. + int msection_index = 0; + for (const MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + const ContentInfo* current_content = nullptr; + if (current_description && + msection_index < + static_cast(current_description->contents().size())) { + current_content = ¤t_description->contents()[msection_index]; + // Media type must match. + RTC_DCHECK(IsMediaContentOfType(current_content, + media_description_options.type)); + } + switch (media_description_options.type) { + case MEDIA_TYPE_AUDIO: + if (!AddAudioContentForOffer(media_description_options, session_options, + current_content, current_description, + audio_rtp_extensions, offer_audio_codecs, + ¤t_streams, offer.get())) { + return nullptr; + } + break; + case MEDIA_TYPE_VIDEO: + if (!AddVideoContentForOffer(media_description_options, session_options, + current_content, current_description, + video_rtp_extensions, offer_video_codecs, + ¤t_streams, offer.get())) { + return nullptr; + } + break; + case MEDIA_TYPE_DATA: + if (!AddDataContentForOffer(media_description_options, session_options, + current_content, current_description, + offer_data_codecs, ¤t_streams, + offer.get())) { + return nullptr; + } + break; + default: + RTC_NOTREACHED(); + } + ++msection_index; } // Bundle the contents together, if we've been asked to do so, and update any // parameters that need to be tweaked for BUNDLE. - if (options.bundle_enabled) { + if (session_options.bundle_enabled && offer->contents().size() > 0u) { ContentGroup offer_bundle(GROUP_TYPE_BUNDLE); - for (ContentInfos::const_iterator content = offer->contents().begin(); - content != offer->contents().end(); ++content) { - offer_bundle.AddContentName(content->name); + for (const ContentInfo& content : offer->contents()) { + // TODO(deadbeef): There are conditions that make bundling two media + // descriptions together illegal. For example, they use the same payload + // type to represent different codecs, or same IDs for different header + // extensions. We need to detect this and not try to bundle those media + // descriptions together. + offer_bundle.AddContentName(content.name); } offer->AddGroup(offer_bundle); if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) { LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle."; - return NULL; + return nullptr; } if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) { LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle."; - return NULL; + return nullptr; } } - return offer.release(); } SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( - const SessionDescription* offer, const MediaSessionOptions& options, + const SessionDescription* offer, + const MediaSessionOptions& session_options, const SessionDescription* current_description) const { if (!offer) { return nullptr; @@ -1511,32 +1444,81 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( // Transport info shared by the bundle group. std::unique_ptr bundle_transport; - ContentInfos::const_iterator it = offer->contents().begin(); - for (; it != offer->contents().end(); ++it) { - if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { - if (!AddAudioContentForAnswer(offer, options, current_description, - bundle_transport.get(), ¤t_streams, - answer.get())) { - return NULL; - } - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { - if (!AddVideoContentForAnswer(offer, options, current_description, - bundle_transport.get(), ¤t_streams, - answer.get())) { - return NULL; - } - } else { - RTC_DCHECK(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)); - if (!AddDataContentForAnswer(offer, options, current_description, - bundle_transport.get(), ¤t_streams, - answer.get())) { - return NULL; - } + // Get list of all possible codecs that respects existing payload type + // mappings and uses a single payload type space. + // + // Note that these lists may be further filtered for each m= section; this + // step is done just to establish the payload type mappings shared by all + // sections. + AudioCodecs answer_audio_codecs; + VideoCodecs answer_video_codecs; + DataCodecs answer_data_codecs; + GetCodecsForAnswer(current_description, offer, &answer_audio_codecs, + &answer_video_codecs, &answer_data_codecs); + + if (!session_options.vad_enabled) { + // If application doesn't want CN codecs in answer. + StripCNCodecs(&answer_audio_codecs); + } + FilterDataCodecs(&answer_data_codecs, + session_options.data_channel_type == DCT_SCTP); + + // Must have options for exactly as many sections as in the offer. + RTC_DCHECK(offer->contents().size() == + session_options.media_description_options.size()); + // Iterate through the media description options, matching with existing + // media descriptions in |current_description|. + int msection_index = 0; + for (const MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + const ContentInfo* offer_content = &offer->contents()[msection_index]; + // Media types and MIDs must match between the remote offer and the + // MediaDescriptionOptions. + RTC_DCHECK( + IsMediaContentOfType(offer_content, media_description_options.type)); + RTC_DCHECK(media_description_options.mid == offer_content->name); + const ContentInfo* current_content = nullptr; + if (current_description && + msection_index < + static_cast(current_description->contents().size())) { + current_content = ¤t_description->contents()[msection_index]; } + switch (media_description_options.type) { + case MEDIA_TYPE_AUDIO: + if (!AddAudioContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, + bundle_transport.get(), answer_audio_codecs, ¤t_streams, + answer.get())) { + return nullptr; + } + break; + case MEDIA_TYPE_VIDEO: + if (!AddVideoContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, + bundle_transport.get(), answer_video_codecs, ¤t_streams, + answer.get())) { + return nullptr; + } + break; + case MEDIA_TYPE_DATA: + if (!AddDataContentForAnswer(media_description_options, session_options, + offer_content, offer, current_content, + current_description, + bundle_transport.get(), answer_data_codecs, + ¤t_streams, answer.get())) { + return nullptr; + } + break; + default: + RTC_NOTREACHED(); + } + ++msection_index; // See if we can add the newly generated m= section to the BUNDLE group in // the answer. ContentInfo& added = answer->contents().back(); - if (!added.rejected && options.bundle_enabled && offer_bundle && + if (!added.rejected && session_options.bundle_enabled && offer_bundle && offer_bundle->HasContentName(added.name)) { answer_bundle.AddContentName(added.name); bundle_transport.reset( @@ -1596,11 +1578,37 @@ const AudioCodecs& MediaSessionDescriptionFactory::GetAudioCodecsForAnswer( } } -void MediaSessionDescriptionFactory::GetCodecsToOffer( +void MergeCodecsFromDescription(const SessionDescription* description, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs, + DataCodecs* data_codecs, + UsedPayloadTypes* used_pltypes) { + RTC_DCHECK(description); + for (const ContentInfo& content : description->contents()) { + if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + static_cast(content.description); + MergeCodecs(audio->codecs(), audio_codecs, used_pltypes); + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + static_cast(content.description); + MergeCodecs(video->codecs(), video_codecs, used_pltypes); + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) { + const DataContentDescription* data = + static_cast(content.description); + MergeCodecs(data->codecs(), data_codecs, used_pltypes); + } + } +} + +// Getting codecs for an offer involves these steps: +// +// 1. Construct payload type -> codec mappings for current description. +// 2. Add any reference codecs that weren't already present +// 3. For each individual media description (m= section), filter codecs based +// on the directional attribute (happens in another method). +void MediaSessionDescriptionFactory::GetCodecsForOffer( const SessionDescription* current_description, - const AudioCodecs& supported_audio_codecs, - const VideoCodecs& supported_video_codecs, - const DataCodecs& supported_data_codecs, AudioCodecs* audio_codecs, VideoCodecs* video_codecs, DataCodecs* data_codecs) const { @@ -1609,87 +1617,150 @@ void MediaSessionDescriptionFactory::GetCodecsToOffer( video_codecs->clear(); data_codecs->clear(); - // First - get all codecs from the current description if the media type - // is used. - // Add them to |used_pltypes| so the payloadtype is not reused if a new media - // type is added. + // is used. Add them to |used_pltypes| so the payload type is not reused if a + // new media type is added. if (current_description) { - const AudioContentDescription* audio = - GetFirstAudioContentDescription(current_description); - if (audio) { - *audio_codecs = audio->codecs(); - used_pltypes.FindAndSetIdUsed(audio_codecs); - } - const VideoContentDescription* video = - GetFirstVideoContentDescription(current_description); - if (video) { - *video_codecs = video->codecs(); - used_pltypes.FindAndSetIdUsed(video_codecs); - } - const DataContentDescription* data = - GetFirstDataContentDescription(current_description); - if (data) { - *data_codecs = data->codecs(); - used_pltypes.FindAndSetIdUsed(data_codecs); - } + MergeCodecsFromDescription(current_description, audio_codecs, video_codecs, + data_codecs, &used_pltypes); } // Add our codecs that are not in |current_description|. - FindCodecsToOffer(supported_audio_codecs, audio_codecs, - &used_pltypes); - FindCodecsToOffer(supported_video_codecs, video_codecs, - &used_pltypes); - FindCodecsToOffer(supported_data_codecs, data_codecs, - &used_pltypes); + MergeCodecs(all_audio_codecs_, audio_codecs, &used_pltypes); + MergeCodecs(video_codecs_, video_codecs, &used_pltypes); + MergeCodecs(data_codecs_, data_codecs, &used_pltypes); +} + +// Getting codecs for an answer involves these steps: +// +// 1. Construct payload type -> codec mappings for current description. +// 2. Add any codecs from the offer that weren't already present. +// 3. Add any remaining codecs that weren't already present. +// 4. For each individual media description (m= section), filter codecs based +// on the directional attribute (happens in another method). +void MediaSessionDescriptionFactory::GetCodecsForAnswer( + const SessionDescription* current_description, + const SessionDescription* remote_offer, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs, + DataCodecs* data_codecs) const { + UsedPayloadTypes used_pltypes; + audio_codecs->clear(); + video_codecs->clear(); + data_codecs->clear(); + + // First - get all codecs from the current description if the media type + // is used. Add them to |used_pltypes| so the payload type is not reused if a + // new media type is added. + if (current_description) { + MergeCodecsFromDescription(current_description, audio_codecs, video_codecs, + data_codecs, &used_pltypes); + } + + // Second - filter out codecs that we don't support at all and should ignore. + AudioCodecs filtered_offered_audio_codecs; + VideoCodecs filtered_offered_video_codecs; + DataCodecs filtered_offered_data_codecs; + for (const ContentInfo& content : remote_offer->contents()) { + if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + static_cast(content.description); + for (const AudioCodec& offered_audio_codec : audio->codecs()) { + if (!FindMatchingCodec(audio->codecs(), + filtered_offered_audio_codecs, + offered_audio_codec, nullptr) && + FindMatchingCodec(audio->codecs(), all_audio_codecs_, + offered_audio_codec, nullptr)) { + filtered_offered_audio_codecs.push_back(offered_audio_codec); + } + } + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + static_cast(content.description); + for (const VideoCodec& offered_video_codec : video->codecs()) { + if (!FindMatchingCodec(video->codecs(), + filtered_offered_video_codecs, + offered_video_codec, nullptr) && + FindMatchingCodec(video->codecs(), video_codecs_, + offered_video_codec, nullptr)) { + filtered_offered_video_codecs.push_back(offered_video_codec); + } + } + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) { + const DataContentDescription* data = + static_cast(content.description); + for (const DataCodec& offered_data_codec : data->codecs()) { + if (!FindMatchingCodec(data->codecs(), + filtered_offered_data_codecs, + offered_data_codec, nullptr) && + FindMatchingCodec(data->codecs(), data_codecs_, + offered_data_codec, nullptr)) { + filtered_offered_data_codecs.push_back(offered_data_codec); + } + } + } + } + + // Add codecs that are not in |current_description| but were in + // |remote_offer|. + MergeCodecs(filtered_offered_audio_codecs, audio_codecs, + &used_pltypes); + MergeCodecs(filtered_offered_video_codecs, video_codecs, + &used_pltypes); + MergeCodecs(filtered_offered_data_codecs, data_codecs, + &used_pltypes); } void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer( const SessionDescription* current_description, - RtpHeaderExtensions* audio_extensions, - RtpHeaderExtensions* video_extensions) const { + RtpHeaderExtensions* offer_audio_extensions, + RtpHeaderExtensions* offer_video_extensions) const { // All header extensions allocated from the same range to avoid potential // issues when using BUNDLE. UsedRtpHeaderExtensionIds used_ids; RtpHeaderExtensions all_regular_extensions; RtpHeaderExtensions all_encrypted_extensions; - audio_extensions->clear(); - video_extensions->clear(); + offer_audio_extensions->clear(); + offer_video_extensions->clear(); // First - get all extensions from the current description if the media type // is used. // Add them to |used_ids| so the local ids are not reused if a new media // type is added. if (current_description) { - const AudioContentDescription* audio = - GetFirstAudioContentDescription(current_description); - if (audio) { - *audio_extensions = audio->rtp_header_extensions(); - FindAndSetRtpHdrExtUsed(audio_extensions, &all_regular_extensions, - &all_encrypted_extensions, &used_ids); - } - const VideoContentDescription* video = - GetFirstVideoContentDescription(current_description); - if (video) { - *video_extensions = video->rtp_header_extensions(); - FindAndSetRtpHdrExtUsed(video_extensions, &all_regular_extensions, - &all_encrypted_extensions, &used_ids); + for (const ContentInfo& content : current_description->contents()) { + if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + static_cast(content.description); + MergeRtpHdrExts(audio->rtp_header_extensions(), offer_audio_extensions, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + static_cast(content.description); + MergeRtpHdrExts(video->rtp_header_extensions(), offer_video_extensions, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + } } } // Add our default RTP header extensions that are not in // |current_description|. - FindRtpHdrExtsToOffer(audio_rtp_header_extensions(), audio_extensions, - &all_regular_extensions, &used_ids); - FindRtpHdrExtsToOffer(video_rtp_header_extensions(), video_extensions, - &all_regular_extensions, &used_ids); + MergeRtpHdrExts(audio_rtp_header_extensions(), offer_audio_extensions, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + MergeRtpHdrExts(video_rtp_header_extensions(), offer_video_extensions, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + // TODO(jbauch): Support adding encrypted header extensions to existing // sessions. if (enable_encrypted_rtp_header_extensions_ && !current_description) { - AddEncryptedVersionsOfHdrExts(audio_extensions, &all_encrypted_extensions, - &used_ids); - AddEncryptedVersionsOfHdrExts(video_extensions, &all_encrypted_extensions, - &used_ids); + AddEncryptedVersionsOfHdrExts(offer_audio_extensions, + &all_encrypted_extensions, &used_ids); + AddEncryptedVersionsOfHdrExts(offer_video_extensions, + &all_encrypted_extensions, &used_ids); } } @@ -1743,35 +1814,71 @@ bool MediaSessionDescriptionFactory::AddTransportAnswer( return true; } +// |audio_codecs| = set of all possible codecs that can be used, with correct +// payload type mappings +// +// |supported_audio_codecs| = set of codecs that are supported for the direction +// of this m= section +// +// acd->codecs() = set of previously negotiated codecs for this m= section +// +// The payload types should come from audio_codecs, but the order should come +// from acd->codecs() and then supported_codecs, to ensure that re-offers don't +// change existing codec priority, and that new codecs are added with the right +// priority. bool MediaSessionDescriptionFactory::AddAudioContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, const RtpHeaderExtensions& audio_rtp_extensions, const AudioCodecs& audio_codecs, StreamParamsVec* current_streams, SessionDescription* desc) const { - const ContentInfo* current_audio_content = - GetFirstAudioContent(current_description); - std::string content_name = - current_audio_content ? current_audio_content->name : CN_AUDIO; + // Filter audio_codecs (which includes all codecs, with correctly remapped + // payload types) based on transceiver direction. + const AudioCodecs& supported_audio_codecs = + GetAudioCodecsForOffer(media_description_options.direction); + + AudioCodecs filtered_codecs; + // Add the codecs from current content if exists. + if (current_content) { + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); + const AudioContentDescription* acd = + static_cast( + current_content->description); + for (const AudioCodec& codec : acd->codecs()) { + if (FindMatchingCodec(supported_audio_codecs, audio_codecs, + codec, nullptr)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported audio codecs. + AudioCodec found_codec; + for (const AudioCodec& codec : supported_audio_codecs) { + if (FindMatchingCodec(supported_audio_codecs, audio_codecs, + codec, &found_codec) && + !FindMatchingCodec(supported_audio_codecs, filtered_codecs, + codec, nullptr)) { + // Use the |found_codec| from |audio_codecs| because it has the correctly + // mapped payload type. + filtered_codecs.push_back(found_codec); + } + } cricket::SecurePolicy sdes_policy = - IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED - : secure(); + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); std::unique_ptr audio(new AudioContentDescription()); std::vector crypto_suites; - GetSupportedAudioSdesCryptoSuiteNames(options.crypto_options, &crypto_suites); + GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options, + &crypto_suites); if (!CreateMediaContentOffer( - options, - audio_codecs, - sdes_policy, - GetCryptos(GetFirstAudioContentDescription(current_description)), - crypto_suites, - audio_rtp_extensions, - add_legacy_, - current_streams, - audio.get())) { + media_description_options.sender_options, session_options, + filtered_codecs, sdes_policy, GetCryptos(current_content), + crypto_suites, audio_rtp_extensions, current_streams, audio.get())) { return false; } audio->set_lang(lang_); @@ -1779,13 +1886,13 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer( bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); SetMediaProtocol(secure_transport, audio.get()); - auto offer_rtd = - RtpTransceiverDirection(!audio->streams().empty(), options.recv_audio); - audio->set_direction(offer_rtd.ToMediaContentDirection()); + audio->set_direction( + media_description_options.direction.ToMediaContentDirection()); - desc->AddContent(content_name, NS_JINGLE_RTP, audio.release()); - if (!AddTransportOffer(content_name, - GetTransportOptions(options, content_name), + desc->AddContent(media_description_options.mid, NS_JINGLE_RTP, + media_description_options.stopped, audio.release()); + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, current_description, desc)) { return false; } @@ -1794,87 +1901,98 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer( } bool MediaSessionDescriptionFactory::AddVideoContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, const RtpHeaderExtensions& video_rtp_extensions, const VideoCodecs& video_codecs, StreamParamsVec* current_streams, SessionDescription* desc) const { - const ContentInfo* current_video_content = - GetFirstVideoContent(current_description); - std::string content_name = - current_video_content ? current_video_content->name : CN_VIDEO; - cricket::SecurePolicy sdes_policy = - IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED - : secure(); + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); std::unique_ptr video(new VideoContentDescription()); std::vector crypto_suites; - GetSupportedVideoSdesCryptoSuiteNames(options.crypto_options, &crypto_suites); + GetSupportedVideoSdesCryptoSuiteNames(session_options.crypto_options, + &crypto_suites); + + VideoCodecs filtered_codecs; + // Add the codecs from current content if exists. + if (current_content) { + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)); + const VideoContentDescription* vcd = + static_cast( + current_content->description); + for (const VideoCodec& codec : vcd->codecs()) { + if (FindMatchingCodec(video_codecs_, video_codecs, codec, + nullptr)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported video codecs. + VideoCodec found_codec; + for (const VideoCodec& codec : video_codecs_) { + if (FindMatchingCodec(video_codecs_, video_codecs, codec, + &found_codec) && + !FindMatchingCodec(video_codecs_, filtered_codecs, codec, + nullptr)) { + // Use the |found_codec| from |video_codecs| because it has the correctly + // mapped payload type. + filtered_codecs.push_back(found_codec); + } + } + if (!CreateMediaContentOffer( - options, - video_codecs, - sdes_policy, - GetCryptos(GetFirstVideoContentDescription(current_description)), - crypto_suites, - video_rtp_extensions, - add_legacy_, - current_streams, - video.get())) { + media_description_options.sender_options, session_options, + filtered_codecs, sdes_policy, GetCryptos(current_content), + crypto_suites, video_rtp_extensions, current_streams, video.get())) { return false; } - video->set_bandwidth(options.video_bandwidth); + video->set_bandwidth(kAutoBandwidth); bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); SetMediaProtocol(secure_transport, video.get()); - if (!video->streams().empty()) { - if (options.recv_video) { - video->set_direction(MD_SENDRECV); - } else { - video->set_direction(MD_SENDONLY); - } - } else { - if (options.recv_video) { - video->set_direction(MD_RECVONLY); - } else { - video->set_direction(MD_INACTIVE); - } - } + video->set_direction( + media_description_options.direction.ToMediaContentDirection()); - desc->AddContent(content_name, NS_JINGLE_RTP, video.release()); - if (!AddTransportOffer(content_name, - GetTransportOptions(options, content_name), + desc->AddContent(media_description_options.mid, NS_JINGLE_RTP, + media_description_options.stopped, video.release()); + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, current_description, desc)) { return false; } - return true; } bool MediaSessionDescriptionFactory::AddDataContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, - DataCodecs* data_codecs, + const DataCodecs& data_codecs, StreamParamsVec* current_streams, SessionDescription* desc) const { bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); std::unique_ptr data(new DataContentDescription()); - bool is_sctp = (options.data_channel_type == DCT_SCTP); - - FilterDataCodecs(data_codecs, is_sctp); - - const ContentInfo* current_data_content = - GetFirstDataContent(current_description); - std::string content_name = - current_data_content ? current_data_content->name : CN_DATA; + bool is_sctp = (session_options.data_channel_type == DCT_SCTP); + // If the DataChannel type is not specified, use the DataChannel type in + // the current description. + if (session_options.data_channel_type == DCT_NONE && current_content) { + is_sctp = (static_cast( + current_content->description) + ->protocol() == kMediaProtocolSctp); + } cricket::SecurePolicy sdes_policy = - IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED - : secure(); + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); std::vector crypto_suites; if (is_sctp) { // SDES doesn't make sense for SCTP, so we disable it, and we only @@ -1890,227 +2008,279 @@ bool MediaSessionDescriptionFactory::AddDataContentForOffer( data->set_protocol( secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp); } else { - GetSupportedDataSdesCryptoSuiteNames(options.crypto_options, + GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options, &crypto_suites); } + // Even SCTP uses a "codec". if (!CreateMediaContentOffer( - options, - *data_codecs, - sdes_policy, - GetCryptos(GetFirstDataContentDescription(current_description)), - crypto_suites, - RtpHeaderExtensions(), - add_legacy_, - current_streams, - data.get())) { + media_description_options.sender_options, session_options, + data_codecs, sdes_policy, GetCryptos(current_content), crypto_suites, + RtpHeaderExtensions(), current_streams, data.get())) { return false; } if (is_sctp) { - desc->AddContent(content_name, NS_JINGLE_DRAFT_SCTP, data.release()); + desc->AddContent(media_description_options.mid, NS_JINGLE_DRAFT_SCTP, + data.release()); } else { - data->set_bandwidth(options.data_bandwidth); + data->set_bandwidth(kDataMaxBandwidth); SetMediaProtocol(secure_transport, data.get()); - desc->AddContent(content_name, NS_JINGLE_RTP, data.release()); + desc->AddContent(media_description_options.mid, NS_JINGLE_RTP, + media_description_options.stopped, data.release()); } - if (!AddTransportOffer(content_name, - GetTransportOptions(options, content_name), + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, current_description, desc)) { return false; } return true; } +// |audio_codecs| = set of all possible codecs that can be used, with correct +// payload type mappings +// +// |supported_audio_codecs| = set of codecs that are supported for the direction +// of this m= section +// +// acd->codecs() = set of previously negotiated codecs for this m= section +// +// The payload types should come from audio_codecs, but the order should come +// from acd->codecs() and then supported_codecs, to ensure that re-offers don't +// change existing codec priority, and that new codecs are added with the right +// priority. bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( - const SessionDescription* offer, - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, const SessionDescription* current_description, const TransportInfo* bundle_transport, + const AudioCodecs& audio_codecs, StreamParamsVec* current_streams, SessionDescription* answer) const { - const ContentInfo* audio_content = GetFirstAudioContent(offer); - const AudioContentDescription* offer_audio = - static_cast(audio_content->description); + const AudioContentDescription* offer_audio_description = + static_cast(offer_content->description); std::unique_ptr audio_transport( - CreateTransportAnswer(audio_content->name, offer, - GetTransportOptions(options, audio_content->name), + CreateTransportAnswer(media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, bundle_transport != nullptr)); if (!audio_transport) { return false; } - // Pick codecs based on the requested communications direction in the offer. - const bool wants_send = - options.HasSendMediaStream(MEDIA_TYPE_AUDIO) || add_legacy_; - auto wants_rtd = RtpTransceiverDirection(wants_send, options.recv_audio); - auto offer_rtd = - RtpTransceiverDirection::FromMediaContentDirection( - offer_audio->direction()); + // Pick codecs based on the requested communications direction in the offer + // and the selected direction in the answer. + // Note these will be filtered one final time in CreateMediaContentAnswer. + auto wants_rtd = media_description_options.direction; + auto offer_rtd = RtpTransceiverDirection::FromMediaContentDirection( + offer_audio_description->direction()); auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd); - AudioCodecs audio_codecs = GetAudioCodecsForAnswer(offer_rtd, answer_rtd); - if (!options.vad_enabled) { - StripCNCodecs(&audio_codecs); + AudioCodecs supported_audio_codecs = + GetAudioCodecsForAnswer(offer_rtd, answer_rtd); + + AudioCodecs filtered_codecs; + // Add the codecs from current content if exists. + if (current_content) { + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); + const AudioContentDescription* acd = + static_cast( + current_content->description); + for (const AudioCodec& codec : acd->codecs()) { + if (FindMatchingCodec(supported_audio_codecs, audio_codecs, + codec, nullptr)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported audio codecs. + AudioCodec found_codec; + for (const AudioCodec& codec : supported_audio_codecs) { + if (FindMatchingCodec(supported_audio_codecs, audio_codecs, + codec, &found_codec) && + !FindMatchingCodec(supported_audio_codecs, filtered_codecs, + codec, nullptr)) { + // Use the |found_codec| from |audio_codecs| because it has the correctly + // mapped payload type. + filtered_codecs.push_back(found_codec); + } } - bool bundle_enabled = - offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; std::unique_ptr audio_answer( new AudioContentDescription()); // Do not require or create SDES cryptos if DTLS is used. cricket::SecurePolicy sdes_policy = audio_transport->secure() ? cricket::SEC_DISABLED : secure(); if (!CreateMediaContentAnswer( - offer_audio, - options, - audio_codecs, - sdes_policy, - GetCryptos(GetFirstAudioContentDescription(current_description)), - audio_rtp_extensions_, - enable_encrypted_rtp_header_extensions_, - current_streams, - add_legacy_, - bundle_enabled, - audio_answer.get())) { + offer_audio_description, media_description_options, session_options, + filtered_codecs, sdes_policy, GetCryptos(current_content), + audio_rtp_extensions_, enable_encrypted_rtp_header_extensions_, + current_streams, bundle_enabled, audio_answer.get())) { return false; // Fails the session setup. } bool secure = bundle_transport ? bundle_transport->description.secure() : audio_transport->secure(); - bool rejected = !options.has_audio() || audio_content->rejected || + bool rejected = media_description_options.stopped || + offer_content->rejected || !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, audio_answer->protocol(), secure); if (!rejected) { - AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer); + AddTransportAnswer(media_description_options.mid, *(audio_transport.get()), + answer); } else { - // RFC 3264 - // The answer MUST contain the same number of m-lines as the offer. - LOG(LS_INFO) << "Audio is not supported in the answer."; + LOG(LS_INFO) << "Audio m= section '" << media_description_options.mid + << "' being rejected in answer."; } - answer->AddContent(audio_content->name, audio_content->type, rejected, - audio_answer.release()); + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, audio_answer.release()); return true; } bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( - const SessionDescription* offer, - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, const SessionDescription* current_description, const TransportInfo* bundle_transport, + const VideoCodecs& video_codecs, StreamParamsVec* current_streams, SessionDescription* answer) const { - const ContentInfo* video_content = GetFirstVideoContent(offer); + const VideoContentDescription* offer_video_description = + static_cast(offer_content->description); + std::unique_ptr video_transport( - CreateTransportAnswer(video_content->name, offer, - GetTransportOptions(options, video_content->name), + CreateTransportAnswer(media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, bundle_transport != nullptr)); if (!video_transport) { return false; } + VideoCodecs filtered_codecs; + // Add the codecs from current content if exists. + if (current_content) { + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)); + const VideoContentDescription* vcd = + static_cast( + current_content->description); + for (const VideoCodec& codec : vcd->codecs()) { + if (FindMatchingCodec(video_codecs_, video_codecs, codec, + nullptr)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported video codecs. + VideoCodec found_codec; + for (const VideoCodec& codec : video_codecs_) { + if (FindMatchingCodec(video_codecs_, video_codecs, codec, + &found_codec) && + !FindMatchingCodec(video_codecs_, filtered_codecs, codec, + nullptr)) { + // Use the |found_codec| from |video_codecs| because it has the correctly + // mapped payload type. + filtered_codecs.push_back(found_codec); + } + } + + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; + std::unique_ptr video_answer( new VideoContentDescription()); // Do not require or create SDES cryptos if DTLS is used. cricket::SecurePolicy sdes_policy = video_transport->secure() ? cricket::SEC_DISABLED : secure(); - bool bundle_enabled = - offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; if (!CreateMediaContentAnswer( - static_cast( - video_content->description), - options, - video_codecs_, - sdes_policy, - GetCryptos(GetFirstVideoContentDescription(current_description)), - video_rtp_extensions_, - enable_encrypted_rtp_header_extensions_, - current_streams, - add_legacy_, - bundle_enabled, - video_answer.get())) { - return false; + offer_video_description, media_description_options, session_options, + filtered_codecs, sdes_policy, GetCryptos(current_content), + video_rtp_extensions_, enable_encrypted_rtp_header_extensions_, + current_streams, bundle_enabled, video_answer.get())) { + return false; // Failed the sessin setup. } bool secure = bundle_transport ? bundle_transport->description.secure() : video_transport->secure(); - bool rejected = !options.has_video() || video_content->rejected || + bool rejected = media_description_options.stopped || + offer_content->rejected || !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, video_answer->protocol(), secure); if (!rejected) { - if (!AddTransportAnswer(video_content->name, *(video_transport.get()), - answer)) { + if (!AddTransportAnswer(media_description_options.mid, + *(video_transport.get()), answer)) { return false; } - video_answer->set_bandwidth(options.video_bandwidth); + video_answer->set_bandwidth(kAutoBandwidth); } else { - // RFC 3264 - // The answer MUST contain the same number of m-lines as the offer. - LOG(LS_INFO) << "Video is not supported in the answer."; + LOG(LS_INFO) << "Video m= section '" << media_description_options.mid + << "' being rejected in answer."; } - answer->AddContent(video_content->name, video_content->type, rejected, - video_answer.release()); + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, video_answer.release()); return true; } bool MediaSessionDescriptionFactory::AddDataContentForAnswer( - const SessionDescription* offer, - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, const SessionDescription* current_description, const TransportInfo* bundle_transport, + const DataCodecs& data_codecs, StreamParamsVec* current_streams, SessionDescription* answer) const { - const ContentInfo* data_content = GetFirstDataContent(offer); std::unique_ptr data_transport( - CreateTransportAnswer(data_content->name, offer, - GetTransportOptions(options, data_content->name), + CreateTransportAnswer(media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, bundle_transport != nullptr)); if (!data_transport) { return false; } - bool is_sctp = (options.data_channel_type == DCT_SCTP); - std::vector data_codecs(data_codecs_); - FilterDataCodecs(&data_codecs, is_sctp); std::unique_ptr data_answer( new DataContentDescription()); // Do not require or create SDES cryptos if DTLS is used. cricket::SecurePolicy sdes_policy = data_transport->secure() ? cricket::SEC_DISABLED : secure(); - bool bundle_enabled = - offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; if (!CreateMediaContentAnswer( static_cast( - data_content->description), - options, - data_codecs_, - sdes_policy, - GetCryptos(GetFirstDataContentDescription(current_description)), - RtpHeaderExtensions(), - enable_encrypted_rtp_header_extensions_, - current_streams, - add_legacy_, - bundle_enabled, - data_answer.get())) { + offer_content->description), + media_description_options, session_options, data_codecs, sdes_policy, + GetCryptos(current_content), RtpHeaderExtensions(), + enable_encrypted_rtp_header_extensions_, current_streams, + bundle_enabled, data_answer.get())) { return false; // Fails the session setup. } // Respond with sctpmap if the offer uses sctpmap. const DataContentDescription* offer_data_description = - static_cast(data_content->description); + static_cast(offer_content->description); bool offer_uses_sctpmap = offer_data_description->use_sctpmap(); data_answer->set_use_sctpmap(offer_uses_sctpmap); bool secure = bundle_transport ? bundle_transport->description.secure() : data_transport->secure(); - bool rejected = !options.has_data() || data_content->rejected || + bool rejected = session_options.data_channel_type == DCT_NONE || + media_description_options.stopped || + offer_content->rejected || !IsMediaProtocolSupported(MEDIA_TYPE_DATA, data_answer->protocol(), secure); if (!rejected) { - data_answer->set_bandwidth(options.data_bandwidth); - if (!AddTransportAnswer(data_content->name, *(data_transport.get()), - answer)) { + data_answer->set_bandwidth(kDataMaxBandwidth); + if (!AddTransportAnswer(media_description_options.mid, + *(data_transport.get()), answer)) { return false; } } else { @@ -2118,11 +2288,39 @@ bool MediaSessionDescriptionFactory::AddDataContentForAnswer( // The answer MUST contain the same number of m-lines as the offer. LOG(LS_INFO) << "Data is not supported in the answer."; } - answer->AddContent(data_content->name, data_content->type, rejected, - data_answer.release()); + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, data_answer.release()); return true; } +void MediaSessionDescriptionFactory::ComputeAudioCodecsIntersectionAndUnion() { + audio_sendrecv_codecs_.clear(); + all_audio_codecs_.clear(); + // Compute the audio codecs union. + for (const AudioCodec& send : audio_send_codecs_) { + all_audio_codecs_.push_back(send); + if (!FindMatchingCodec(audio_send_codecs_, audio_recv_codecs_, + send, nullptr)) { + // It doesn't make sense to have an RTX codec we support sending but not + // receiving. + RTC_DCHECK(!IsRtxCodec(send)); + } + } + for (const AudioCodec& recv : audio_recv_codecs_) { + if (!FindMatchingCodec(audio_recv_codecs_, audio_send_codecs_, + recv, nullptr)) { + all_audio_codecs_.push_back(recv); + } + } + // Use NegotiateCodecs to merge our codec lists, since the operation is + // essentially the same. Put send_codecs as the offered_codecs, which is the + // order we'd like to follow. The reasoning is that encoding is usually more + // expensive than decoding, and prioritizing a codec in the send list probably + // means it's a codec we can handle efficiently. + NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_, + &audio_sendrecv_codecs_); +} + bool IsMediaContent(const ContentInfo* content) { return (content && (content->type == NS_JINGLE_RTP || diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h index 596bd18ad1..35bd44421a 100644 --- a/webrtc/pc/mediasession.h +++ b/webrtc/pc/mediasession.h @@ -102,83 +102,72 @@ RtpTransceiverDirection NegotiateRtpTransceiverDirection(RtpTransceiverDirection offer, RtpTransceiverDirection wants); -struct MediaSessionOptions { - MediaSessionOptions() - : recv_audio(true), - recv_video(false), - data_channel_type(DCT_NONE), - is_muc(false), - vad_enabled(true), // When disabled, removes all CN codecs from SDP. - rtcp_mux_enabled(true), - bundle_enabled(false), - video_bandwidth(kAutoBandwidth), - data_bandwidth(kDataMaxBandwidth), - rtcp_cname(kDefaultRtcpCname) {} +// Options for an RtpSender contained with an media description/"m=" section. +struct SenderOptions { + std::string track_id; + std::string stream_id; + int num_sim_layers; +}; - bool has_audio() const { - return recv_audio || HasSendMediaStream(MEDIA_TYPE_AUDIO); - } - bool has_video() const { - return recv_video || HasSendMediaStream(MEDIA_TYPE_VIDEO); - } - bool has_data() const { return data_channel_type != DCT_NONE; } +// Options for an individual media description/"m=" section. +struct MediaDescriptionOptions { + MediaDescriptionOptions(MediaType type, + const std::string& mid, + RtpTransceiverDirection direction, + bool stopped) + : type(type), mid(mid), direction(direction), stopped(stopped) {} - // Add a stream with MediaType type and id. - // All streams with the same sync_label will get the same CNAME. - // All ids must be unique. - void AddSendStream(MediaType type, - const std::string& id, - const std::string& sync_label); - void AddSendVideoStream(const std::string& id, - const std::string& sync_label, + // TODO(deadbeef): When we don't support Plan B, there will only be one + // sender per media description and this can be simplified. + void AddAudioSender(const std::string& track_id, + const std::string& stream_id); + void AddVideoSender(const std::string& track_id, + const std::string& stream_id, int num_sim_layers); - void RemoveSendStream(MediaType type, const std::string& id); + // Internally just uses sender_options. + void AddRtpDataChannel(const std::string& track_id, + const std::string& stream_id); - // Helper function. - void AddSendStreamInternal(MediaType type, - const std::string& id, - const std::string& sync_label, + MediaType type; + std::string mid; + RtpTransceiverDirection direction; + bool stopped; + TransportOptions transport_options; + // Note: There's no equivalent "RtpReceiverOptions" because only send + // stream information goes in the local descriptions. + std::vector sender_options; + + private: + // Doesn't DCHECK on |type|. + void AddSenderInternal(const std::string& track_id, + const std::string& stream_id, int num_sim_layers); +}; - bool HasSendMediaStream(MediaType type) const; +// Provides a mechanism for describing how m= sections should be generated. +// The m= section with index X will use media_description_options[X]. There +// must be an option for each existing section if creating an answer, or a +// subsequent offer. +struct MediaSessionOptions { + MediaSessionOptions() {} - // TODO(deadbeef): Put all the audio/video/data-specific options into a map - // structure (content name -> options). - // MediaSessionDescriptionFactory assumes there will never be more than one - // audio/video/data content, but this will change with unified plan. - bool recv_audio; - bool recv_video; - DataChannelType data_channel_type; - bool is_muc; - bool vad_enabled; - bool rtcp_mux_enabled; - bool bundle_enabled; - // bps. -1 == auto. - int video_bandwidth; - int data_bandwidth; - bool enable_ice_renomination = false; - // content name ("mid") => options. - std::map transport_options; - std::string rtcp_cname; + bool has_audio() const { return HasMediaDescription(MEDIA_TYPE_AUDIO); } + bool has_video() const { return HasMediaDescription(MEDIA_TYPE_VIDEO); } + bool has_data() const { return HasMediaDescription(MEDIA_TYPE_DATA); } + + bool HasMediaDescription(MediaType type) const; + + DataChannelType data_channel_type = DCT_NONE; + bool is_muc = false; + bool vad_enabled = true; // When disabled, removes all CN codecs from SDP. + bool rtcp_mux_enabled = true; + bool bundle_enabled = false; + std::string rtcp_cname = kDefaultRtcpCname; rtc::CryptoOptions crypto_options; - - struct Stream { - Stream(MediaType type, - const std::string& id, - const std::string& sync_label, - int num_sim_layers) - : type(type), id(id), sync_label(sync_label), - num_sim_layers(num_sim_layers) { - } - MediaType type; - std::string id; - std::string sync_label; - int num_sim_layers; - }; - - typedef std::vector Streams; - Streams streams; + // List of media description options in the same order that the media + // descriptions will be generated. + std::vector media_description_options; }; // "content" (as used in XEP-0166) descriptions for voice and video. @@ -277,7 +266,6 @@ class MediaContentDescription : public ContentDescription { streams_.push_back(sp); } // Sets the CNAME of all StreamParams if it have not been set. - // This can be used to set the CNAME of legacy streams. void SetCnameIfEmpty(const std::string& cname) { for (cricket::StreamParamsVec::iterator it = streams_.begin(); it != streams_.end(); ++it) { @@ -469,11 +457,6 @@ class MediaSessionDescriptionFactory { void set_data_codecs(const DataCodecs& codecs) { data_codecs_ = codecs; } SecurePolicy secure() const { return secure_; } void set_secure(SecurePolicy s) { secure_ = s; } - // Decides if a StreamParams shall be added to the audio and video media - // content in SessionDescription when CreateOffer and CreateAnswer is called - // even if |options| don't include a Stream. This is needed to support legacy - // applications. |add_legacy_| is true per default. - void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; } void set_enable_encrypted_rtp_header_extensions(bool enable) { enable_encrypted_rtp_header_extensions_ = enable; @@ -493,13 +476,15 @@ class MediaSessionDescriptionFactory { const AudioCodecs& GetAudioCodecsForAnswer( const RtpTransceiverDirection& offer, const RtpTransceiverDirection& answer) const; - void GetCodecsToOffer(const SessionDescription* current_description, - const AudioCodecs& supported_audio_codecs, - const VideoCodecs& supported_video_codecs, - const DataCodecs& supported_data_codecs, - AudioCodecs* audio_codecs, - VideoCodecs* video_codecs, - DataCodecs* data_codecs) const; + void GetCodecsForOffer(const SessionDescription* current_description, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs, + DataCodecs* data_codecs) const; + void GetCodecsForAnswer(const SessionDescription* current_description, + const SessionDescription* remote_offer, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs, + DataCodecs* data_codecs) const; void GetRtpHdrExtsToOffer(const SessionDescription* current_description, RtpHeaderExtensions* audio_extensions, RtpHeaderExtensions* video_extensions) const; @@ -526,7 +511,9 @@ class MediaSessionDescriptionFactory { // error. bool AddAudioContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, const RtpHeaderExtensions& audio_rtp_extensions, const AudioCodecs& audio_codecs, @@ -534,7 +521,9 @@ class MediaSessionDescriptionFactory { SessionDescription* desc) const; bool AddVideoContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, const RtpHeaderExtensions& video_rtp_extensions, const VideoCodecs& video_codecs, @@ -542,43 +531,66 @@ class MediaSessionDescriptionFactory { SessionDescription* desc) const; bool AddDataContentForOffer( - const MediaSessionOptions& options, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, const SessionDescription* current_description, - DataCodecs* data_codecs, + const DataCodecs& data_codecs, StreamParamsVec* current_streams, SessionDescription* desc) const; - bool AddAudioContentForAnswer(const SessionDescription* offer, - const MediaSessionOptions& options, - const SessionDescription* current_description, - const TransportInfo* bundle_transport, - StreamParamsVec* current_streams, - SessionDescription* answer) const; + bool AddAudioContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + const AudioCodecs& audio_codecs, + StreamParamsVec* current_streams, + SessionDescription* answer) const; - bool AddVideoContentForAnswer(const SessionDescription* offer, - const MediaSessionOptions& options, - const SessionDescription* current_description, - const TransportInfo* bundle_transport, - StreamParamsVec* current_streams, - SessionDescription* answer) const; + bool AddVideoContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + const VideoCodecs& video_codecs, + StreamParamsVec* current_streams, + SessionDescription* answer) const; - bool AddDataContentForAnswer(const SessionDescription* offer, - const MediaSessionOptions& options, - const SessionDescription* current_description, - const TransportInfo* bundle_transport, - StreamParamsVec* current_streams, - SessionDescription* answer) const; + bool AddDataContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + const DataCodecs& data_codecs, + StreamParamsVec* current_streams, + SessionDescription* answer) const; + + void ComputeAudioCodecsIntersectionAndUnion(); AudioCodecs audio_send_codecs_; AudioCodecs audio_recv_codecs_; + // Intersection of send and recv. AudioCodecs audio_sendrecv_codecs_; + // Union of send and recv. + AudioCodecs all_audio_codecs_; RtpHeaderExtensions audio_rtp_extensions_; VideoCodecs video_codecs_; RtpHeaderExtensions video_rtp_extensions_; DataCodecs data_codecs_; - SecurePolicy secure_; - bool add_legacy_; bool enable_encrypted_rtp_header_extensions_ = false; + // TODO(zhihuang): Rename secure_ to sdec_policy_; rename the related getter + // and setter. + SecurePolicy secure_ = SEC_DISABLED; std::string lang_; const TransportDescriptionFactory* transport_desc_factory_; }; diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc index ae8d13940b..f87dda1bf3 100644 --- a/webrtc/pc/mediasession_unittest.cc +++ b/webrtc/pc/mediasession_unittest.cc @@ -34,6 +34,7 @@ typedef std::vector Candidates; using cricket::MediaContentDescription; using cricket::MediaSessionDescriptionFactory; using cricket::MediaContentDirection; +using cricket::MediaDescriptionOptions; using cricket::MediaSessionOptions; using cricket::MediaType; using cricket::SessionDescription; @@ -65,6 +66,7 @@ using cricket::MEDIA_TYPE_DATA; using cricket::SEC_DISABLED; using cricket::SEC_ENABLED; using cricket::SEC_REQUIRED; +using cricket::RtpTransceiverDirection; using rtc::CS_AES_CM_128_HMAC_SHA1_32; using rtc::CS_AES_CM_128_HMAC_SHA1_80; using rtc::CS_AEAD_AES_128_GCM; @@ -93,6 +95,9 @@ static const AudioCodec kAudioCodecsAnswer[] = { static const VideoCodec kVideoCodecs1[] = {VideoCodec(96, "H264-SVC"), VideoCodec(97, "H264")}; +static const VideoCodec kVideoCodecs1Reverse[] = {VideoCodec(97, "H264"), + VideoCodec(96, "H264-SVC")}; + static const VideoCodec kVideoCodecs2[] = {VideoCodec(126, "H264"), VideoCodec(127, "H263")}; @@ -208,6 +213,11 @@ static const char* kMediaProtocolsDtls[] = { "TCP/TLS/RTP/SAVPF", "TCP/TLS/RTP/SAVP", "UDP/TLS/RTP/SAVPF", "UDP/TLS/RTP/SAVP"}; +// These constants are used to make the code using "AddMediaSection" more +// readable. +static constexpr bool kStopped = true; +static constexpr bool kActive = false; + static bool IsMediaContentOfType(const ContentInfo* content, MediaType media_type) { const MediaContentDescription* mdesc = @@ -237,11 +247,95 @@ static std::vector GetCodecNames(const std::vector& codecs) { return codec_names; } +// This is used for test only. MIDs are not the identification of the +// MediaDescriptionOptions since some end points may not support MID and the SDP +// may not contain 'mid'. +std::vector::iterator FindFirstMediaDescriptionByMid( + const std::string& mid, + MediaSessionOptions* opts) { + return std::find_if( + opts->media_description_options.begin(), + opts->media_description_options.end(), + [mid](const MediaDescriptionOptions& t) { return t.mid == mid; }); +} + +// Add a media section to the |session_options|. +static void AddMediaSection(MediaType type, + const std::string& mid, + MediaContentDirection direction, + bool stopped, + MediaSessionOptions* opts) { + opts->media_description_options.push_back(MediaDescriptionOptions( + type, mid, + cricket::RtpTransceiverDirection::FromMediaContentDirection(direction), + stopped)); +} + +static void AddAudioVideoSections(MediaContentDirection direction, + MediaSessionOptions* opts) { + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", direction, kActive, opts); +} + +static void AddDataSection(cricket::DataChannelType dct, + MediaContentDirection direction, + MediaSessionOptions* opts) { + opts->data_channel_type = dct; + AddMediaSection(MEDIA_TYPE_DATA, "data", direction, kActive, opts); +} + +static void AttachSenderToMediaSection(const std::string& mid, + MediaType type, + const std::string& track_id, + const std::string& stream_id, + int num_sim_layer, + MediaSessionOptions* session_options) { + auto it = FindFirstMediaDescriptionByMid(mid, session_options); + switch (type) { + case MEDIA_TYPE_AUDIO: + it->AddAudioSender(track_id, stream_id); + break; + case MEDIA_TYPE_VIDEO: + it->AddVideoSender(track_id, stream_id, num_sim_layer); + break; + case MEDIA_TYPE_DATA: + it->AddRtpDataChannel(track_id, stream_id); + break; + default: + RTC_NOTREACHED(); + } +} + +static void DetachSenderFromMediaSection(const std::string& mid, + const std::string& track_id, + MediaSessionOptions* session_options) { + auto it = FindFirstMediaDescriptionByMid(mid, session_options); + auto sender_it = it->sender_options.begin(); + for (; sender_it != it->sender_options.end(); ++sender_it) { + if (sender_it->track_id == track_id) { + it->sender_options.erase(sender_it); + return; + } + } + RTC_NOTREACHED(); +} + +// Helper function used to create a default MediaSessionOptions for Plan B SDP. +// (https://tools.ietf.org/html/draft-uberti-rtcweb-plan-00). +static MediaSessionOptions CreatePlanBMediaSessionOptions() { + MediaSessionOptions session_options; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &session_options); + return session_options; +} + +// TODO(zhihuang): Most of these tests were written while MediaSessionOptions +// was designed for Plan B SDP, where only one audio "m=" section and one video +// "m=" section could be generated, and ordering couldn't be controlled. Many of +// these tests may be obsolete as a result, and should be refactored or removed. class MediaSessionDescriptionFactoryTest : public testing::Test { public: - MediaSessionDescriptionFactoryTest() - : f1_(&tdf1_), - f2_(&tdf2_) { + MediaSessionDescriptionFactoryTest() : f1_(&tdf1_), f2_(&tdf2_) { f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1), MAKE_VECTOR(kAudioCodecs1)); f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1)); @@ -305,7 +399,8 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { return iter != ice_options.end(); } - void TestTransportInfo(bool offer, const MediaSessionOptions& options, + void TestTransportInfo(bool offer, + MediaSessionOptions& options, bool has_current_desc) { const std::string current_audio_ufrag = "current_audio_ufrag"; const std::string current_audio_pwd = "current_audio_pwd"; @@ -350,7 +445,11 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { EXPECT_EQ(static_cast(cricket::ICE_PWD_LENGTH), ti_audio->description.ice_pwd.size()); } - EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_audio)); + auto media_desc_options_it = + FindFirstMediaDescriptionByMid("audio", &options); + EXPECT_EQ( + media_desc_options_it->transport_options.enable_ice_renomination, + GetIceRenomination(ti_audio)); } else { EXPECT_TRUE(ti_audio == NULL); @@ -374,7 +473,11 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { ti_video->description.ice_pwd.size()); } } - EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_video)); + auto media_desc_options_it = + FindFirstMediaDescriptionByMid("video", &options); + EXPECT_EQ( + media_desc_options_it->transport_options.enable_ice_renomination, + GetIceRenomination(ti_video)); } else { EXPECT_TRUE(ti_video == NULL); } @@ -397,7 +500,11 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { ti_data->description.ice_pwd.size()); } } - EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_data)); + auto media_desc_options_it = + FindFirstMediaDescriptionByMid("data", &options); + EXPECT_EQ( + media_desc_options_it->transport_options.enable_ice_renomination, + GetIceRenomination(ti_data)); } else { EXPECT_TRUE(ti_video == NULL); @@ -407,9 +514,8 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { void TestCryptoWithBundle(bool offer) { f1_.set_secure(SEC_ENABLED); MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); std::unique_ptr ref_desc; std::unique_ptr desc; if (offer) { @@ -455,27 +561,25 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { // This test that the audio and video media direction is set to // |expected_direction_in_answer| in an answer if the offer direction is set - // to |direction_in_offer|. + // to |direction_in_offer| and the answer is willing to both send and receive. void TestMediaDirectionInAnswer( cricket::MediaContentDirection direction_in_offer, cricket::MediaContentDirection expected_direction_in_answer) { - MediaSessionOptions opts; - opts.recv_video = true; - std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); + MediaSessionOptions offer_opts; + AddAudioVideoSections(direction_in_offer, &offer_opts); + + std::unique_ptr offer( + f1_.CreateOffer(offer_opts, NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* ac_offer = offer->GetContentByName("audio"); ASSERT_TRUE(ac_offer != NULL); - AudioContentDescription* acd_offer = - static_cast(ac_offer->description); - acd_offer->set_direction(direction_in_offer); ContentInfo* vc_offer = offer->GetContentByName("video"); ASSERT_TRUE(vc_offer != NULL); - VideoContentDescription* vcd_offer = - static_cast(vc_offer->description); - vcd_offer->set_direction(direction_in_offer); + MediaSessionOptions answer_opts; + AddAudioVideoSections(cricket::MD_SENDRECV, &answer_opts); std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), opts, NULL)); + f2_.CreateAnswer(offer.get(), answer_opts, NULL)); const AudioContentDescription* acd_answer = GetFirstAudioContentDescription(answer.get()); EXPECT_EQ(expected_direction_in_answer, acd_answer->direction()); @@ -499,11 +603,13 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { void TestVideoGcmCipher(bool gcm_offer, bool gcm_answer) { MediaSessionOptions offer_opts; - offer_opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &offer_opts); offer_opts.crypto_options.enable_gcm_crypto_suites = gcm_offer; + MediaSessionOptions answer_opts; - answer_opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &answer_opts); answer_opts.crypto_options.enable_gcm_crypto_suites = gcm_answer; + f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer( @@ -524,7 +630,7 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux if (gcm_offer && gcm_answer) { ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); @@ -533,7 +639,7 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { } EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type()); EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs()); - EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, vcd->first_ssrc()); // no sender is attached EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux if (gcm_offer && gcm_answer) { ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM); @@ -554,7 +660,7 @@ class MediaSessionDescriptionFactoryTest : public testing::Test { TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) { f1_.set_secure(SEC_ENABLED); std::unique_ptr offer( - f1_.CreateOffer(MediaSessionOptions(), NULL)); + f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(offer.get() != NULL); const ContentInfo* ac = offer->GetContentByName("audio"); const ContentInfo* vc = offer->GetContentByName("video"); @@ -565,7 +671,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) { static_cast(ac->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs()); - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached. EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto) EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32); @@ -575,7 +681,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) { // Create a typical video offer, and ensure it matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); @@ -591,14 +697,14 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) { static_cast(vc->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs()); - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto) EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol()); EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type()); EXPECT_EQ(f1_.video_codecs(), vcd->codecs()); - EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, vcd->first_ssrc()); // no sender is attached EXPECT_EQ(kAutoBandwidth, vcd->bandwidth()); // default bandwidth (auto) EXPECT_TRUE(vcd->rtcp_mux()); // rtcp-mux defaults on ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); @@ -616,9 +722,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) { ASSERT_EQ(offered_video_codec.id, offered_data_codec.id); MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; - opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); opts.bundle_enabled = true; std::unique_ptr offer(f2_.CreateOffer(opts, NULL)); const VideoContentDescription* vcd = @@ -638,15 +743,17 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) { EXPECT_EQ(dcd->codecs()[0].name, offered_data_codec.name); } -// Test creating an updated offer with with bundle, audio, video and data +// Test creating an updated offer with bundle, audio, video and data // after an audio only session has been negotiated. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateUpdatedVideoOfferWithBundle) { f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = false; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_INACTIVE, kStopped, + &opts); opts.data_channel_type = cricket::DCT_NONE; opts.bundle_enabled = true; std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); @@ -654,9 +761,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, f2_.CreateAnswer(offer.get(), opts, NULL)); MediaSessionOptions updated_opts; - updated_opts.recv_audio = true; - updated_opts.recv_video = true; - updated_opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &updated_opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &updated_opts); updated_opts.bundle_enabled = true; std::unique_ptr updated_offer( f1_.CreateOffer(updated_opts, answer.get())); @@ -682,7 +788,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, // Create a RTP data offer, and ensure it matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) { MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); @@ -698,14 +805,14 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) { static_cast(dc->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs()); - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attched. EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto) EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol()); EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type()); EXPECT_EQ(f1_.data_codecs(), dcd->codecs()); - EXPECT_NE(0U, dcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, dcd->first_ssrc()); // no sender is attached. EXPECT_EQ(cricket::kDataMaxBandwidth, dcd->bandwidth()); // default bandwidth (auto) EXPECT_TRUE(dcd->rtcp_mux()); // rtcp-mux defaults on @@ -716,9 +823,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) { // Create an SCTP data offer with bundle without error. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) { MediaSessionOptions opts; - opts.recv_audio = false; opts.bundle_enabled = true; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); EXPECT_TRUE(offer.get() != NULL); @@ -728,9 +834,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) { // Test creating an sctp data channel from an already generated offer. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) { MediaSessionOptions opts; - opts.recv_audio = false; opts.bundle_enabled = true; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer1(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer1.get() != NULL); @@ -756,8 +861,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) { TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithoutLegacyStreams) { MediaSessionOptions opts; - opts.recv_video = true; - f1_.set_add_legacy_streams(false); + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); const ContentInfo* ac = offer->GetContentByName("audio"); @@ -775,13 +879,14 @@ TEST_F(MediaSessionDescriptionFactoryTest, // Creates an audio+video sendonly offer. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) { - MediaSessionOptions options; - options.recv_audio = false; - options.recv_video = false; - options.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1); - options.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); + MediaSessionOptions opts; + AddAudioVideoSections(cricket::MD_SENDONLY, &opts); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, 1, &opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &opts); - std::unique_ptr offer(f1_.CreateOffer(options, NULL)); + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); EXPECT_EQ(2u, offer->contents().size()); EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MEDIA_TYPE_AUDIO)); @@ -795,16 +900,15 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) { // SessionDescription is preserved in the new SessionDescription. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) { MediaSessionOptions opts; - opts.recv_audio = false; - opts.recv_video = false; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); std::unique_ptr offer1(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer1.get() != NULL); EXPECT_EQ(1u, offer1->contents().size()); EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA)); - opts.recv_video = true; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer2( f1_.CreateOffer(opts, offer1.get())); ASSERT_TRUE(offer2.get() != NULL); @@ -812,7 +916,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) { EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA)); EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO)); - opts.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer3( f1_.CreateOffer(opts, offer2.get())); ASSERT_TRUE(offer3.get() != NULL); @@ -820,15 +925,6 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) { EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[0], MEDIA_TYPE_DATA)); EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[1], MEDIA_TYPE_VIDEO)); EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[2], MEDIA_TYPE_AUDIO)); - - // Verifies the default order is audio-video-data, so that the previous checks - // didn't pass by accident. - std::unique_ptr offer4(f1_.CreateOffer(opts, NULL)); - ASSERT_TRUE(offer4.get() != NULL); - EXPECT_EQ(3u, offer4->contents().size()); - EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[0], MEDIA_TYPE_AUDIO)); - EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[1], MEDIA_TYPE_VIDEO)); - EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[2], MEDIA_TYPE_DATA)); } // Create a typical audio answer, and ensure it matches what we expect. @@ -836,10 +932,10 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) { f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer( - f1_.CreateOffer(MediaSessionOptions(), NULL)); + f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL)); const ContentInfo* ac = answer->GetContentByName("audio"); const ContentInfo* vc = answer->GetContentByName("video"); ASSERT_TRUE(ac != NULL); @@ -849,7 +945,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) { static_cast(ac->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32); @@ -861,13 +957,12 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) { TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) { f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); - MediaSessionOptions options; - options.crypto_options.enable_gcm_crypto_suites = true; - std::unique_ptr offer( - f1_.CreateOffer(options, NULL)); + MediaSessionOptions opts = CreatePlanBMediaSessionOptions(); + opts.crypto_options.enable_gcm_crypto_suites = true; + std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), options, NULL)); + f2_.CreateAnswer(offer.get(), opts, NULL)); const ContentInfo* ac = answer->GetContentByName("audio"); const ContentInfo* vc = answer->GetContentByName("video"); ASSERT_TRUE(ac != NULL); @@ -877,7 +972,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) { static_cast(ac->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); @@ -887,7 +982,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) { // Create a typical video answer, and ensure it matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); @@ -907,12 +1002,12 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) { EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32); EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type()); EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs()); - EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, vcd->first_ssrc()); // no sender is attached EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol()); @@ -937,8 +1032,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerGcmAnswer) { } TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) { - MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_RTP; + MediaSessionOptions opts = CreatePlanBMediaSessionOptions(); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); @@ -958,20 +1053,20 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) { EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32); EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type()); EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs()); - EXPECT_NE(0U, dcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, dcd->first_ssrc()); // no sender is attached EXPECT_TRUE(dcd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol()); } TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) { - MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_RTP; + MediaSessionOptions opts = CreatePlanBMediaSessionOptions(); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); opts.crypto_options.enable_gcm_crypto_suites = true; f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); @@ -992,12 +1087,12 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) { EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs()); EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw - EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, acd->first_ssrc()); // no sender is attached EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM); EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type()); EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs()); - EXPECT_NE(0U, dcd->first_ssrc()); // a random nonzero ssrc + EXPECT_EQ(0U, dcd->first_ssrc()); // no sender is attached EXPECT_TRUE(dcd->rtcp_mux()); // negotiated rtcp-mux ASSERT_CRYPTO(dcd, 1U, CS_AEAD_AES_256_GCM); EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol()); @@ -1007,7 +1102,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) { // The answer's use_sctpmap flag should match the offer's. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) { MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* dc_offer = offer->GetContentByName("data"); @@ -1028,7 +1123,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) { // The answer's use_sctpmap flag should match the offer's. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) { MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* dc_offer = offer->GetContentByName("data"); @@ -1058,7 +1153,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, tdf2_.set_secure(SEC_ENABLED); MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); ASSERT_TRUE(offer.get() != nullptr); ContentInfo* dc_offer = offer->GetContentByName("data"); @@ -1087,19 +1182,20 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerContentOrder) { MediaSessionOptions opts; // Creates a data only offer. - opts.recv_audio = false; - opts.data_channel_type = cricket::DCT_SCTP; + AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts); std::unique_ptr offer1(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer1.get() != NULL); // Appends audio to the offer. - opts.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer2( f1_.CreateOffer(opts, offer1.get())); ASSERT_TRUE(offer2.get() != NULL); // Appends video to the offer. - opts.recv_video = true; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer3( f1_.CreateOffer(opts, offer2.get())); ASSERT_TRUE(offer3.get() != NULL); @@ -1144,8 +1240,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToInactiveOffer) { TEST_F(MediaSessionDescriptionFactoryTest, CreateDataAnswerToOfferWithUnknownProtocol) { MediaSessionOptions opts; - opts.data_channel_type = cricket::DCT_RTP; - opts.recv_audio = false; + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); @@ -1171,7 +1266,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, // Test that the media protocol is RTP/AVPF if DTLS and SDES are disabled. TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) { - MediaSessionOptions opts; + MediaSessionOptions opts = CreatePlanBMediaSessionOptions(); f1_.set_secure(SEC_DISABLED); f2_.set_secure(SEC_DISABLED); tdf1_.set_secure(SEC_DISABLED); @@ -1200,8 +1295,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) { // matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) { MediaSessionOptions opts; - opts.recv_video = true; - + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1)); f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1)); f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2)); @@ -1229,7 +1323,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) { TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithEncryptedRtpExtensionsBoth) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_enable_encrypted_rtp_header_extensions(true); f2_.set_enable_encrypted_rtp_header_extensions(true); @@ -1265,7 +1359,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithEncryptedRtpExtensionsOffer) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_enable_encrypted_rtp_header_extensions(true); @@ -1300,7 +1394,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithEncryptedRtpExtensionsAnswer) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f2_.set_enable_encrypted_rtp_header_extensions(true); @@ -1336,10 +1430,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerWithoutLegacyStreams) { MediaSessionOptions opts; - opts.recv_video = true; - opts.data_channel_type = cricket::DCT_RTP; - f1_.set_add_legacy_streams(false); - f2_.set_add_legacy_streams(false); + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); std::unique_ptr answer( @@ -1363,8 +1455,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) { MediaSessionOptions opts; - opts.recv_video = true; - opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); @@ -1400,18 +1492,18 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) { // Create a typical video answer, and ensure it matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) { MediaSessionOptions offer_opts; + AddAudioVideoSections(cricket::MD_SENDRECV, &offer_opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &offer_opts); + MediaSessionOptions answer_opts; - answer_opts.recv_video = true; - offer_opts.recv_video = true; - answer_opts.data_channel_type = cricket::DCT_RTP; - offer_opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_SENDRECV, &answer_opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &answer_opts); std::unique_ptr offer; std::unique_ptr answer; offer_opts.rtcp_mux_enabled = true; answer_opts.rtcp_mux_enabled = true; - offer.reset(f1_.CreateOffer(offer_opts, NULL)); answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL)); ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get())); @@ -1429,7 +1521,6 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) { offer_opts.rtcp_mux_enabled = true; answer_opts.rtcp_mux_enabled = false; - offer.reset(f1_.CreateOffer(offer_opts, NULL)); answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL)); ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get())); @@ -1447,7 +1538,6 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) { offer_opts.rtcp_mux_enabled = false; answer_opts.rtcp_mux_enabled = true; - offer.reset(f1_.CreateOffer(offer_opts, NULL)); answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL)); ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get())); @@ -1465,7 +1555,6 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) { offer_opts.rtcp_mux_enabled = false; answer_opts.rtcp_mux_enabled = false; - offer.reset(f1_.CreateOffer(offer_opts, NULL)); answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL)); ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get())); @@ -1485,11 +1574,16 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) { // Create an audio-only answer to a video offer. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) { MediaSessionOptions opts; - opts.recv_video = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); + + opts.media_description_options[1].stopped = true; std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + f2_.CreateAnswer(offer.get(), opts, NULL)); const ContentInfo* ac = answer->GetContentByName("audio"); const ContentInfo* vc = answer->GetContentByName("video"); ASSERT_TRUE(ac != NULL); @@ -1500,12 +1594,16 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) { // Create an audio-only answer to an offer with data. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) { - MediaSessionOptions opts; + MediaSessionOptions opts = CreatePlanBMediaSessionOptions(); opts.data_channel_type = cricket::DCT_RTP; + AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); + + opts.media_description_options[1].stopped = true; std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + f2_.CreateAnswer(offer.get(), opts, NULL)); const ContentInfo* ac = answer->GetContentByName("audio"); const ContentInfo* dc = answer->GetContentByName("data"); ASSERT_TRUE(ac != NULL); @@ -1518,8 +1616,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) { TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToOfferWithRejectedMedia) { MediaSessionOptions opts; - opts.recv_video = true; - opts.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* ac = offer->GetContentByName("audio"); @@ -1552,12 +1650,19 @@ TEST_F(MediaSessionDescriptionFactoryTest, // adding a new video track and replaces one of the audio tracks. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) { MediaSessionOptions opts; - opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1); - opts.data_channel_type = cricket::DCT_RTP; - opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1); + AddAudioVideoSections(cricket::MD_SENDRECV, &opts); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, 1, &opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack2, + kMediaStream1, 1, &opts); + + AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &opts); + AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1, + kMediaStream1, 1, &opts); + AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2, + kMediaStream1, 1, &opts); f1_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); @@ -1622,14 +1727,16 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) { EXPECT_TRUE(dcd->rtcp_mux()); // rtcp-mux defaults on ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80); - // Update the offer. Add a new video track that is not synched to the // other tracks and replace audio track 2 with audio track 3. - opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2); - opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2); - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1); - opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2); - opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack3, kMediaStream1); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack2, + kMediaStream2, 1, &opts); + DetachSenderFromMediaSection("audio", kAudioTrack2, &opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack3, + kMediaStream1, 1, &opts); + DetachSenderFromMediaSection("data", kDataTrack2, &opts); + AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack3, + kMediaStream1, 1, &opts); std::unique_ptr updated_offer( f1_.CreateOffer(opts, offer.get())); @@ -1691,8 +1798,13 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) { // Create an offer with simulcast video stream. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) { MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &opts); const int num_sim_layers = 3; - opts.AddSendVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, num_sim_layers, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); @@ -1718,22 +1830,39 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) { // adding a new video track and removes one of the audio tracks. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) { MediaSessionOptions offer_opts; - offer_opts.recv_video = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &offer_opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &offer_opts); offer_opts.data_channel_type = cricket::DCT_RTP; + AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_RECVONLY, kActive, + &offer_opts); f1_.set_secure(SEC_ENABLED); f2_.set_secure(SEC_ENABLED); std::unique_ptr offer(f1_.CreateOffer(offer_opts, NULL)); - MediaSessionOptions opts; - opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1); - opts.data_channel_type = cricket::DCT_RTP; - opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1); - opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1); + MediaSessionOptions answer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_SENDRECV, kActive, + &answer_opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &answer_opts); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, 1, &answer_opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &answer_opts); + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack2, + kMediaStream1, 1, &answer_opts); + + AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_SENDRECV, kActive, + &answer_opts); + AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1, + kMediaStream1, 1, &answer_opts); + AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2, + kMediaStream1, 1, &answer_opts); + answer_opts.data_channel_type = cricket::DCT_RTP; std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), opts, NULL)); + f2_.CreateAnswer(offer.get(), answer_opts, NULL)); ASSERT_TRUE(answer.get() != NULL); const ContentInfo* ac = answer->GetContentByName("audio"); @@ -1797,11 +1926,12 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) { // Update the answer. Add a new video track that is not synched to the // other tracks and remove 1 audio track. - opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2); - opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2); - opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack2, + kMediaStream2, 1, &answer_opts); + DetachSenderFromMediaSection("audio", kAudioTrack2, &answer_opts); + DetachSenderFromMediaSection("data", kDataTrack2, &answer_opts); std::unique_ptr updated_answer( - f2_.CreateAnswer(offer.get(), opts, answer.get())); + f2_.CreateAnswer(offer.get(), answer_opts, answer.get())); ASSERT_TRUE(updated_answer.get() != NULL); ac = updated_answer->GetContentByName("audio"); @@ -1853,8 +1983,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) { TEST_F(MediaSessionDescriptionFactoryTest, RespondentCreatesOfferAfterCreatingAnswer) { MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); std::unique_ptr answer( @@ -1905,8 +2034,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, RespondentCreatesOfferAfterCreatingAnswerWithRtx) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::vector f1_codecs = MAKE_VECTOR(kVideoCodecs1); // This creates rtx for H264 with the payload type |f1_| uses. AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs); @@ -1958,8 +2087,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, f1_.set_video_codecs(f1_codecs); MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = false; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &opts); std::unique_ptr offer(f1_.CreateOffer(opts, NULL)); std::unique_ptr answer( @@ -1972,8 +2101,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, // Now - let |f2_| add video with RTX and let the payload type the RTX codec // reference be the same as an audio codec that was negotiated in the // first offer/answer exchange. - opts.recv_audio = true; - opts.recv_video = true; + opts.media_description_options.clear(); + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); std::vector f2_codecs = MAKE_VECTOR(kVideoCodecs2); int used_pl_type = acd->codecs()[0].id; @@ -2010,8 +2139,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TEST_F(MediaSessionDescriptionFactoryTest, RespondentCreatesOfferWithRtxAfterCreatingAnswerWithoutRtx) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); std::vector f2_codecs = MAKE_VECTOR(kVideoCodecs2); // This creates rtx for H264 with the payload type |f2_| uses. @@ -2049,8 +2177,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, // Test that RTX is ignored when there is no associated payload type parameter. TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::vector f1_codecs = MAKE_VECTOR(kVideoCodecs1); // This creates RTX without associated payload type parameter. AddRtxCodec(VideoCodec(126, cricket::kRtxCodecName), &f1_codecs); @@ -2093,8 +2221,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) { // type doesn't match the local value. TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::vector f1_codecs = MAKE_VECTOR(kVideoCodecs1); // This creates RTX for H264 in sender. AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs); @@ -2123,8 +2251,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) { TEST_F(MediaSessionDescriptionFactoryTest, FilterOutUnsupportedRtxWhenCreatingAnswer) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::vector f1_codecs = MAKE_VECTOR(kVideoCodecs1); // This creates RTX for H264-SVC in sender. AddRtxCodec(VideoCodec::CreateRtxCodec(125, kVideoCodecs1[0].id), &f1_codecs); @@ -2158,8 +2286,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, // to add another. TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive, + &opts); std::vector f1_codecs = MAKE_VECTOR(kVideoCodecs1); // This creates RTX for H264 for the offerer. AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs); @@ -2193,11 +2321,11 @@ TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) { // generated for each simulcast ssrc and correctly grouped. TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; - + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &opts); // Add simulcast streams. - opts.AddSendVideoStream("stream1", "stream1label", 3); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1", + "stream1label", 3, &opts); // Use a single real codec, and then add RTX for it. std::vector f1_codecs; @@ -2234,11 +2362,11 @@ TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) { // together with a FEC-FR grouping. TEST_F(MediaSessionDescriptionFactoryTest, GenerateFlexfecSsrc) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; - + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &opts); // Add single stream. - opts.AddSendVideoStream("stream1", "stream1label", 1); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1", + "stream1label", 1, &opts); // Use a single real codec, and then add FlexFEC for it. std::vector f1_codecs; @@ -2274,11 +2402,11 @@ TEST_F(MediaSessionDescriptionFactoryTest, GenerateFlexfecSsrc) { // multiple FlexfecSenders, or through multistream protection. TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateNoFlexfecSsrcs) { MediaSessionOptions opts; - opts.recv_video = true; - opts.recv_audio = false; - + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &opts); // Add simulcast streams. - opts.AddSendVideoStream("stream1", "stream1label", 3); + AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1", + "stream1label", 3, &opts); // Use a single real codec, and then add FlexFEC for it. std::vector f1_codecs; @@ -2318,8 +2446,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateNoFlexfecSsrcs) { TEST_F(MediaSessionDescriptionFactoryTest, RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) { MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1)); f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1)); @@ -2373,8 +2500,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, // updated offer (this was previously a bug). TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) { MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension3)); f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension3)); @@ -2409,8 +2535,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) { // Same as "RtpExtensionIdReused" above for encrypted RTP extensions. TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReusedEncrypted) { MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); f1_.set_enable_encrypted_rtp_header_extensions(true); f2_.set_enable_encrypted_rtp_header_extensions(true); @@ -2486,45 +2611,47 @@ TEST(MediaSessionDescription, CopySessionDescription) { // ensure the TransportInfo in the SessionDescription matches what we expect. TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) { MediaSessionOptions options; - options.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); TestTransportInfo(true, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferIceRenomination) { MediaSessionOptions options; - options.enable_ice_renomination = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); + options.media_description_options[0] + .transport_options.enable_ice_renomination = true; TestTransportInfo(true, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) { MediaSessionOptions options; - options.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); TestTransportInfo(true, options, true); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); TestTransportInfo(true, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimediaCurrent) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); TestTransportInfo(true, options, true); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); options.bundle_enabled = true; TestTransportInfo(true, options, false); } @@ -2532,55 +2659,56 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) { TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundleCurrent) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); options.bundle_enabled = true; TestTransportInfo(true, options, true); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) { MediaSessionOptions options; - options.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); TestTransportInfo(false, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerIceRenomination) { MediaSessionOptions options; - options.enable_ice_renomination = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); + options.media_description_options[0] + .transport_options.enable_ice_renomination = true; TestTransportInfo(false, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudioCurrent) { MediaSessionOptions options; - options.recv_audio = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive, + &options); TestTransportInfo(false, options, true); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); TestTransportInfo(false, options, false); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimediaCurrent) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); TestTransportInfo(false, options, true); } TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); options.bundle_enabled = true; TestTransportInfo(false, options, false); } @@ -2588,9 +2716,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) { TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundleCurrent) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); options.bundle_enabled = true; TestTransportInfo(false, options, true); } @@ -2617,7 +2744,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, tdf2_.set_secure(SEC_DISABLED); std::unique_ptr offer( - f1_.CreateOffer(MediaSessionOptions(), NULL)); + f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* offer_content = offer->GetContentByName("audio"); ASSERT_TRUE(offer_content != NULL); @@ -2626,7 +2753,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf); std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(answer != NULL); ContentInfo* answer_content = answer->GetContentByName("audio"); ASSERT_TRUE(answer_content != NULL); @@ -2643,7 +2770,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestOfferDtlsSavpfCreateAnswer) { tdf2_.set_secure(SEC_ENABLED); std::unique_ptr offer( - f1_.CreateOffer(MediaSessionOptions(), NULL)); + f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(offer.get() != NULL); ContentInfo* offer_content = offer->GetContentByName("audio"); ASSERT_TRUE(offer_content != NULL); @@ -2652,7 +2779,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestOfferDtlsSavpfCreateAnswer) { offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf); std::unique_ptr answer( - f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL)); + f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL)); ASSERT_TRUE(answer != NULL); const ContentInfo* answer_content = answer->GetContentByName("audio"); @@ -2673,8 +2800,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoDtls) { tdf1_.set_secure(SEC_ENABLED); tdf2_.set_secure(SEC_DISABLED); MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); std::unique_ptr offer, answer; const cricket::MediaContentDescription* audio_media_desc; const cricket::MediaContentDescription* video_media_desc; @@ -2770,7 +2896,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoDtls) { // Test that an answer can't be created if cryptos are required but the offer is // unsecure. TEST_F(MediaSessionDescriptionFactoryTest, TestSecureAnswerToUnsecureOffer) { - MediaSessionOptions options; + MediaSessionOptions options = CreatePlanBMediaSessionOptions(); f1_.set_secure(SEC_DISABLED); tdf1_.set_secure(SEC_DISABLED); f2_.set_secure(SEC_REQUIRED); @@ -2791,9 +2917,8 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoOfferDtlsButNotSdes) { tdf1_.set_secure(SEC_ENABLED); tdf2_.set_secure(SEC_ENABLED); MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; - options.data_channel_type = cricket::DCT_RTP; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); + AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options); std::unique_ptr offer, answer; @@ -2840,8 +2965,7 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoOfferDtlsButNotSdes) { // offer or answer. TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) { MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &options); std::unique_ptr offer(f1_.CreateOffer(options, NULL)); ASSERT_TRUE(offer.get() != NULL); const ContentInfo* audio_content = offer->GetContentByName("audio"); @@ -2859,22 +2983,21 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) { EXPECT_TRUE(VerifyNoCNCodecs(audio_content)); } -// Test that the content name ("mid" in SDP) is unchanged when creating a -// new offer. -TEST_F(MediaSessionDescriptionFactoryTest, - TestContentNameNotChangedInSubsequentOffers) { +// Test that the generated MIDs match the existing offer. +TEST_F(MediaSessionDescriptionFactoryTest, TestMIDsMatchesExistingOffer) { MediaSessionOptions opts; - opts.recv_audio = true; - opts.recv_video = true; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio_modified", cricket::MD_RECVONLY, + kActive, &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video_modified", cricket::MD_RECVONLY, + kActive, &opts); opts.data_channel_type = cricket::DCT_SCTP; - // Create offer and modify the default content names. + AddMediaSection(MEDIA_TYPE_DATA, "data_modified", cricket::MD_SENDRECV, + kActive, &opts); + // Create offer. std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); - for (ContentInfo& content : offer->contents()) { - content.name.append("_modified"); - } - std::unique_ptr updated_offer( f1_.CreateOffer(opts, offer.get())); + const ContentInfo* audio_content = GetFirstAudioContent(updated_offer.get()); const ContentInfo* video_content = GetFirstVideoContent(updated_offer.get()); const ContentInfo* data_content = GetFirstDataContent(updated_offer.get()); @@ -2886,6 +3009,340 @@ TEST_F(MediaSessionDescriptionFactoryTest, EXPECT_EQ("data_modified", data_content->name); } +// The following tests verify that the unified plan SDP is supported. +// Test that we can create an offer with multiple media sections of same media +// type. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateOfferWithMultipleAVMediaSections) { + MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &opts); + + AddMediaSection(MEDIA_TYPE_VIDEO, "video_1", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, 1, &opts); + + AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2, + kMediaStream2, 1, &opts); + + AddMediaSection(MEDIA_TYPE_VIDEO, "video_2", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2, + kMediaStream2, 1, &opts); + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer); + + ASSERT_EQ(4u, offer->contents().size()); + EXPECT_FALSE(offer->contents()[0].rejected); + const AudioContentDescription* acd = + static_cast( + offer->contents()[0].description); + ASSERT_EQ(1u, acd->streams().size()); + EXPECT_EQ(kAudioTrack1, acd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, acd->direction()); + + EXPECT_FALSE(offer->contents()[1].rejected); + const VideoContentDescription* vcd = + static_cast( + offer->contents()[1].description); + ASSERT_EQ(1u, vcd->streams().size()); + EXPECT_EQ(kVideoTrack1, vcd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction()); + + EXPECT_FALSE(offer->contents()[2].rejected); + acd = static_cast( + offer->contents()[2].description); + ASSERT_EQ(1u, acd->streams().size()); + EXPECT_EQ(kAudioTrack2, acd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, acd->direction()); + + EXPECT_FALSE(offer->contents()[3].rejected); + vcd = static_cast( + offer->contents()[3].description); + ASSERT_EQ(1u, vcd->streams().size()); + EXPECT_EQ(kVideoTrack2, vcd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction()); +} + +// Test that we can create an answer with multiple media sections of same media +// type. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateAnswerWithMultipleAVMediaSections) { + MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &opts); + + AddMediaSection(MEDIA_TYPE_VIDEO, "video_1", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1, + kMediaStream1, 1, &opts); + + AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2, + kMediaStream2, 1, &opts); + + AddMediaSection(MEDIA_TYPE_VIDEO, "video_2", cricket::MD_SENDRECV, kActive, + &opts); + AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2, + kMediaStream2, 1, &opts); + + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, nullptr)); + + ASSERT_EQ(4u, answer->contents().size()); + EXPECT_FALSE(answer->contents()[0].rejected); + const AudioContentDescription* acd = + static_cast( + answer->contents()[0].description); + ASSERT_EQ(1u, acd->streams().size()); + EXPECT_EQ(kAudioTrack1, acd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, acd->direction()); + + EXPECT_FALSE(answer->contents()[1].rejected); + const VideoContentDescription* vcd = + static_cast( + answer->contents()[1].description); + ASSERT_EQ(1u, vcd->streams().size()); + EXPECT_EQ(kVideoTrack1, vcd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction()); + + EXPECT_FALSE(answer->contents()[2].rejected); + acd = static_cast( + answer->contents()[2].description); + ASSERT_EQ(1u, acd->streams().size()); + EXPECT_EQ(kAudioTrack2, acd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, acd->direction()); + + EXPECT_FALSE(answer->contents()[3].rejected); + vcd = static_cast( + answer->contents()[3].description); + ASSERT_EQ(1u, vcd->streams().size()); + EXPECT_EQ(kVideoTrack2, vcd->streams()[0].id); + EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction()); +} + +// Test that the media section will be rejected in offer if the corresponding +// MediaDescriptionOptions is stopped by the offerer. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateOfferWithMediaSectionStoppedByOfferer) { + // Create an offer with two audio sections and one of them is stopped. + MediaSessionOptions offer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive, + &offer_opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped, + &offer_opts); + std::unique_ptr offer( + f1_.CreateOffer(offer_opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + EXPECT_FALSE(offer->contents()[0].rejected); + EXPECT_TRUE(offer->contents()[1].rejected); +} + +// Test that the media section will be rejected in answer if the corresponding +// MediaDescriptionOptions is stopped by the offerer. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateAnswerWithMediaSectionStoppedByOfferer) { + // Create an offer with two audio sections and one of them is stopped. + MediaSessionOptions offer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive, + &offer_opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped, + &offer_opts); + std::unique_ptr offer( + f1_.CreateOffer(offer_opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + EXPECT_FALSE(offer->contents()[0].rejected); + EXPECT_TRUE(offer->contents()[1].rejected); + + // Create an answer based on the offer. + MediaSessionOptions answer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive, + &answer_opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_SENDRECV, kActive, + &answer_opts); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), answer_opts, nullptr)); + ASSERT_EQ(2u, answer->contents().size()); + EXPECT_FALSE(answer->contents()[0].rejected); + EXPECT_TRUE(answer->contents()[1].rejected); +} + +// Test that the media section will be rejected in answer if the corresponding +// MediaDescriptionOptions is stopped by the answerer. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateAnswerWithMediaSectionRejectedByAnswerer) { + // Create an offer with two audio sections. + MediaSessionOptions offer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive, + &offer_opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_SENDRECV, kActive, + &offer_opts); + std::unique_ptr offer( + f1_.CreateOffer(offer_opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + ASSERT_FALSE(offer->contents()[0].rejected); + ASSERT_FALSE(offer->contents()[1].rejected); + + // The answerer rejects one of the audio sections. + MediaSessionOptions answer_opts; + AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive, + &answer_opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped, + &answer_opts); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), answer_opts, nullptr)); + ASSERT_EQ(2u, answer->contents().size()); + EXPECT_FALSE(answer->contents()[0].rejected); + EXPECT_TRUE(answer->contents()[1].rejected); +} + +// Test the generated media sections has the same order of the +// corresponding MediaDescriptionOptions. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateOfferRespectsMediaDescriptionOptionsOrder) { + MediaSessionOptions opts; + // This tests put video section first because normally audio comes first by + // default. + AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_SENDRECV, kActive, + &opts); + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + EXPECT_EQ("video", offer->contents()[0].name); + EXPECT_EQ("audio", offer->contents()[1].name); +} + +// Test that different media sections using the same codec have same payload +// type. +TEST_F(MediaSessionDescriptionFactoryTest, + PayloadTypesSharedByMediaSectionsOfSameType) { + MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive, + &opts); + // Create an offer with two video sections using same codecs. + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + const VideoContentDescription* vcd1 = + static_cast( + offer->contents()[0].description); + const VideoContentDescription* vcd2 = + static_cast( + offer->contents()[1].description); + EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size()); + ASSERT_EQ(2u, vcd1->codecs().size()); + EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name); + EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id); + EXPECT_EQ(vcd1->codecs()[1].name, vcd2->codecs()[1].name); + EXPECT_EQ(vcd1->codecs()[1].id, vcd2->codecs()[1].id); + + // Create answer and negotiate the codecs. + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, nullptr)); + ASSERT_TRUE(answer); + ASSERT_EQ(2u, answer->contents().size()); + vcd1 = static_cast( + answer->contents()[0].description); + vcd2 = static_cast( + answer->contents()[1].description); + EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size()); + ASSERT_EQ(1u, vcd1->codecs().size()); + EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name); + EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id); +} + +// Test that the codec preference order per media section is respected in +// subsequent offer. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateOfferRespectsCodecPreferenceOrder) { + MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive, + &opts); + // Create an offer with two video sections using same codecs. + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + VideoContentDescription* vcd1 = + static_cast(offer->contents()[0].description); + const VideoContentDescription* vcd2 = + static_cast( + offer->contents()[1].description); + auto video_codecs = MAKE_VECTOR(kVideoCodecs1); + EXPECT_EQ(video_codecs, vcd1->codecs()); + EXPECT_EQ(video_codecs, vcd2->codecs()); + + // Change the codec preference of the first video section and create a + // follow-up offer. + auto video_codecs_reverse = MAKE_VECTOR(kVideoCodecs1Reverse); + vcd1->set_codecs(video_codecs_reverse); + std::unique_ptr updated_offer( + f1_.CreateOffer(opts, offer.get())); + vcd1 = static_cast( + updated_offer->contents()[0].description); + vcd2 = static_cast( + updated_offer->contents()[1].description); + // The video codec preference order should be respected. + EXPECT_EQ(video_codecs_reverse, vcd1->codecs()); + EXPECT_EQ(video_codecs, vcd2->codecs()); +} + +// Test that the codec preference order per media section is respected in +// the answer. +TEST_F(MediaSessionDescriptionFactoryTest, + CreateAnswerRespectsCodecPreferenceOrder) { + MediaSessionOptions opts; + AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive, + &opts); + AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive, + &opts); + // Create an offer with two video sections using same codecs. + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer); + ASSERT_EQ(2u, offer->contents().size()); + VideoContentDescription* vcd1 = + static_cast(offer->contents()[0].description); + const VideoContentDescription* vcd2 = + static_cast( + offer->contents()[1].description); + auto video_codecs = MAKE_VECTOR(kVideoCodecs1); + EXPECT_EQ(video_codecs, vcd1->codecs()); + EXPECT_EQ(video_codecs, vcd2->codecs()); + + // Change the codec preference of the first video section and create an + // answer. + auto video_codecs_reverse = MAKE_VECTOR(kVideoCodecs1Reverse); + vcd1->set_codecs(video_codecs_reverse); + std::unique_ptr answer( + f1_.CreateAnswer(offer.get(), opts, nullptr)); + vcd1 = + static_cast(answer->contents()[0].description); + vcd2 = static_cast( + answer->contents()[1].description); + // The video codec preference order should be respected. + EXPECT_EQ(video_codecs_reverse, vcd1->codecs()); + EXPECT_EQ(video_codecs, vcd2->codecs()); +} + class MediaProtocolTest : public ::testing::TestWithParam { public: MediaProtocolTest() : f1_(&tdf1_), f2_(&tdf2_) { @@ -2916,7 +3373,7 @@ class MediaProtocolTest : public ::testing::TestWithParam { TEST_P(MediaProtocolTest, TestAudioVideoAcceptance) { MediaSessionOptions opts; - opts.recv_video = true; + AddAudioVideoSections(cricket::MD_RECVONLY, &opts); std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); ASSERT_TRUE(offer.get() != nullptr); // Set the protocol for all the contents. @@ -2976,32 +3433,47 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestSetAudioCodecs) { // Test proper merge sf.set_audio_codecs(send_codecs, recv_codecs); - EXPECT_TRUE(sf.audio_send_codecs() == send_codecs); - EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs); - EXPECT_TRUE(sf.audio_sendrecv_codecs() == sendrecv_codecs); + EXPECT_EQ(send_codecs, sf.audio_send_codecs()); + EXPECT_EQ(recv_codecs, sf.audio_recv_codecs()); + EXPECT_EQ(sendrecv_codecs, sf.audio_sendrecv_codecs()); // Test empty send codecs list sf.set_audio_codecs(no_codecs, recv_codecs); - EXPECT_TRUE(sf.audio_send_codecs() == no_codecs); - EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs); - EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs); + EXPECT_EQ(no_codecs, sf.audio_send_codecs()); + EXPECT_EQ(recv_codecs, sf.audio_recv_codecs()); + EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs()); // Test empty recv codecs list sf.set_audio_codecs(send_codecs, no_codecs); - EXPECT_TRUE(sf.audio_send_codecs() == send_codecs); - EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs); - EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs); + EXPECT_EQ(send_codecs, sf.audio_send_codecs()); + EXPECT_EQ(no_codecs, sf.audio_recv_codecs()); + EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs()); // Test all empty codec lists sf.set_audio_codecs(no_codecs, no_codecs); - EXPECT_TRUE(sf.audio_send_codecs() == no_codecs); - EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs); - EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs); + EXPECT_EQ(no_codecs, sf.audio_send_codecs()); + EXPECT_EQ(no_codecs, sf.audio_recv_codecs()); + EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs()); } namespace { -void TestAudioCodecsOffer(MediaContentDirection direction, - bool add_legacy_stream) { +// Compare the two vectors of codecs ignoring the payload type. +template +bool CodecsMatch(const std::vector& codecs1, + const std::vector& codecs2) { + if (codecs1.size() != codecs2.size()) { + return false; + } + + for (size_t i = 0; i < codecs1.size(); ++i) { + if (!codecs1[i].Matches(codecs2[i])) { + return false; + } + } + return true; +} + +void TestAudioCodecsOffer(MediaContentDirection direction) { TransportDescriptionFactory tdf; MediaSessionDescriptionFactory sf(&tdf); const std::vector send_codecs = MAKE_VECTOR(kAudioCodecs1); @@ -3009,57 +3481,56 @@ void TestAudioCodecsOffer(MediaContentDirection direction, const std::vector sendrecv_codecs = MAKE_VECTOR(kAudioCodecsAnswer); sf.set_audio_codecs(send_codecs, recv_codecs); - sf.set_add_legacy_streams(add_legacy_stream); MediaSessionOptions opts; - opts.recv_audio = (direction == cricket::MD_RECVONLY || - direction == cricket::MD_SENDRECV); - opts.recv_video = false; - if (direction == cricket::MD_SENDONLY || direction == cricket::MD_SENDRECV) - opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, &opts); + + if (RtpTransceiverDirection::FromMediaContentDirection(direction).send) { + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &opts); + } std::unique_ptr offer(sf.CreateOffer(opts, NULL)); ASSERT_TRUE(offer.get() != NULL); const ContentInfo* ac = offer->GetContentByName("audio"); // If the factory didn't add any audio content to the offer, we cannot check - // that the codecs put in are right. This happens when we neither want to send - // nor receive audio. The checks are still in place if at some point we'd - // instead create an inactive stream. + // that the codecs put in are right. This happens when we neither want to + // send nor receive audio. The checks are still in place if at some point + // we'd instead create an inactive stream. if (ac) { AudioContentDescription* acd = static_cast(ac->description); - // sendrecv and inactive should both present lists as if the channel was to - // be used for sending and receiving. Inactive essentially means it might - // eventually be used anything, but we don't know more at this moment. + // sendrecv and inactive should both present lists as if the channel was + // to be used for sending and receiving. Inactive essentially means it + // might eventually be used anything, but we don't know more at this + // moment. if (acd->direction() == cricket::MD_SENDONLY) { - EXPECT_TRUE(acd->codecs() == send_codecs); + EXPECT_TRUE(CodecsMatch(send_codecs, acd->codecs())); } else if (acd->direction() == cricket::MD_RECVONLY) { - EXPECT_TRUE(acd->codecs() == recv_codecs); + EXPECT_TRUE(CodecsMatch(recv_codecs, acd->codecs())); } else { - EXPECT_TRUE(acd->codecs() == sendrecv_codecs); + EXPECT_TRUE(CodecsMatch(sendrecv_codecs, acd->codecs())); } } } static const AudioCodec kOfferAnswerCodecs[] = { - AudioCodec(0, "codec0", 16000, -1, 1), - AudioCodec(1, "codec1", 8000, 13300, 1), - AudioCodec(2, "codec2", 8000, 64000, 1), - AudioCodec(3, "codec3", 8000, 64000, 1), - AudioCodec(4, "codec4", 8000, 0, 2), - AudioCodec(5, "codec5", 32000, 0, 1), - AudioCodec(6, "codec6", 48000, 0, 1) -}; + AudioCodec(0, "codec0", 16000, -1, 1), + AudioCodec(1, "codec1", 8000, 13300, 1), + AudioCodec(2, "codec2", 8000, 64000, 1), + AudioCodec(3, "codec3", 8000, 64000, 1), + AudioCodec(4, "codec4", 8000, 0, 2), + AudioCodec(5, "codec5", 32000, 0, 1), + AudioCodec(6, "codec6", 48000, 0, 1)}; - -/* The codecs groups below are chosen as per the matrix below. The objective is - * to have different sets of codecs in the inputs, to get unique sets of codecs - * after negotiation, depending on offer and answer communication directions. - * One-way directions in the offer should either result in the opposite - * direction in the answer, or an inactive answer. Regardless, the choice of - * codecs should be as if the answer contained the opposite direction. - * Inactive offers should be treated as sendrecv/sendrecv. +/* The codecs groups below are chosen as per the matrix below. The objective + * is to have different sets of codecs in the inputs, to get unique sets of + * codecs after negotiation, depending on offer and answer communication + * directions. One-way directions in the offer should either result in the + * opposite direction in the answer, or an inactive answer. Regardless, the + * choice of codecs should be as if the answer contained the opposite + * direction. Inactive offers should be treated as sendrecv/sendrecv. * * | Offer | Answer | Result * codec|send recv sr | send recv sr | s/r r/s sr/s sr/r sr/sr @@ -3072,18 +3543,18 @@ static const AudioCodec kOfferAnswerCodecs[] = { * 6 | x x x | x x x | x x x x x */ // Codecs used by offerer in the AudioCodecsAnswerTest -static const int kOfferSendCodecs[] = { 0, 1, 3, 5, 6 }; -static const int kOfferRecvCodecs[] = { 1, 2, 3, 4, 6 }; +static const int kOfferSendCodecs[] = {0, 1, 3, 5, 6}; +static const int kOfferRecvCodecs[] = {1, 2, 3, 4, 6}; // Codecs used in the answerer in the AudioCodecsAnswerTest. The order is // jumbled to catch the answer not following the order in the offer. -static const int kAnswerSendCodecs[] = { 6, 5, 2, 3, 4 }; -static const int kAnswerRecvCodecs[] = { 6, 5, 4, 1, 0 }; +static const int kAnswerSendCodecs[] = {6, 5, 2, 3, 4}; +static const int kAnswerRecvCodecs[] = {6, 5, 4, 1, 0}; // The resulting sets of codecs in the answer in the AudioCodecsAnswerTest -static const int kResultSend_RecvCodecs[] = { 0, 1, 5, 6 }; -static const int kResultRecv_SendCodecs[] = { 2, 3, 4, 6 }; -static const int kResultSendrecv_SendCodecs[] = { 3, 6 }; -static const int kResultSendrecv_RecvCodecs[] = { 1, 6 }; -static const int kResultSendrecv_SendrecvCodecs[] = { 6 }; +static const int kResultSend_RecvCodecs[] = {0, 1, 5, 6}; +static const int kResultRecv_SendCodecs[] = {2, 3, 4, 6}; +static const int kResultSendrecv_SendCodecs[] = {3, 6}; +static const int kResultSendrecv_RecvCodecs[] = {1, 6}; +static const int kResultSendrecv_SendrecvCodecs[] = {6}; template std::vector VectorFromIndices(const T* array, const int (&indices)[IDXS]) { @@ -3109,17 +3580,14 @@ void TestAudioCodecsAnswer(MediaContentDirection offer_direction, VectorFromIndices(kOfferAnswerCodecs, kAnswerSendCodecs), VectorFromIndices(kOfferAnswerCodecs, kAnswerRecvCodecs)); - // Never add a legacy stream to offer - we want to control the offer - // parameters exactly. - offer_factory.set_add_legacy_streams(false); - answer_factory.set_add_legacy_streams(add_legacy_stream); MediaSessionOptions offer_opts; - offer_opts.recv_audio = (offer_direction == cricket::MD_RECVONLY || - offer_direction == cricket::MD_SENDRECV); - offer_opts.recv_video = false; - if (offer_direction == cricket::MD_SENDONLY || - offer_direction == cricket::MD_SENDRECV) { - offer_opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", offer_direction, kActive, + &offer_opts); + + if (RtpTransceiverDirection::FromMediaContentDirection(offer_direction) + .send) { + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &offer_opts); } std::unique_ptr offer( @@ -3127,27 +3595,27 @@ void TestAudioCodecsAnswer(MediaContentDirection offer_direction, ASSERT_TRUE(offer.get() != NULL); MediaSessionOptions answer_opts; - answer_opts.recv_audio = (answer_direction == cricket::MD_RECVONLY || - answer_direction == cricket::MD_SENDRECV); - answer_opts.recv_video = false; - if (answer_direction == cricket::MD_SENDONLY || - answer_direction == cricket::MD_SENDRECV) { - answer_opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1); + AddMediaSection(MEDIA_TYPE_AUDIO, "audio", answer_direction, kActive, + &answer_opts); + + if (RtpTransceiverDirection::FromMediaContentDirection(answer_direction) + .send) { + AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1, + kMediaStream1, 1, &answer_opts); } std::unique_ptr answer( answer_factory.CreateAnswer(offer.get(), answer_opts, NULL)); const ContentInfo* ac = answer->GetContentByName("audio"); - // If the factory didn't add any audio content to the answer, we cannot check - // that the codecs put in are right. This happens when we neither want to send - // nor receive audio. The checks are still in place if at some point we'd - // instead create an inactive stream. + // If the factory didn't add any audio content to the answer, we cannot + // check that the codecs put in are right. This happens when we neither want + // to send nor receive audio. The checks are still in place if at some point + // we'd instead create an inactive stream. if (ac) { const AudioContentDescription* acd = static_cast(ac->description); EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type()); - std::vector target_codecs; // For offers with sendrecv or inactive, we should never reply with more // codecs than offered, with these codec sets. @@ -3157,20 +3625,20 @@ void TestAudioCodecsAnswer(MediaContentDirection offer_direction, kResultSendrecv_SendrecvCodecs); break; case cricket::MD_SENDONLY: - target_codecs = VectorFromIndices(kOfferAnswerCodecs, - kResultSend_RecvCodecs); + target_codecs = + VectorFromIndices(kOfferAnswerCodecs, kResultSend_RecvCodecs); break; case cricket::MD_RECVONLY: - target_codecs = VectorFromIndices(kOfferAnswerCodecs, - kResultRecv_SendCodecs); + target_codecs = + VectorFromIndices(kOfferAnswerCodecs, kResultRecv_SendCodecs); break; case cricket::MD_SENDRECV: if (acd->direction() == cricket::MD_SENDONLY) { - target_codecs = VectorFromIndices(kOfferAnswerCodecs, - kResultSendrecv_SendCodecs); + target_codecs = + VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_SendCodecs); } else if (acd->direction() == cricket::MD_RECVONLY) { - target_codecs = VectorFromIndices(kOfferAnswerCodecs, - kResultSendrecv_RecvCodecs); + target_codecs = + VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_RecvCodecs); } else { target_codecs = VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_SendrecvCodecs); @@ -3178,7 +3646,7 @@ void TestAudioCodecsAnswer(MediaContentDirection offer_direction, break; } - auto format_codecs = [] (const std::vector& codecs) { + auto format_codecs = [](const std::vector& codecs) { std::stringstream os; bool first = true; os << "{"; @@ -3199,36 +3667,31 @@ void TestAudioCodecsAnswer(MediaContentDirection offer_direction, << "; got: " << MediaContentDirectionToString(acd->direction()); } else { EXPECT_EQ(offer_direction, cricket::MD_INACTIVE) - << "Only inactive offers are allowed to not generate any audio content"; + << "Only inactive offers are allowed to not generate any audio " + "content"; } } } // namespace class AudioCodecsOfferTest - : public ::testing::TestWithParam<::testing::tuple> { -}; + : public ::testing::TestWithParam {}; TEST_P(AudioCodecsOfferTest, TestCodecsInOffer) { - TestAudioCodecsOffer(::testing::get<0>(GetParam()), - ::testing::get<1>(GetParam())); + TestAudioCodecsOffer(GetParam()); } INSTANTIATE_TEST_CASE_P(MediaSessionDescriptionFactoryTest, AudioCodecsOfferTest, - ::testing::Combine( - ::testing::Values(cricket::MD_SENDONLY, - cricket::MD_RECVONLY, - cricket::MD_SENDRECV, - cricket::MD_INACTIVE), - ::testing::Bool())); + ::testing::Values(cricket::MD_SENDONLY, + cricket::MD_RECVONLY, + cricket::MD_SENDRECV, + cricket::MD_INACTIVE)); class AudioCodecsAnswerTest : public ::testing::TestWithParam<::testing::tuple> { -}; + bool>> {}; TEST_P(AudioCodecsAnswerTest, TestCodecsInAnswer) { TestAudioCodecsAnswer(::testing::get<0>(GetParam()), @@ -3236,15 +3699,15 @@ TEST_P(AudioCodecsAnswerTest, TestCodecsInAnswer) { ::testing::get<2>(GetParam())); } -INSTANTIATE_TEST_CASE_P(MediaSessionDescriptionFactoryTest, - AudioCodecsAnswerTest, - ::testing::Combine( - ::testing::Values(cricket::MD_SENDONLY, - cricket::MD_RECVONLY, - cricket::MD_SENDRECV, - cricket::MD_INACTIVE), - ::testing::Values(cricket::MD_SENDONLY, - cricket::MD_RECVONLY, - cricket::MD_SENDRECV, - cricket::MD_INACTIVE), - ::testing::Bool())); +INSTANTIATE_TEST_CASE_P( + MediaSessionDescriptionFactoryTest, + AudioCodecsAnswerTest, + ::testing::Combine(::testing::Values(cricket::MD_SENDONLY, + cricket::MD_RECVONLY, + cricket::MD_SENDRECV, + cricket::MD_INACTIVE), + ::testing::Values(cricket::MD_SENDONLY, + cricket::MD_RECVONLY, + cricket::MD_SENDRECV, + cricket::MD_INACTIVE), + ::testing::Bool())); diff --git a/webrtc/pc/peerconnection.cc b/webrtc/pc/peerconnection.cc index 3af56671af..6112357ce1 100644 --- a/webrtc/pc/peerconnection.cc +++ b/webrtc/pc/peerconnection.cc @@ -133,32 +133,45 @@ bool IsValidOfferToReceiveMedia(int value) { (value <= Options::kMaxOfferToReceiveMedia); } -// Add the stream and RTP data channel info to |session_options|. -void AddSendStreams( - cricket::MediaSessionOptions* session_options, +// Add options to |[audio/video]_media_description_options| from |senders|. +void AddRtpSenderOptions( const std::vector>>& senders, - const std::map>& - rtp_data_channels) { - session_options->streams.clear(); + cricket::MediaDescriptionOptions* audio_media_description_options, + cricket::MediaDescriptionOptions* video_media_description_options) { for (const auto& sender : senders) { - session_options->AddSendStream(sender->media_type(), sender->id(), - sender->internal()->stream_id()); + if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) { + if (audio_media_description_options) { + audio_media_description_options->AddAudioSender( + sender->id(), sender->internal()->stream_id()); + } + } else { + RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO); + if (video_media_description_options) { + video_media_description_options->AddVideoSender( + sender->id(), sender->internal()->stream_id(), 1); + } + } } +} +// Add options to |session_options| from |rtp_data_channels|. +void AddRtpDataChannelOptions( + const std::map>& + rtp_data_channels, + cricket::MediaDescriptionOptions* data_media_description_options) { + if (!data_media_description_options) { + return; + } // Check for data channels. for (const auto& kv : rtp_data_channels) { const DataChannel* channel = kv.second; if (channel->state() == DataChannel::kConnecting || channel->state() == DataChannel::kOpen) { - // |streamid| and |sync_label| are both set to the DataChannel label - // here so they can be signaled the same way as MediaStreams and Tracks. - // For MediaStreams, the sync_label is the MediaStream label and the - // track label is the same as |streamid|. - const std::string& streamid = channel->label(); - const std::string& sync_label = channel->label(); - session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, streamid, - sync_label); + // Legacy RTP data channels are signaled with the track/stream ID set to + // the data channel's label. + data_media_description_options->AddRtpDataChannel(channel->label(), + channel->label()); } } } @@ -314,92 +327,62 @@ std::string GenerateRtcpCname() { return cname; } -bool ExtractMediaSessionOptions( - const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, - bool is_offer, - cricket::MediaSessionOptions* session_options) { - typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions; - if (!IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) || - !IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video)) { - return false; - } - - // If constraints don't prevent us, we always accept video. - if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { - session_options->recv_audio = (rtc_options.offer_to_receive_audio > 0); - } else { - session_options->recv_audio = true; - } - // For offers, we only offer video if we have it or it's forced by options. - // For answers, we will always accept video (if offered). - if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { - session_options->recv_video = (rtc_options.offer_to_receive_video > 0); - } else if (is_offer) { - session_options->recv_video = false; - } else { - session_options->recv_video = true; - } - - session_options->vad_enabled = rtc_options.voice_activity_detection; - session_options->bundle_enabled = rtc_options.use_rtp_mux; - for (auto& kv : session_options->transport_options) { - kv.second.ice_restart = rtc_options.ice_restart; - } - - return true; +bool ValidateOfferAnswerOptions( + const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) { + return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) && + IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video); } -bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints, - cricket::MediaSessionOptions* session_options) { - bool value = false; - size_t mandatory_constraints_satisfied = 0; - - // kOfferToReceiveAudio defaults to true according to spec. - if (!FindConstraint(constraints, - MediaConstraintsInterface::kOfferToReceiveAudio, &value, - &mandatory_constraints_satisfied) || - value) { - session_options->recv_audio = true; - } - - // kOfferToReceiveVideo defaults to false according to spec. But - // if it is an answer and video is offered, we should still accept video - // per default. - value = false; - if (!FindConstraint(constraints, - MediaConstraintsInterface::kOfferToReceiveVideo, &value, - &mandatory_constraints_satisfied) || - value) { - session_options->recv_video = true; - } - - if (FindConstraint(constraints, - MediaConstraintsInterface::kVoiceActivityDetection, &value, - &mandatory_constraints_satisfied)) { - session_options->vad_enabled = value; - } - - if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value, - &mandatory_constraints_satisfied)) { - session_options->bundle_enabled = value; - } else { - // kUseRtpMux defaults to true according to spec. - session_options->bundle_enabled = true; - } - - bool ice_restart = false; - if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart, - &value, &mandatory_constraints_satisfied)) { - // kIceRestart defaults to false according to spec. - ice_restart = true; - } - for (auto& kv : session_options->transport_options) { - kv.second.ice_restart = ice_restart; - } +// From |rtc_options|, fill parts of |session_options| shared by all generated +// m= sections (in other words, nothing that involves a map/array). +void ExtractSharedMediaSessionOptions( + const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, + cricket::MediaSessionOptions* session_options) { + session_options->vad_enabled = rtc_options.voice_activity_detection; + session_options->bundle_enabled = rtc_options.use_rtp_mux; +} +bool ConvertConstraintsToOfferAnswerOptions( + const MediaConstraintsInterface* constraints, + PeerConnectionInterface::RTCOfferAnswerOptions* offer_answer_options) { if (!constraints) { return true; } + + bool value = false; + size_t mandatory_constraints_satisfied = 0; + + if (FindConstraint(constraints, + MediaConstraintsInterface::kOfferToReceiveAudio, &value, + &mandatory_constraints_satisfied)) { + offer_answer_options->offer_to_receive_audio = + value ? PeerConnectionInterface::RTCOfferAnswerOptions:: + kOfferToReceiveMediaTrue + : 0; + } + + if (FindConstraint(constraints, + MediaConstraintsInterface::kOfferToReceiveVideo, &value, + &mandatory_constraints_satisfied)) { + offer_answer_options->offer_to_receive_video = + value ? PeerConnectionInterface::RTCOfferAnswerOptions:: + kOfferToReceiveMediaTrue + : 0; + } + if (FindConstraint(constraints, + MediaConstraintsInterface::kVoiceActivityDetection, &value, + &mandatory_constraints_satisfied)) { + offer_answer_options->voice_activity_detection = value; + } + if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value, + &mandatory_constraints_satisfied)) { + offer_answer_options->use_rtp_mux = value; + } + if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart, + &value, &mandatory_constraints_satisfied)) { + offer_answer_options->ice_restart = value; + } + return mandatory_constraints_satisfied == constraints->GetMandatory().size(); } @@ -842,49 +825,17 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } - RTCOfferAnswerOptions options; + PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; + // Always create an offer even if |ConvertConstraintsToOfferAnswerOptions| + // returns false for now. Because |ConvertConstraintsToOfferAnswerOptions| + // compares the mandatory fields parsed with the mandatory fields added in the + // |constraints| and some downstream applications might create offers with + // mandatory fields which would not be parsed in the helper method. For + // example, in Chromium/remoting, |kEnableDtlsSrtp| is added to the + // |constraints| as a mandatory field but it is not parsed. + ConvertConstraintsToOfferAnswerOptions(constraints, &offer_answer_options); - bool value; - size_t mandatory_constraints = 0; - - if (FindConstraint(constraints, - MediaConstraintsInterface::kOfferToReceiveAudio, - &value, - &mandatory_constraints)) { - options.offer_to_receive_audio = - value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; - } - - if (FindConstraint(constraints, - MediaConstraintsInterface::kOfferToReceiveVideo, - &value, - &mandatory_constraints)) { - options.offer_to_receive_video = - value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; - } - - if (FindConstraint(constraints, - MediaConstraintsInterface::kVoiceActivityDetection, - &value, - &mandatory_constraints)) { - options.voice_activity_detection = value; - } - - if (FindConstraint(constraints, - MediaConstraintsInterface::kIceRestart, - &value, - &mandatory_constraints)) { - options.ice_restart = value; - } - - if (FindConstraint(constraints, - MediaConstraintsInterface::kUseRtpMux, - &value, - &mandatory_constraints)) { - options.use_rtp_mux = value; - } - - CreateOffer(observer, options); + CreateOffer(observer, offer_answer_options); } void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, @@ -895,14 +846,15 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, return; } - cricket::MediaSessionOptions session_options; - if (!GetOptionsForOffer(options, &session_options)) { + if (!ValidateOfferAnswerOptions(options)) { std::string error = "CreateOffer called with invalid options."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailure(observer, error); return; } + cricket::MediaSessionOptions session_options; + GetOptionsForOffer(options, &session_options); session_->CreateOffer(observer, options, session_options); } @@ -915,14 +867,26 @@ void PeerConnection::CreateAnswer( return; } - cricket::MediaSessionOptions session_options; - if (!GetOptionsForAnswer(constraints, &session_options)) { + if (!session_->remote_description() || + session_->remote_description()->type() != + SessionDescriptionInterface::kOffer) { + std::string error = "CreateAnswer called without remote offer."; + LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailure(observer, error); + return; + } + + PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; + if (!ConvertConstraintsToOfferAnswerOptions(constraints, + &offer_answer_options)) { std::string error = "CreateAnswer called with invalid constraints."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailure(observer, error); return; } + cricket::MediaSessionOptions session_options; + GetOptionsForAnswer(offer_answer_options, &session_options); session_->CreateAnswer(observer, session_options); } @@ -935,12 +899,7 @@ void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, } cricket::MediaSessionOptions session_options; - if (!GetOptionsForAnswer(options, &session_options)) { - std::string error = "CreateAnswer called with invalid options."; - LOG(LS_ERROR) << error; - PostCreateSessionDescriptionFailure(observer, error); - return; - } + GetOptionsForAnswer(options, &session_options); session_->CreateAnswer(observer, session_options); } @@ -1698,121 +1657,242 @@ void PeerConnection::PostCreateSessionDescriptionFailure( MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg); } -bool PeerConnection::GetOptionsForOffer( +void PeerConnection::GetOptionsForOffer( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, cricket::MediaSessionOptions* session_options) { - // TODO(deadbeef): Once we have transceivers, enumerate them here instead of - // ContentInfos. + ExtractSharedMediaSessionOptions(rtc_options, session_options); + + // Figure out transceiver directional preferences. + bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO); + bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO); + + // By default, generate sendrecv/recvonly m= sections. + bool recv_audio = true; + bool recv_video = true; + + // By default, only offer a new m= section if we have media to send with it. + bool offer_new_audio_description = send_audio; + bool offer_new_video_description = send_video; + bool offer_new_data_description = HasDataChannels(); + + // The "offer_to_receive_X" options allow those defaults to be overridden. + if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { + recv_audio = (rtc_options.offer_to_receive_audio > 0); + offer_new_audio_description = + offer_new_audio_description || (rtc_options.offer_to_receive_audio > 0); + } + if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { + recv_video = (rtc_options.offer_to_receive_video > 0); + offer_new_video_description = + offer_new_video_description || (rtc_options.offer_to_receive_video > 0); + } + + rtc::Optional audio_index; + rtc::Optional video_index; + rtc::Optional data_index; + // If a current description exists, generate m= sections in the same order, + // using the first audio/video/data section that appears and rejecting + // extraneous ones. if (session_->local_description()) { - for (const cricket::ContentInfo& content : - session_->local_description()->description()->contents()) { - session_options->transport_options[content.name] = - cricket::TransportOptions(); - } - } - session_options->enable_ice_renomination = - configuration_.enable_ice_renomination; - - if (!ExtractMediaSessionOptions(rtc_options, true, session_options)) { - return false; + GenerateMediaDescriptionOptions( + session_->local_description(), + cricket::RtpTransceiverDirection(send_audio, recv_audio), + cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index, + &video_index, &data_index, session_options); } - AddSendStreams(session_options, senders_, rtp_data_channels_); - // Offer to receive audio/video if the constraint is not set and there are - // send streams, or we're currently receiving. - if (rtc_options.offer_to_receive_audio == RTCOfferAnswerOptions::kUndefined) { - session_options->recv_audio = - session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO) || - !remote_audio_tracks_.empty(); + // Add audio/video/data m= sections to the end if needed. + if (!audio_index && offer_new_audio_description) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, + cricket::RtpTransceiverDirection(send_audio, recv_audio), false)); + audio_index = rtc::Optional( + session_options->media_description_options.size() - 1); } - if (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) { - session_options->recv_video = - session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO) || - !remote_video_tracks_.empty(); + if (!video_index && offer_new_video_description) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::RtpTransceiverDirection(send_video, recv_video), false)); + video_index = rtc::Optional( + session_options->media_description_options.size() - 1); } + if (!data_index && offer_new_data_description) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_DATA, cricket::CN_DATA, + cricket::RtpTransceiverDirection(true, true), false)); + data_index = rtc::Optional( + session_options->media_description_options.size() - 1); + } + + cricket::MediaDescriptionOptions* audio_media_description_options = + !audio_index ? nullptr + : &session_options->media_description_options[*audio_index]; + cricket::MediaDescriptionOptions* video_media_description_options = + !video_index ? nullptr + : &session_options->media_description_options[*video_index]; + cricket::MediaDescriptionOptions* data_media_description_options = + !data_index ? nullptr + : &session_options->media_description_options[*data_index]; + + // Apply ICE restart flag and renomination flag. + for (auto& options : session_options->media_description_options) { + options.transport_options.ice_restart = rtc_options.ice_restart; + options.transport_options.enable_ice_renomination = + configuration_.enable_ice_renomination; + } + + AddRtpSenderOptions(senders_, audio_media_description_options, + video_media_description_options); + AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options); // Intentionally unset the data channel type for RTP data channel with the // second condition. Otherwise the RTP data channels would be successfully // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail // when building with chromium. We want to leave RTP data channels broken, so // people won't try to use them. - if (HasDataChannels() && session_->data_channel_type() != cricket::DCT_RTP) { + if (!rtp_data_channels_.empty() || + session_->data_channel_type() != cricket::DCT_RTP) { session_options->data_channel_type = session_->data_channel_type(); } - session_options->bundle_enabled = - session_options->bundle_enabled && - (session_options->has_audio() || session_options->has_video() || - session_options->has_data()); - session_options->rtcp_cname = rtcp_cname_; session_options->crypto_options = factory_->options().crypto_options; - return true; } -void PeerConnection::InitializeOptionsForAnswer( +void PeerConnection::GetOptionsForAnswer( + const RTCOfferAnswerOptions& rtc_options, cricket::MediaSessionOptions* session_options) { - session_options->recv_audio = false; - session_options->recv_video = false; - session_options->enable_ice_renomination = - configuration_.enable_ice_renomination; -} + ExtractSharedMediaSessionOptions(rtc_options, session_options); -void PeerConnection::FinishOptionsForAnswer( - cricket::MediaSessionOptions* session_options) { - // TODO(deadbeef): Once we have transceivers, enumerate them here instead of - // ContentInfos. - if (session_->remote_description()) { - // Initialize the transport_options map. - for (const cricket::ContentInfo& content : - session_->remote_description()->description()->contents()) { - session_options->transport_options[content.name] = - cricket::TransportOptions(); - } + // Figure out transceiver directional preferences. + bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO); + bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO); + + // By default, generate sendrecv/recvonly m= sections. The direction is also + // restricted by the direction in the offer. + bool recv_audio = true; + bool recv_video = true; + + // The "offer_to_receive_X" options allow those defaults to be overridden. + if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { + recv_audio = (rtc_options.offer_to_receive_audio > 0); } - AddSendStreams(session_options, senders_, rtp_data_channels_); - // RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams - // are not signaled in the SDP so does not go through that path and must be - // handled here. + if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { + recv_video = (rtc_options.offer_to_receive_video > 0); + } + + rtc::Optional audio_index; + rtc::Optional video_index; + rtc::Optional data_index; + // There should be a pending remote description that's an offer... + RTC_DCHECK(session_->remote_description()); + RTC_DCHECK(session_->remote_description()->type() == + SessionDescriptionInterface::kOffer); + // Generate m= sections that match those in the offer. + // Note that mediasession.cc will handle intersection our preferred direction + // with the offered direction. + GenerateMediaDescriptionOptions( + session_->remote_description(), + cricket::RtpTransceiverDirection(send_audio, recv_audio), + cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index, + &video_index, &data_index, session_options); + + cricket::MediaDescriptionOptions* audio_media_description_options = + !audio_index ? nullptr + : &session_options->media_description_options[*audio_index]; + cricket::MediaDescriptionOptions* video_media_description_options = + !video_index ? nullptr + : &session_options->media_description_options[*video_index]; + cricket::MediaDescriptionOptions* data_media_description_options = + !data_index ? nullptr + : &session_options->media_description_options[*data_index]; + + // Apply ICE renomination flag. + for (auto& options : session_options->media_description_options) { + options.transport_options.enable_ice_renomination = + configuration_.enable_ice_renomination; + } + + AddRtpSenderOptions(senders_, audio_media_description_options, + video_media_description_options); + AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options); + // Intentionally unset the data channel type for RTP data channel. Otherwise // the RTP data channels would be successfully negotiated by default and the // unit tests in WebRtcDataBrowserTest will fail when building with chromium. // We want to leave RTP data channels broken, so people won't try to use them. - if (session_->data_channel_type() != cricket::DCT_RTP) { + if (!rtp_data_channels_.empty() || + session_->data_channel_type() != cricket::DCT_RTP) { session_options->data_channel_type = session_->data_channel_type(); } - session_options->bundle_enabled = - session_options->bundle_enabled && - (session_options->has_audio() || session_options->has_video() || - session_options->has_data()); + session_options->rtcp_cname = rtcp_cname_; session_options->crypto_options = factory_->options().crypto_options; } -bool PeerConnection::GetOptionsForAnswer( - const MediaConstraintsInterface* constraints, +void PeerConnection::GenerateMediaDescriptionOptions( + const SessionDescriptionInterface* session_desc, + cricket::RtpTransceiverDirection audio_direction, + cricket::RtpTransceiverDirection video_direction, + rtc::Optional* audio_index, + rtc::Optional* video_index, + rtc::Optional* data_index, cricket::MediaSessionOptions* session_options) { - InitializeOptionsForAnswer(session_options); - if (!ParseConstraintsForAnswer(constraints, session_options)) { - return false; + for (const cricket::ContentInfo& content : + session_desc->description()->contents()) { + if (IsAudioContent(&content)) { + // If we already have an audio m= section, reject this extra one. + if (*audio_index) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_AUDIO, content.name, + cricket::RtpTransceiverDirection(false, false), true)); + } else { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_AUDIO, content.name, audio_direction, + !audio_direction.send && !audio_direction.recv)); + *audio_index = rtc::Optional( + session_options->media_description_options.size() - 1); + } + } else if (IsVideoContent(&content)) { + // If we already have an video m= section, reject this extra one. + if (*video_index) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, content.name, + cricket::RtpTransceiverDirection(false, false), true)); + } else { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, content.name, video_direction, + !video_direction.send && !video_direction.recv)); + *video_index = rtc::Optional( + session_options->media_description_options.size() - 1); + } + } else { + RTC_DCHECK(IsDataContent(&content)); + // If we already have an data m= section, reject this extra one. + if (*data_index) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_DATA, content.name, + cricket::RtpTransceiverDirection(false, false), true)); + } else { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_DATA, content.name, + // Direction for data sections is meaningless, but legacy + // endpoints might expect sendrecv. + cricket::RtpTransceiverDirection(true, true), false)); + *data_index = rtc::Optional( + session_options->media_description_options.size() - 1); + } + } } - session_options->rtcp_cname = rtcp_cname_; - - FinishOptionsForAnswer(session_options); - return true; -} - -bool PeerConnection::GetOptionsForAnswer( - const RTCOfferAnswerOptions& options, - cricket::MediaSessionOptions* session_options) { - InitializeOptionsForAnswer(session_options); - if (!ExtractMediaSessionOptions(options, false, session_options)) { - return false; - } - session_options->rtcp_cname = rtcp_cname_; - - FinishOptionsForAnswer(session_options); - return true; } void PeerConnection::RemoveTracks(cricket::MediaType media_type) { @@ -2285,6 +2365,15 @@ void PeerConnection::OnDataChannelOpenMessage( observer_->OnDataChannel(std::move(proxy_channel)); } +bool PeerConnection::HasRtpSender(cricket::MediaType type) const { + return std::find_if( + senders_.begin(), senders_.end(), + [type](const rtc::scoped_refptr< + RtpSenderProxyWithInternal>& sender) { + return sender->media_type() == type; + }) != senders_.end(); +} + RtpSenderInternal* PeerConnection::FindSenderById(const std::string& id) { auto it = std::find_if( senders_.begin(), senders_.end(), diff --git a/webrtc/pc/peerconnection.h b/webrtc/pc/peerconnection.h index f8b6a54cad..5889629bab 100644 --- a/webrtc/pc/peerconnection.h +++ b/webrtc/pc/peerconnection.h @@ -32,28 +32,12 @@ class MediaStreamObserver; class VideoRtpReceiver; class RtcEventLog; -// Populates |session_options| from |rtc_options|, and returns true if options -// are valid. -// |session_options|->transport_options map entries must exist in order for -// them to be populated from |rtc_options|. -bool ExtractMediaSessionOptions( +// TODO(zhihuang): Remove this declaration when the WebRtcSession tests don't +// need it. +void ExtractSharedMediaSessionOptions( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, - bool is_offer, cricket::MediaSessionOptions* session_options); -// Populates |session_options| from |constraints|, and returns true if all -// mandatory constraints are satisfied. -// Assumes that |session_options|->transport_options map entries exist. -// Will also set defaults if corresponding constraints are not present: -// recv_audio=true, recv_video=true, bundle_enabled=true. -// Other fields will be left with existing values. -// -// Deprecated. Will be removed once callers that use constraints are gone. -// TODO(hta): Remove when callers are gone. -// https://bugs.chromium.org/p/webrtc/issues/detail?id=5617 -bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints, - cricket::MediaSessionOptions* session_options); - // PeerConnection implements the PeerConnectionInterface interface. // It uses WebRtcSession to implement the PeerConnection functionality. class PeerConnection : public PeerConnectionInterface, @@ -244,26 +228,24 @@ class PeerConnection : public PeerConnectionInterface, // Returns a MediaSessionOptions struct with options decided by |options|, // the local MediaStreams and DataChannels. - virtual bool GetOptionsForOffer( + void GetOptionsForOffer( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, cricket::MediaSessionOptions* session_options); // Returns a MediaSessionOptions struct with options decided by // |constraints|, the local MediaStreams and DataChannels. - // Deprecated, use version without constraints. - virtual bool GetOptionsForAnswer( - const MediaConstraintsInterface* constraints, - cricket::MediaSessionOptions* session_options); - virtual bool GetOptionsForAnswer( - const RTCOfferAnswerOptions& options, - cricket::MediaSessionOptions* session_options); + void GetOptionsForAnswer(const RTCOfferAnswerOptions& options, + cricket::MediaSessionOptions* session_options); - void InitializeOptionsForAnswer( - cricket::MediaSessionOptions* session_options); - - // Helper function for options processing. - // Deprecated. - virtual void FinishOptionsForAnswer( + // Generates MediaDescriptionOptions for the |session_opts| based on existing + // local description or remote description. + void GenerateMediaDescriptionOptions( + const SessionDescriptionInterface* session_desc, + cricket::RtpTransceiverDirection audio_direction, + cricket::RtpTransceiverDirection video_direction, + rtc::Optional* audio_index, + rtc::Optional* video_index, + rtc::Optional* data_index, cricket::MediaSessionOptions* session_options); // Remove all local and remote tracks of type |media_type|. @@ -361,6 +343,7 @@ class PeerConnection : public PeerConnectionInterface, void OnDataChannelOpenMessage(const std::string& label, const InternalDataChannelInit& config); + bool HasRtpSender(cricket::MediaType type) const; RtpSenderInternal* FindSenderById(const std::string& id); std::vectorstreams()[0].cname; } + std::unique_ptr CreateOfferWithOptions( + const RTCOfferAnswerOptions& offer_answer_options) { + RTC_DCHECK(pc_); + rtc::scoped_refptr observer( + new rtc::RefCountedObject()); + pc_->CreateOffer(observer, offer_answer_options); + EXPECT_EQ_WAIT(true, observer->called(), kTimeout); + return observer->MoveDescription(); + } + + void CreateOfferWithOptionsAsRemoteDescription( + std::unique_ptr* desc, + const RTCOfferAnswerOptions& offer_answer_options) { + *desc = CreateOfferWithOptions(offer_answer_options); + ASSERT_TRUE(desc != nullptr); + std::string sdp; + EXPECT_TRUE((*desc)->ToString(&sdp)); + SessionDescriptionInterface* remote_offer = + webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer, + sdp, NULL); + EXPECT_TRUE(DoSetRemoteDescription(remote_offer)); + EXPECT_EQ(PeerConnectionInterface::kHaveRemoteOffer, observer_.state_); + } + + void CreateOfferWithOptionsAsLocalDescription( + std::unique_ptr* desc, + const RTCOfferAnswerOptions& offer_answer_options) { + *desc = CreateOfferWithOptions(offer_answer_options); + ASSERT_TRUE(desc != nullptr); + std::string sdp; + EXPECT_TRUE((*desc)->ToString(&sdp)); + SessionDescriptionInterface* new_offer = webrtc::CreateSessionDescription( + SessionDescriptionInterface::kOffer, sdp, NULL); + + EXPECT_TRUE(DoSetLocalDescription(new_offer)); + EXPECT_EQ(PeerConnectionInterface::kHaveLocalOffer, observer_.state_); + } + + bool HasCNCodecs(const cricket::ContentInfo* content) { + const cricket::ContentDescription* description = content->description; + RTC_DCHECK(description); + const cricket::AudioContentDescription* audio_content_desc = + static_cast(description); + RTC_DCHECK(audio_content_desc); + for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) { + if (audio_content_desc->codecs()[i].name == "CN") + return true; + } + return false; + } + std::unique_ptr vss_; rtc::AutoSocketServerThread main_; cricket::FakePortAllocator* port_allocator_ = nullptr; @@ -3494,6 +3545,224 @@ TEST_F(PeerConnectionInterfaceTest, SetBitrateCurrentLessThanImplicitMin) { EXPECT_TRUE(pc_->SetBitrate(bitrate).ok()); } +// The following tests verify that the offer can be created correctly. +TEST_F(PeerConnectionInterfaceTest, + CreateOfferFailsWithInvalidOfferToReceiveAudio) { + RTCOfferAnswerOptions rtc_options; + + // Setting offer_to_receive_audio to a value lower than kUndefined or greater + // than kMaxOfferToReceiveMedia should be treated as invalid. + rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1; + CreatePeerConnection(); + EXPECT_FALSE(CreateOfferWithOptions(rtc_options)); + + rtc_options.offer_to_receive_audio = + RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1; + EXPECT_FALSE(CreateOfferWithOptions(rtc_options)); +} + +TEST_F(PeerConnectionInterfaceTest, + CreateOfferFailsWithInvalidOfferToReceiveVideo) { + RTCOfferAnswerOptions rtc_options; + + // Setting offer_to_receive_video to a value lower than kUndefined or greater + // than kMaxOfferToReceiveMedia should be treated as invalid. + rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1; + CreatePeerConnection(); + EXPECT_FALSE(CreateOfferWithOptions(rtc_options)); + + rtc_options.offer_to_receive_video = + RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1; + EXPECT_FALSE(CreateOfferWithOptions(rtc_options)); +} + +// Test that the audio and video content will be added to an offer if both +// |offer_to_receive_audio| and |offer_to_receive_video| options are 1. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioVideoOptions) { + RTCOfferAnswerOptions rtc_options; + rtc_options.offer_to_receive_audio = 1; + rtc_options.offer_to_receive_video = 1; + + std::unique_ptr offer; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); +} + +// Test that only audio content will be added to the offer if only +// |offer_to_receive_audio| options is 1. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioOnlyOptions) { + RTCOfferAnswerOptions rtc_options; + rtc_options.offer_to_receive_audio = 1; + rtc_options.offer_to_receive_video = 0; + + std::unique_ptr offer; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description())); +} + +// Test that only video content will be added if only |offer_to_receive_video| +// options is 1. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVideoOnlyOptions) { + RTCOfferAnswerOptions rtc_options; + rtc_options.offer_to_receive_audio = 0; + rtc_options.offer_to_receive_video = 1; + + std::unique_ptr offer; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); +} + +// Test that if |voice_activity_detection| is false, no CN codec is added to the +// offer. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVADOptions) { + RTCOfferAnswerOptions rtc_options; + rtc_options.offer_to_receive_audio = 1; + rtc_options.offer_to_receive_video = 0; + + std::unique_ptr offer; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + const cricket::ContentInfo* audio_content = + offer->description()->GetContentByName(cricket::CN_AUDIO); + ASSERT_TRUE(audio_content); + // |voice_activity_detection| is true by default. + EXPECT_TRUE(HasCNCodecs(audio_content)); + + rtc_options.voice_activity_detection = false; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + audio_content = offer->description()->GetContentByName(cricket::CN_AUDIO); + ASSERT_TRUE(audio_content); + EXPECT_FALSE(HasCNCodecs(audio_content)); +} + +// Test that no media content will be added to the offer if using default +// RTCOfferAnswerOptions. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) { + RTCOfferAnswerOptions rtc_options; + + std::unique_ptr offer; + CreatePeerConnection(); + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description())); +} + +// Test that if |ice_restart| is true, the ufrag/pwd will change, otherwise +// ufrag/pwd will be the same in the new offer. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithIceRestart) { + RTCOfferAnswerOptions rtc_options; + rtc_options.ice_restart = false; + rtc_options.offer_to_receive_audio = 1; + + std::unique_ptr offer; + CreatePeerConnection(); + CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options); + auto ufrag1 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_ufrag; + auto pwd1 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_pwd; + + // |ice_restart| is false, the ufrag/pwd shouldn't change. + CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options); + auto ufrag2 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_ufrag; + auto pwd2 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_pwd; + + // |ice_restart| is true, the ufrag/pwd should change. + rtc_options.ice_restart = true; + CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options); + auto ufrag3 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_ufrag; + auto pwd3 = offer->description() + ->GetTransportInfoByName(cricket::CN_AUDIO) + ->description.ice_pwd; + + EXPECT_EQ(ufrag1, ufrag2); + EXPECT_EQ(pwd1, pwd2); + EXPECT_NE(ufrag2, ufrag3); + EXPECT_NE(pwd2, pwd3); +} + +// Test that if |use_rtp_mux| is true, the bundling will be enabled in the +// offer; if it is false, there won't be any bundle group in the offer. +TEST_F(PeerConnectionInterfaceTest, CreateOfferWithRtpMux) { + RTCOfferAnswerOptions rtc_options; + rtc_options.offer_to_receive_audio = 1; + rtc_options.offer_to_receive_video = 1; + + std::unique_ptr offer; + CreatePeerConnection(); + + rtc_options.use_rtp_mux = true; + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); + EXPECT_TRUE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE)); + + rtc_options.use_rtp_mux = false; + offer = CreateOfferWithOptions(rtc_options); + ASSERT_TRUE(offer); + EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); + EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE)); +} + +// If SetMandatoryReceiveAudio(false) and SetMandatoryReceiveVideo(false) are +// called for the answer constraints, but an audio and a video section were +// offered, there will still be an audio and a video section in the answer. +TEST_F(PeerConnectionInterfaceTest, + RejectAudioAndVideoInAnswerWithConstraints) { + // Offer both audio and video. + RTCOfferAnswerOptions rtc_offer_options; + rtc_offer_options.offer_to_receive_audio = 1; + rtc_offer_options.offer_to_receive_video = 1; + + CreatePeerConnection(); + std::unique_ptr offer; + CreateOfferWithOptionsAsRemoteDescription(&offer, rtc_offer_options); + EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); + EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); + + // Since an offer has been created with both audio and video, + // Answers will contain the media types that exist in the offer regardless of + // the value of |answer_options.has_audio| and |answer_options.has_video|. + FakeConstraints answer_c; + // Reject both audio and video. + answer_c.SetMandatoryReceiveAudio(false); + answer_c.SetMandatoryReceiveVideo(false); + + std::unique_ptr answer; + ASSERT_TRUE(DoCreateAnswer(&answer, &answer_c)); + const cricket::ContentInfo* audio_content = + GetFirstAudioContent(answer->description()); + const cricket::ContentInfo* video_content = + GetFirstVideoContent(answer->description()); + ASSERT_NE(nullptr, audio_content); + ASSERT_NE(nullptr, video_content); + EXPECT_TRUE(audio_content->rejected); + EXPECT_TRUE(video_content->rejected); +} + class PeerConnectionMediaConfigTest : public testing::Test { protected: void SetUp() override { @@ -3502,8 +3771,7 @@ class PeerConnectionMediaConfigTest : public testing::Test { } const cricket::MediaConfig TestCreatePeerConnection( const PeerConnectionInterface::RTCConfiguration& config, - const MediaConstraintsInterface *constraints) { - + const MediaConstraintsInterface* constraints) { rtc::scoped_refptr pc(pcf_->CreatePeerConnection( config, constraints, nullptr, nullptr, &observer_)); EXPECT_TRUE(pc.get()); @@ -3585,177 +3853,6 @@ TEST_F(PeerConnectionMediaConfigTest, EXPECT_TRUE(media_config.video.suspend_below_min_bitrate); } -// The following tests verify that session options are created correctly. -// TODO(deadbeef): Convert these tests to be more end-to-end. Instead of -// "verify options are converted correctly", should be "pass options into -// CreateOffer and verify the correct offer is produced." - -TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidAudioOption) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1; - - cricket::MediaSessionOptions options; - EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options)); - - rtc_options.offer_to_receive_audio = - RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1; - EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options)); -} - -TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidVideoOption) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1; - - cricket::MediaSessionOptions options; - EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options)); - - rtc_options.offer_to_receive_video = - RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1; - EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options)); -} - -// Test that a MediaSessionOptions is created for an offer if -// OfferToReceiveAudio and OfferToReceiveVideo options are set. -TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudioVideo) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = 1; - rtc_options.offer_to_receive_video = 1; - - cricket::MediaSessionOptions options; - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_TRUE(options.has_audio()); - EXPECT_TRUE(options.has_video()); - EXPECT_TRUE(options.bundle_enabled); -} - -// Test that a correct MediaSessionOptions is created for an offer if -// OfferToReceiveAudio is set. -TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudio) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = 1; - - cricket::MediaSessionOptions options; - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_TRUE(options.has_audio()); - EXPECT_FALSE(options.has_video()); - EXPECT_TRUE(options.bundle_enabled); -} - -// Test that a correct MediaSessionOptions is created for an offer if -// the default OfferOptions are used. -TEST(CreateSessionOptionsTest, GetDefaultMediaSessionOptionsForOffer) { - RTCOfferAnswerOptions rtc_options; - - cricket::MediaSessionOptions options; - options.transport_options["audio"] = cricket::TransportOptions(); - options.transport_options["video"] = cricket::TransportOptions(); - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_TRUE(options.has_audio()); - EXPECT_FALSE(options.has_video()); - EXPECT_TRUE(options.bundle_enabled); - EXPECT_TRUE(options.vad_enabled); - EXPECT_FALSE(options.transport_options["audio"].ice_restart); - EXPECT_FALSE(options.transport_options["video"].ice_restart); -} - -// Test that a correct MediaSessionOptions is created for an offer if -// OfferToReceiveVideo is set. -TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithVideo) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = 0; - rtc_options.offer_to_receive_video = 1; - - cricket::MediaSessionOptions options; - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_FALSE(options.has_audio()); - EXPECT_TRUE(options.has_video()); - EXPECT_TRUE(options.bundle_enabled); -} - -// Test that a correct MediaSessionOptions is created for an offer if -// UseRtpMux is set to false. -TEST(CreateSessionOptionsTest, - GetMediaSessionOptionsForOfferWithBundleDisabled) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = 1; - rtc_options.offer_to_receive_video = 1; - rtc_options.use_rtp_mux = false; - - cricket::MediaSessionOptions options; - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_TRUE(options.has_audio()); - EXPECT_TRUE(options.has_video()); - EXPECT_FALSE(options.bundle_enabled); -} - -// Test that a correct MediaSessionOptions is created to restart ice if -// IceRestart is set. It also tests that subsequent MediaSessionOptions don't -// have |audio_transport_options.ice_restart| etc. set. -TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithIceRestart) { - RTCOfferAnswerOptions rtc_options; - rtc_options.ice_restart = true; - - cricket::MediaSessionOptions options; - options.transport_options["audio"] = cricket::TransportOptions(); - options.transport_options["video"] = cricket::TransportOptions(); - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_TRUE(options.transport_options["audio"].ice_restart); - EXPECT_TRUE(options.transport_options["video"].ice_restart); - - rtc_options = RTCOfferAnswerOptions(); - EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options)); - EXPECT_FALSE(options.transport_options["audio"].ice_restart); - EXPECT_FALSE(options.transport_options["video"].ice_restart); -} - -// Test that the MediaConstraints in an answer don't affect if audio and video -// is offered in an offer but that if kOfferToReceiveAudio or -// kOfferToReceiveVideo constraints are true in an offer, the media type will be -// included in subsequent answers. -TEST(CreateSessionOptionsTest, MediaConstraintsInAnswer) { - FakeConstraints answer_c; - answer_c.SetMandatoryReceiveAudio(true); - answer_c.SetMandatoryReceiveVideo(true); - - cricket::MediaSessionOptions answer_options; - EXPECT_TRUE(ParseConstraintsForAnswer(&answer_c, &answer_options)); - EXPECT_TRUE(answer_options.has_audio()); - EXPECT_TRUE(answer_options.has_video()); - - RTCOfferAnswerOptions rtc_offer_options; - - cricket::MediaSessionOptions offer_options; - EXPECT_TRUE( - ExtractMediaSessionOptions(rtc_offer_options, false, &offer_options)); - EXPECT_TRUE(offer_options.has_audio()); - EXPECT_TRUE(offer_options.has_video()); - - RTCOfferAnswerOptions updated_rtc_offer_options; - updated_rtc_offer_options.offer_to_receive_audio = 1; - updated_rtc_offer_options.offer_to_receive_video = 1; - - cricket::MediaSessionOptions updated_offer_options; - EXPECT_TRUE(ExtractMediaSessionOptions(updated_rtc_offer_options, false, - &updated_offer_options)); - EXPECT_TRUE(updated_offer_options.has_audio()); - EXPECT_TRUE(updated_offer_options.has_video()); - - // Since an offer has been created with both audio and video, subsequent - // offers and answers should contain both audio and video. - // Answers will only contain the media types that exist in the offer - // regardless of the value of |updated_answer_options.has_audio| and - // |updated_answer_options.has_video|. - FakeConstraints updated_answer_c; - answer_c.SetMandatoryReceiveAudio(false); - answer_c.SetMandatoryReceiveVideo(false); - - cricket::MediaSessionOptions updated_answer_options; - EXPECT_TRUE( - ParseConstraintsForAnswer(&updated_answer_c, &updated_answer_options)); - EXPECT_TRUE(updated_answer_options.has_audio()); - EXPECT_TRUE(updated_answer_options.has_video()); -} - // Tests a few random fields being different. TEST(RTCConfigurationTest, ComparisonOperators) { PeerConnectionInterface::RTCConfiguration a; diff --git a/webrtc/pc/webrtcsession_unittest.cc b/webrtc/pc/webrtcsession_unittest.cc index f6d334e3e4..bb1c877a5c 100644 --- a/webrtc/pc/webrtcsession_unittest.cc +++ b/webrtc/pc/webrtcsession_unittest.cc @@ -143,6 +143,9 @@ static const char kStream2[] = "stream2"; static const char kVideoTrack2[] = "video2"; static const char kAudioTrack2[] = "audio2"; +static constexpr bool kStopped = true; +static constexpr bool kActive = false; + enum RTCCertificateGenerationMethod { ALREADY_GENERATED, DTLS_IDENTITY_STORE }; class MockIceObserver : public webrtc::IceObserver { @@ -398,7 +401,6 @@ class WebRtcSessionTest allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_DISABLE_RELAY); EXPECT_TRUE(channel_manager_->Init()); - desc_factory_->set_add_legacy_streams(false); allocator_->set_step_delay(cricket::kMinimumStepDelay); } @@ -507,107 +509,237 @@ class WebRtcSessionTest InitWithCryptoOptions(crypto_options); } + // The following convenience functions can be applied for both local side and + // remote side. The flags can be overwritten for different use cases. void SendAudioVideoStream1() { send_stream_1_ = true; send_stream_2_ = false; - send_audio_ = true; - send_video_ = true; + local_send_audio_ = true; + local_send_video_ = true; + remote_send_audio_ = true; + remote_send_video_ = true; } void SendAudioVideoStream2() { send_stream_1_ = false; send_stream_2_ = true; - send_audio_ = true; - send_video_ = true; + local_send_audio_ = true; + local_send_video_ = true; + remote_send_audio_ = true; + remote_send_video_ = true; } void SendAudioVideoStream1And2() { send_stream_1_ = true; send_stream_2_ = true; - send_audio_ = true; - send_video_ = true; + local_send_audio_ = true; + local_send_video_ = true; + remote_send_audio_ = true; + remote_send_video_ = true; } void SendNothing() { send_stream_1_ = false; send_stream_2_ = false; - send_audio_ = false; - send_video_ = false; + local_send_audio_ = false; + local_send_video_ = false; + remote_send_audio_ = false; + remote_send_video_ = false; } void SendAudioOnlyStream2() { send_stream_1_ = false; send_stream_2_ = true; - send_audio_ = true; - send_video_ = false; + local_send_audio_ = true; + local_send_video_ = false; + remote_send_audio_ = true; + remote_send_video_ = false; } void SendVideoOnlyStream2() { send_stream_1_ = false; send_stream_2_ = true; - send_audio_ = false; - send_video_ = true; + local_send_audio_ = false; + local_send_video_ = true; + remote_send_audio_ = false; + remote_send_video_ = true; } - void AddStreamsToOptions(cricket::MediaSessionOptions* session_options) { - if (send_stream_1_ && send_audio_) { - session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, - kStream1); + // Helper function used to add a specific media section to the + // |session_options|. + void AddMediaSection(cricket::MediaType type, + const std::string& mid, + cricket::MediaContentDirection direction, + bool stopped, + cricket::MediaSessionOptions* opts) { + opts->media_description_options.push_back(cricket::MediaDescriptionOptions( + type, mid, + cricket::RtpTransceiverDirection::FromMediaContentDirection(direction), + stopped)); + } + + // Add the media sections to the options from |offered_media_sections_| when + // creating an answer or a new offer. + // This duplicates a lot of logic from PeerConnection but this can be fixed + // when PeerConnection and WebRtcSession are merged. + void AddExistingMediaSectionsAndSendersToOptions( + cricket::MediaSessionOptions* session_options, + bool send_audio, + bool recv_audio, + bool send_video, + bool recv_video) { + int num_sim_layer = 1; + for (auto media_description_options : offered_media_sections_) { + if (media_description_options.type == cricket::MEDIA_TYPE_AUDIO) { + bool stopped = !send_audio && !recv_audio; + auto media_desc_options = cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_AUDIO, media_description_options.mid, + cricket::RtpTransceiverDirection(send_audio, recv_audio), stopped); + if (send_stream_1_ && send_audio) { + media_desc_options.AddAudioSender(kAudioTrack1, kStream1); + } + if (send_stream_2_ && send_audio) { + media_desc_options.AddAudioSender(kAudioTrack2, kStream2); + } + session_options->media_description_options.push_back( + media_desc_options); + } else if (media_description_options.type == cricket::MEDIA_TYPE_VIDEO) { + bool stopped = !send_video && !recv_video; + auto media_desc_options = cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, media_description_options.mid, + cricket::RtpTransceiverDirection(send_video, recv_video), stopped); + if (send_stream_1_ && send_video) { + media_desc_options.AddVideoSender(kVideoTrack1, kStream1, + num_sim_layer); + } + if (send_stream_2_ && send_video) { + media_desc_options.AddVideoSender(kVideoTrack2, kStream2, + num_sim_layer); + } + session_options->media_description_options.push_back( + media_desc_options); + } else if (media_description_options.type == cricket::MEDIA_TYPE_DATA) { + session_options->media_description_options.push_back( + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_DATA, media_description_options.mid, + // Direction for data sections is meaningless, but legacy + // endpoints might expect sendrecv. + cricket::RtpTransceiverDirection(true, true), false)); + } else { + RTC_NOTREACHED(); + } } - if (send_stream_1_ && send_video_) { - session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, - kStream1); + } + + // Add the existing media sections first and then add new media sections if + // needed. + void AddMediaSectionsAndSendersToOptions( + cricket::MediaSessionOptions* session_options, + bool send_audio, + bool recv_audio, + bool send_video, + bool recv_video) { + AddExistingMediaSectionsAndSendersToOptions( + session_options, send_audio, recv_audio, send_video, recv_video); + + if (!session_options->has_audio() && (send_audio || recv_audio)) { + cricket::MediaDescriptionOptions media_desc_options = + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, + cricket::RtpTransceiverDirection(send_audio, recv_audio), + kActive); + if (send_stream_1_ && send_audio) { + media_desc_options.AddAudioSender(kAudioTrack1, kStream1); + } + if (send_stream_2_ && send_audio) { + media_desc_options.AddAudioSender(kAudioTrack2, kStream2); + } + session_options->media_description_options.push_back(media_desc_options); + offered_media_sections_.push_back(media_desc_options); } - if (send_stream_2_ && send_audio_) { - session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, - kStream2); + + if (!session_options->has_video() && (send_video || recv_video)) { + cricket::MediaDescriptionOptions media_desc_options = + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::RtpTransceiverDirection(send_video, recv_video), + kActive); + int num_sim_layer = 1; + if (send_stream_1_ && send_video) { + media_desc_options.AddVideoSender(kVideoTrack1, kStream1, + num_sim_layer); + } + if (send_stream_2_ && send_video) { + media_desc_options.AddVideoSender(kVideoTrack2, kStream2, + num_sim_layer); + } + session_options->media_description_options.push_back(media_desc_options); + offered_media_sections_.push_back(media_desc_options); } - if (send_stream_2_ && send_video_) { - session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, - kStream2); - } - if (data_channel_ && session_->data_channel_type() == cricket::DCT_RTP) { - session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, - data_channel_->label(), - data_channel_->label()); + + if (!session_options->has_data() && + (data_channel_ || + session_options->data_channel_type != cricket::DCT_NONE)) { + cricket::MediaDescriptionOptions media_desc_options = + cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_DATA, cricket::CN_DATA, + cricket::RtpTransceiverDirection(true, true), kActive); + if (session_options->data_channel_type == cricket::DCT_RTP) { + media_desc_options.AddRtpDataChannel(data_channel_->label(), + data_channel_->label()); + } + session_options->media_description_options.push_back(media_desc_options); + offered_media_sections_.push_back(media_desc_options); } } void GetOptionsForOffer( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, cricket::MediaSessionOptions* session_options) { - ASSERT_TRUE(ExtractMediaSessionOptions(rtc_options, true, session_options)); + ExtractSharedMediaSessionOptions(rtc_options, session_options); - AddStreamsToOptions(session_options); - if (rtc_options.offer_to_receive_audio == - RTCOfferAnswerOptions::kUndefined) { - session_options->recv_audio = - session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO); - } - if (rtc_options.offer_to_receive_video == - RTCOfferAnswerOptions::kUndefined) { - session_options->recv_video = - session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO); - } + // |recv_X| is true by default if |offer_to_receive_X| is undefined. + bool recv_audio = rtc_options.offer_to_receive_audio != 0; + bool recv_video = rtc_options.offer_to_receive_video != 0; + + AddMediaSectionsAndSendersToOptions(session_options, local_send_audio_, + recv_audio, local_send_video_, + recv_video); session_options->bundle_enabled = session_options->bundle_enabled && (session_options->has_audio() || session_options->has_video() || session_options->has_data()); - if (session_->data_channel_type() == cricket::DCT_SCTP && data_channel_) { - session_options->data_channel_type = cricket::DCT_SCTP; - } else if (session_->data_channel_type() == cricket::DCT_QUIC) { - session_options->data_channel_type = cricket::DCT_QUIC; + session_options->crypto_options = crypto_options_; + } + + void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) { + AddExistingMediaSectionsAndSendersToOptions( + session_options, local_send_audio_, local_recv_audio_, + local_send_video_, local_recv_video_); + + session_options->bundle_enabled = + session_options->bundle_enabled && + (session_options->has_audio() || session_options->has_video() || + session_options->has_data()); + + if (session_->data_channel_type() != cricket::DCT_RTP) { + session_options->data_channel_type = session_->data_channel_type(); } session_options->crypto_options = crypto_options_; } - void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) { - // ParseConstraintsForAnswer is used to set some defaults. - ASSERT_TRUE(webrtc::ParseConstraintsForAnswer(nullptr, session_options)); + void GetOptionsForRemoteAnswer( + cricket::MediaSessionOptions* session_options) { + bool recv_audio = local_send_audio_ || remote_recv_audio_; + bool recv_video = local_send_video_ || remote_recv_video_; + bool send_audio = false; + bool send_video = false; + + AddExistingMediaSectionsAndSendersToOptions( + session_options, send_audio, recv_audio, send_video, recv_video); - AddStreamsToOptions(session_options); session_options->bundle_enabled = session_options->bundle_enabled && (session_options->has_audio() || session_options->has_video() || @@ -620,6 +752,28 @@ class WebRtcSessionTest session_options->crypto_options = crypto_options_; } + void GetOptionsForAudioOnlyRemoteOffer( + cricket::MediaSessionOptions* session_options) { + remote_recv_audio_ = true; + remote_recv_video_ = false; + GetOptionsForRemoteOffer(session_options); + } + + void GetOptionsForRemoteOffer(cricket::MediaSessionOptions* session_options) { + AddMediaSectionsAndSendersToOptions(session_options, remote_send_audio_, + remote_recv_audio_, remote_send_video_, + remote_recv_video_); + session_options->bundle_enabled = + (session_options->has_audio() || session_options->has_video() || + session_options->has_data()); + + if (session_->data_channel_type() != cricket::DCT_RTP) { + session_options->data_channel_type = session_->data_channel_type(); + } + + session_options->crypto_options = crypto_options_; + } + // Creates a local offer and applies it. Starts ICE. // Call SendAudioVideoStreamX() before this function // to decide which streams to create. @@ -635,7 +789,6 @@ class WebRtcSessionTest PeerConnectionInterface::RTCOfferAnswerOptions options; options.offer_to_receive_audio = RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - return CreateOffer(options); } @@ -658,9 +811,6 @@ class WebRtcSessionTest = new WebRtcSessionCreateSDPObserverForTest(); cricket::MediaSessionOptions session_options = options; GetOptionsForAnswer(&session_options); - // Overwrite recv_audio and recv_video with passed-in values. - session_options.recv_video = options.recv_video; - session_options.recv_audio = options.recv_audio; session_->CreateAnswer(observer, session_options); EXPECT_TRUE_WAIT( observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit, @@ -670,8 +820,7 @@ class WebRtcSessionTest SessionDescriptionInterface* CreateAnswer() { cricket::MediaSessionOptions options; - options.recv_video = true; - options.recv_audio = true; + options.bundle_enabled = true; return CreateAnswer(options); } @@ -789,7 +938,7 @@ class WebRtcSessionTest void VerifyAnswerFromNonCryptoOffer() { // Create an SDP without Crypto. cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer( CreateRemoteOffer(options, cricket::SEC_DISABLED)); ASSERT_TRUE(offer != NULL); @@ -803,7 +952,7 @@ class WebRtcSessionTest void VerifyAnswerFromCryptoOffer() { cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); options.bundle_enabled = true; std::unique_ptr offer( CreateRemoteOffer(options, cricket::SEC_REQUIRED)); @@ -926,7 +1075,7 @@ class WebRtcSessionTest SetLocalDescriptionWithoutError(answer); } void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) { - EXPECT_TRUE(session_->SetLocalDescription(desc, NULL)); + ASSERT_TRUE(session_->SetLocalDescription(desc, nullptr)); session_->MaybeStartGathering(); } void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc, @@ -955,7 +1104,7 @@ class WebRtcSessionTest expected_error, desc); } void SetRemoteDescriptionWithoutError(SessionDescriptionInterface* desc) { - EXPECT_TRUE(session_->SetRemoteDescription(desc, NULL)); + ASSERT_TRUE(session_->SetRemoteDescription(desc, nullptr)); } void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc, WebRtcSession::State expected_state) { @@ -992,21 +1141,26 @@ class WebRtcSessionTest SessionDescriptionInterface** nocrypto_answer) { // Create a SDP without Crypto. cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); options.bundle_enabled = true; *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED); ASSERT_TRUE(*offer != NULL); VerifyCryptoParams((*offer)->description()); - *nocrypto_answer = CreateRemoteAnswer(*offer, options, - cricket::SEC_DISABLED); + cricket::MediaSessionOptions answer_options; + GetOptionsForRemoteAnswer(&answer_options); + *nocrypto_answer = + CreateRemoteAnswer(*offer, answer_options, cricket::SEC_DISABLED); EXPECT_TRUE(*nocrypto_answer != NULL); } void CreateDtlsOfferAndNonDtlsAnswer(SessionDescriptionInterface** offer, SessionDescriptionInterface** nodtls_answer) { cricket::MediaSessionOptions options; - options.recv_video = true; + AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, + cricket::MD_RECVONLY, kActive, &options); + AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::MD_RECVONLY, kActive, &options); options.bundle_enabled = true; std::unique_ptr temp_offer( @@ -1068,8 +1222,7 @@ class WebRtcSessionTest const char* sctp_stream_name, int new_port, cricket::MediaSessionOptions options) { options.data_channel_type = cricket::DCT_SCTP; - options.AddSendStream(cricket::MEDIA_TYPE_DATA, "datachannel", - sctp_stream_name); + GetOptionsForRemoteOffer(&options); return ChangeSDPSctpPort(new_port, CreateRemoteOffer(options)); } @@ -1097,7 +1250,7 @@ class WebRtcSessionTest // before this function to decide which streams to create. JsepSessionDescription* CreateRemoteOffer() { cricket::MediaSessionOptions options; - GetOptionsForAnswer(&options); + GetOptionsForRemoteOffer(&options); return CreateRemoteOffer(options, session_->remote_description()); } @@ -1132,6 +1285,7 @@ class WebRtcSessionTest const SessionDescriptionInterface* offer) { cricket::MediaSessionOptions options; GetOptionsForAnswer(&options); + options.bundle_enabled = true; return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED); } @@ -1455,6 +1609,7 @@ class WebRtcSessionTest SetFactoryDtlsSrtp(); if (type == CreateSessionDescriptionRequest::kAnswer) { cricket::MediaSessionOptions options; + GetOptionsForRemoteOffer(&options); std::unique_ptr offer( CreateRemoteOffer(options, cricket::SEC_DISABLED)); ASSERT_TRUE(offer.get() != NULL); @@ -1462,16 +1617,19 @@ class WebRtcSessionTest } PeerConnectionInterface::RTCOfferAnswerOptions options; - cricket::MediaSessionOptions session_options; + cricket::MediaSessionOptions offer_session_options; + cricket::MediaSessionOptions answer_session_options; + GetOptionsForOffer(options, &offer_session_options); + GetOptionsForAnswer(&answer_session_options); const int kNumber = 3; rtc::scoped_refptr observers[kNumber]; for (int i = 0; i < kNumber; ++i) { observers[i] = new WebRtcSessionCreateSDPObserverForTest(); if (type == CreateSessionDescriptionRequest::kOffer) { - session_->CreateOffer(observers[i], options, session_options); + session_->CreateOffer(observers[i], options, offer_session_options); } else { - session_->CreateAnswer(observers[i], session_options); + session_->CreateAnswer(observers[i], answer_session_options); } } @@ -1529,8 +1687,15 @@ class WebRtcSessionTest // The following flags affect options created for CreateOffer/CreateAnswer. bool send_stream_1_ = false; bool send_stream_2_ = false; - bool send_audio_ = false; - bool send_video_ = false; + bool local_send_audio_ = false; + bool local_send_video_ = false; + bool local_recv_audio_ = true; + bool local_recv_video_ = true; + bool remote_send_audio_ = false; + bool remote_send_video_ = false; + bool remote_recv_audio_ = true; + bool remote_recv_video_ = true; + std::vector offered_media_sections_; rtc::scoped_refptr data_channel_; // Last values received from data channel creation signal. std::string last_data_channel_label_; @@ -1770,7 +1935,7 @@ TEST_F(WebRtcSessionTest, SetLocalSdpFailedOnCreateChannel) { TEST_F(WebRtcSessionTest, TestSetNonSdesOfferWhenSdesOn) { Init(); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer = CreateRemoteOffer( options, cricket::SEC_DISABLED); ASSERT_TRUE(offer != NULL); @@ -1816,7 +1981,7 @@ TEST_P(WebRtcSessionTest, TestReceiveDtlsOfferCreateDtlsAnswer) { InitWithDtls(GetParam()); SetFactoryDtlsSrtp(); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); ASSERT_TRUE(offer != NULL); @@ -1855,7 +2020,7 @@ TEST_P(WebRtcSessionTest, TestCreateDtlsOfferReceiveDtlsAnswer) { SetLocalDescriptionWithoutError(offer); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForAnswer(&options); JsepSessionDescription* answer = CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); ASSERT_TRUE(answer != NULL); @@ -1871,7 +2036,7 @@ TEST_P(WebRtcSessionTest, TestCreateDtlsOfferReceiveDtlsAnswer) { TEST_P(WebRtcSessionTest, TestReceiveNonDtlsOfferWhenDtlsOn) { InitWithDtls(GetParam()); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); options.bundle_enabled = true; JsepSessionDescription* offer = CreateRemoteOffer( options, cricket::SEC_REQUIRED); @@ -1909,12 +2074,16 @@ TEST_P(WebRtcSessionTest, TestSetLocalNonDtlsAnswerWhenDtlsOn) { TEST_P(WebRtcSessionTest, TestSetRemoteNonDtlsAnswerWhenDtlsOn) { InitWithDtls(GetParam()); SessionDescriptionInterface* offer = CreateOffer(); - cricket::MediaSessionOptions options; - options.recv_video = true; + cricket::MediaSessionOptions offer_options; + GetOptionsForRemoteOffer(&offer_options); + std::unique_ptr temp_offer( - CreateRemoteOffer(options, cricket::SEC_ENABLED)); - JsepSessionDescription* answer = - CreateRemoteAnswer(temp_offer.get(), options, cricket::SEC_ENABLED); + CreateRemoteOffer(offer_options, cricket::SEC_ENABLED)); + + cricket::MediaSessionOptions answer_options; + GetOptionsForAnswer(&answer_options); + JsepSessionDescription* answer = CreateRemoteAnswer( + temp_offer.get(), answer_options, cricket::SEC_ENABLED); // SetRemoteDescription and SetLocalDescription will take the ownership of // the offer and answer. @@ -1941,7 +2110,7 @@ TEST_P(WebRtcSessionTest, TestCreateOfferReceiveAnswerWithoutEncryption) { SetLocalDescriptionWithoutError(offer); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForAnswer(&options); JsepSessionDescription* answer = CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); ASSERT_TRUE(answer != NULL); @@ -1959,7 +2128,7 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerReceiveOfferWithoutEncryption) { InitWithDtls(GetParam()); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); ASSERT_TRUE(offer != NULL); @@ -1992,7 +2161,7 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { SetLocalDescriptionWithoutError(offer); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForAnswer(&options); // First, negotiate different SSL roles. SessionDescriptionInterface* answer = @@ -2014,7 +2183,9 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { session_->remote_description()); SetRemoteDescriptionWithoutError(offer); - answer = CreateAnswer(); + cricket::MediaSessionOptions answer_options; + answer_options.bundle_enabled = true; + answer = CreateAnswer(answer_options); audio_transport_info = answer->description()->GetTransportInfoByName("audio"); EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE, audio_transport_info->description.connection_role); @@ -2031,7 +2202,7 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { kSessionVersion, session_->remote_description()); SetRemoteDescriptionWithoutError(offer); - answer = CreateAnswer(); + answer = CreateAnswer(answer_options); audio_transport_info = answer->description()->GetTransportInfoByName("audio"); EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE, audio_transport_info->description.connection_role); @@ -2392,6 +2563,7 @@ TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { std::unique_ptr local_offer(CreateOffer()); + ASSERT_TRUE(local_offer); ASSERT_TRUE(local_offer->candidates(kMediaContentIndex0) != NULL); EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex0)->count()); @@ -2417,42 +2589,27 @@ TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { // present in SDP. std::string sdp; EXPECT_TRUE(offer->ToString(&sdp)); - const std::string kAudioMid = "a=mid:audio"; - const std::string kAudioMidReplaceStr = "a=mid:audio_content_name"; - const std::string kVideoMid = "a=mid:video"; - const std::string kVideoMidReplaceStr = "a=mid:video_content_name"; - - // Replacing |audio| with |audio_content_name|. - rtc::replace_substrs(kAudioMid.c_str(), kAudioMid.length(), - kAudioMidReplaceStr.c_str(), - kAudioMidReplaceStr.length(), - &sdp); - // Replacing |video| with |video_content_name|. - rtc::replace_substrs(kVideoMid.c_str(), kVideoMid.length(), - kVideoMidReplaceStr.c_str(), - kVideoMidReplaceStr.length(), - &sdp); SessionDescriptionInterface* modified_offer = CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); SetRemoteDescriptionWithoutError(modified_offer); - SessionDescriptionInterface* answer = CreateAnswer(); + cricket::MediaSessionOptions answer_options; + answer_options.bundle_enabled = false; + SessionDescriptionInterface* answer = CreateAnswer(answer_options); SetLocalDescriptionWithoutError(answer); rtc::PacketTransportInternal* voice_transport_channel = session_->voice_rtp_transport_channel(); EXPECT_TRUE(voice_transport_channel != NULL); EXPECT_EQ(voice_transport_channel->debug_name(), - "audio_content_name " + - std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + "audio " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); rtc::PacketTransportInternal* video_transport_channel = session_->video_rtp_transport_channel(); ASSERT_TRUE(video_transport_channel != NULL); EXPECT_EQ(video_transport_channel->debug_name(), - "video_content_name " + - std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + "video " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); EXPECT_TRUE((video_channel_ = media_engine_->GetVideoChannel(0)) != NULL); EXPECT_TRUE((voice_channel_ = media_engine_->GetVoiceChannel(0)) != NULL); } @@ -2466,9 +2623,17 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraintsOrStreams) { ASSERT_TRUE(offer != NULL); const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_RECVONLY, + static_cast(content->description) + ->direction()); content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content == NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_RECVONLY, + static_cast(content->description) + ->direction()); } // Test that an offer contains the correct media content descriptions based on @@ -2481,17 +2646,34 @@ TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) { const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_SENDRECV, + static_cast(content->description) + ->direction()); content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content == NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_RECVONLY, + static_cast(content->description) + ->direction()); // Test Audio / Video offer. SendAudioVideoStream1(); offer.reset(CreateOffer()); content = cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_SENDRECV, + static_cast(content->description) + ->direction()); + content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content != NULL); + ASSERT_TRUE(content != NULL); + EXPECT_EQ( + cricket::MD_SENDRECV, + static_cast(content->description) + ->direction()); } // Test that an offer contains no media content descriptions if @@ -2519,6 +2701,7 @@ TEST_F(WebRtcSessionTest, CreateAudioOnlyOfferWithConstraints) { PeerConnectionInterface::RTCOfferAnswerOptions options; options.offer_to_receive_audio = RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + options.offer_to_receive_video = 0; std::unique_ptr offer(CreateOffer(options)); @@ -2553,6 +2736,8 @@ TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) { // removed. options.offer_to_receive_audio = 0; options.offer_to_receive_video = 0; + // Remove the media sections added in previous offer. + offered_media_sections_.clear(); offer.reset(CreateOffer(options)); content = cricket::GetFirstAudioContent(offer->description()); @@ -2596,6 +2781,7 @@ TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { Init(); // Create a remote offer with audio only. cricket::MediaSessionOptions options; + GetOptionsForAudioOnlyRemoteOffer(&options); std::unique_ptr offer(CreateRemoteOffer(options)); ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL); @@ -2640,8 +2826,10 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) { SetRemoteDescriptionWithoutError(offer.release()); cricket::MediaSessionOptions session_options; - session_options.recv_audio = false; - session_options.recv_video = false; + remote_send_audio_ = false; + remote_send_video_ = false; + local_recv_audio_ = false; + local_recv_video_ = false; std::unique_ptr answer( CreateAnswer(session_options)); @@ -2664,9 +2852,6 @@ TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) { SetRemoteDescriptionWithoutError(offer.release()); cricket::MediaSessionOptions options; - options.recv_audio = false; - options.recv_video = false; - // Test with a stream with tracks. SendAudioVideoStream1(); std::unique_ptr answer(CreateAnswer(options)); @@ -2726,6 +2911,11 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { SessionDescriptionInterface* offer = CreateOffer(); cricket::MediaSessionOptions options; + AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, + cricket::MD_RECVONLY, kActive, &options); + AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::MD_INACTIVE, kStopped, &options); + local_recv_video_ = false; SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options); // SetLocalDescription and SetRemoteDescriptions takes ownership of offer @@ -2736,7 +2926,7 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { video_channel_ = media_engine_->GetVideoChannel(0); voice_channel_ = media_engine_->GetVoiceChannel(0); - ASSERT_TRUE(video_channel_ == NULL); + ASSERT_TRUE(video_channel_ == nullptr); ASSERT_EQ(0u, voice_channel_->recv_streams().size()); ASSERT_EQ(1u, voice_channel_->send_streams().size()); @@ -2744,13 +2934,14 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { // Let the remote end update the session descriptions, with Audio and Video. SendAudioVideoStream2(); + local_recv_video_ = true; CreateAndSetRemoteOfferAndLocalAnswer(); video_channel_ = media_engine_->GetVideoChannel(0); voice_channel_ = media_engine_->GetVoiceChannel(0); - ASSERT_TRUE(video_channel_ != NULL); - ASSERT_TRUE(voice_channel_ != NULL); + ASSERT_TRUE(video_channel_ != nullptr); + ASSERT_TRUE(voice_channel_ != nullptr); ASSERT_EQ(1u, video_channel_->recv_streams().size()); ASSERT_EQ(1u, video_channel_->send_streams().size()); @@ -2762,10 +2953,17 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id); // Change session back to audio only. + // The remote side doesn't send and recv video. SendAudioOnlyStream2(); + remote_recv_video_ = false; CreateAndSetRemoteOfferAndLocalAnswer(); - EXPECT_EQ(0u, video_channel_->recv_streams().size()); + video_channel_ = media_engine_->GetVideoChannel(0); + voice_channel_ = media_engine_->GetVoiceChannel(0); + + // The audio is expected to be rejected. + EXPECT_TRUE(video_channel_ == nullptr); + ASSERT_EQ(1u, voice_channel_->recv_streams().size()); EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id); ASSERT_EQ(1u, voice_channel_->send_streams().size()); @@ -2782,10 +2980,13 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) { SessionDescriptionInterface* offer = CreateOffer(); cricket::MediaSessionOptions options; - options.recv_audio = false; - options.recv_video = true; - SessionDescriptionInterface* answer = CreateRemoteAnswer( - offer, options, cricket::SEC_ENABLED); + AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, + cricket::MD_INACTIVE, kStopped, &options); + AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::MD_RECVONLY, kActive, &options); + local_recv_audio_ = false; + SessionDescriptionInterface* answer = + CreateRemoteAnswer(offer, options, cricket::SEC_ENABLED); // SetLocalDescription and SetRemoteDescriptions takes ownership of offer // and answer. @@ -2804,23 +3005,41 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) { // Update the session descriptions, with Audio and Video. SendAudioVideoStream2(); - CreateAndSetRemoteOfferAndLocalAnswer(); + local_recv_audio_ = true; + SessionDescriptionInterface* offer2 = CreateRemoteOffer(); + SetRemoteDescriptionWithoutError(offer2); + cricket::MediaSessionOptions answer_options; + // Disable the bundling here. If the media is bundled on audio + // transport, then we can't reject the audio because switching the bundled + // transport is not currently supported. + // (https://bugs.chromium.org/p/webrtc/issues/detail?id=6704) + answer_options.bundle_enabled = false; + SessionDescriptionInterface* answer2 = CreateAnswer(answer_options); + SetLocalDescriptionWithoutError(answer2); voice_channel_ = media_engine_->GetVoiceChannel(0); - ASSERT_TRUE(voice_channel_ != NULL); + ASSERT_TRUE(voice_channel_ != NULL); ASSERT_EQ(1u, voice_channel_->recv_streams().size()); ASSERT_EQ(1u, voice_channel_->send_streams().size()); EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id); EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id); // Change session back to video only. + // The remote side doesn't send and recv audio. SendVideoOnlyStream2(); - CreateAndSetRemoteOfferAndLocalAnswer(); + remote_recv_audio_ = false; + SessionDescriptionInterface* offer3 = CreateRemoteOffer(); + SetRemoteDescriptionWithoutError(offer3); + SessionDescriptionInterface* answer3 = CreateAnswer(answer_options); + SetLocalDescriptionWithoutError(answer3); video_channel_ = media_engine_->GetVideoChannel(0); voice_channel_ = media_engine_->GetVoiceChannel(0); + // The video is expected to be rejected. + EXPECT_TRUE(voice_channel_ == nullptr); + ASSERT_EQ(1u, video_channel_->recv_streams().size()); EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id); ASSERT_EQ(1u, video_channel_->send_streams().size()); @@ -3025,13 +3244,16 @@ TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); SendAudioVideoStream1(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; + cricket::MediaSessionOptions offer_options; + GetOptionsForRemoteOffer(&offer_options); + offer_options.bundle_enabled = true; - SessionDescriptionInterface* offer = CreateRemoteOffer(); + SessionDescriptionInterface* offer = CreateRemoteOffer(offer_options); SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = CreateAnswer(); + cricket::MediaSessionOptions answer_options; + answer_options.bundle_enabled = true; + SessionDescriptionInterface* answer = CreateAnswer(answer_options); SetLocalDescriptionWithoutError(answer); EXPECT_EQ(session_->voice_rtp_transport_channel(), @@ -3196,10 +3418,11 @@ TEST_F(WebRtcSessionTest, TestMaxBundleRejectAudio) { EXPECT_EQ(session_->voice_rtp_transport_channel(), session_->video_rtp_transport_channel()); - SendAudioVideoStream2(); + SendVideoOnlyStream2(); + local_send_audio_ = false; + remote_recv_audio_ = false; cricket::MediaSessionOptions recv_options; - recv_options.recv_audio = false; - recv_options.recv_video = true; + GetOptionsForRemoteAnswer(&recv_options); SessionDescriptionInterface* answer = CreateRemoteAnswer(session_->local_description(), recv_options); SetRemoteDescriptionWithoutError(answer); @@ -3286,10 +3509,10 @@ TEST_F(WebRtcSessionTest, TestMaxCompatBundleInAnswer) { InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat); SendAudioVideoStream1(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; + PeerConnectionInterface::RTCOfferAnswerOptions rtc_options; + rtc_options.use_rtp_mux = true; - SessionDescriptionInterface* offer = CreateOffer(options); + SessionDescriptionInterface* offer = CreateOffer(rtc_options); SetLocalDescriptionWithoutError(offer); EXPECT_NE(session_->voice_rtp_transport_channel(), @@ -3645,7 +3868,7 @@ TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) { TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { Init(); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); std::unique_ptr offer(CreateRemoteOffer(options)); SetRemoteDescriptionWithoutError(offer.release()); @@ -3654,10 +3877,10 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { SetLocalDescriptionWithoutError(answer.release()); // Receive an offer with new ufrag and password. - for (const cricket::ContentInfo& content : - session_->local_description()->description()->contents()) { - options.transport_options[content.name].ice_restart = true; + for (size_t i = 0; i < options.media_description_options.size(); ++i) { + options.media_description_options[i].transport_options.ice_restart = true; } + std::unique_ptr updated_offer1( CreateRemoteOffer(options, session_->remote_description())); SetRemoteDescriptionWithoutError(updated_offer1.release()); @@ -3685,8 +3908,7 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { TEST_F(WebRtcSessionTest, TestOfferChangingOnlyUfragOrPassword) { Init(); cricket::MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); // Create an offer with audio and video. std::unique_ptr offer(CreateRemoteOffer(options)); SetIceUfragPwd(offer.get(), "original_ufrag", "original_password12345"); @@ -3726,7 +3948,7 @@ TEST_F(WebRtcSessionTest, TestOfferChangingOnlyUfragOrPassword) { TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) { Init(); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); std::unique_ptr offer(CreateRemoteOffer(options)); SetRemoteDescriptionWithoutError(offer.release()); @@ -3753,8 +3975,7 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) { TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewAndOldUfragAndPassword) { Init(); cricket::MediaSessionOptions options; - options.recv_video = true; - options.recv_audio = true; + GetOptionsForRemoteOffer(&options); options.bundle_enabled = false; std::unique_ptr offer(CreateRemoteOffer(options)); @@ -3886,9 +4107,9 @@ TEST_P(WebRtcSessionTest, SctpContentAndTransportName) { // Create answer that finishes BUNDLE negotiation, which means everything // should be bundled on the first transport (audio). cricket::MediaSessionOptions answer_options; - answer_options.recv_video = true; answer_options.bundle_enabled = true; answer_options.data_channel_type = cricket::DCT_SCTP; + GetOptionsForAnswer(&answer_options); SetRemoteDescriptionWithoutError(CreateRemoteAnswer( session_->local_description(), answer_options, cricket::SEC_DISABLED)); ASSERT_TRUE(session_->sctp_content_name()); @@ -3912,6 +4133,7 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithSctpInOfferAndNoStreams) { // Create remote offer with SCTP. cricket::MediaSessionOptions options; options.data_channel_type = cricket::DCT_SCTP; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); SetRemoteDescriptionWithoutError(offer); @@ -4055,7 +4277,7 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerBeforeIdentityRequestReturnSuccess) { SetFactoryDtlsSrtp(); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); std::unique_ptr offer( CreateRemoteOffer(options, cricket::SEC_DISABLED)); ASSERT_TRUE(offer.get() != NULL); @@ -4129,6 +4351,7 @@ TEST_F(WebRtcSessionTest, TestSetRemoteOfferFailIfDtlsDisabledAndNoCrypto) { Init(); // Create a remote offer with secured transport disabled. cricket::MediaSessionOptions options; + GetOptionsForRemoteOffer(&options); JsepSessionDescription* offer(CreateRemoteOffer( options, cricket::SEC_DISABLED)); // Adds a DTLS fingerprint to the remote offer. @@ -4172,7 +4395,7 @@ TEST_P(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesInSdp) { SetRemoteDescriptionWithoutError(answer); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); cricket::Candidate candidate1; @@ -4201,7 +4424,7 @@ TEST_P(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesSeparated) { SetRemoteDescriptionWithoutError(answer); cricket::MediaSessionOptions options; - options.recv_video = true; + GetOptionsForRemoteOffer(&options); offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); SetRemoteDescriptionWithoutError(offer); @@ -4226,8 +4449,7 @@ TEST_P(WebRtcSessionTest, TestNegotiateQuic) { ASSERT_TRUE(offer->description()); SetLocalDescriptionWithoutError(offer); cricket::MediaSessionOptions options; - options.recv_audio = true; - options.recv_video = true; + GetOptionsForAnswer(&options); SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); ASSERT_TRUE(answer); @@ -4240,7 +4462,8 @@ TEST_P(WebRtcSessionTest, TestNegotiateQuic) { // by local side. TEST_F(WebRtcSessionTest, TestRtxRemovedByCreateAnswer) { Init(); - SendAudioVideoStream1(); + // Send video only to match the |kSdpWithRtx|. + SendVideoOnlyStream2(); std::string offer_sdp(kSdpWithRtx); SessionDescriptionInterface* offer = @@ -4251,6 +4474,11 @@ TEST_F(WebRtcSessionTest, TestRtxRemovedByCreateAnswer) { EXPECT_TRUE(ContainsVideoCodecWithName(offer, "rtx")); SetRemoteDescriptionWithoutError(offer); + // |offered_media_sections_| is used when creating answer. + offered_media_sections_.push_back(cricket::MediaDescriptionOptions( + cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, + cricket::RtpTransceiverDirection(true, true), false)); + // Don't create media section for audio in the answer. SessionDescriptionInterface* answer = CreateAnswer(); // Answer SDP does not contain the RTX codec. EXPECT_FALSE(ContainsVideoCodecWithName(answer, "rtx")); @@ -4316,8 +4544,7 @@ TEST_F(WebRtcSessionTest, CreateOffersAndShutdown) { options.offer_to_receive_audio = RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; cricket::MediaSessionOptions session_options; - session_options.recv_audio = true; - + GetOptionsForOffer(options, &session_options); for (auto& o : observers) { o = new WebRtcSessionCreateSDPObserverForTest(); session_->CreateOffer(o, options, session_options); diff --git a/webrtc/pc/webrtcsessiondescriptionfactory.cc b/webrtc/pc/webrtcsessiondescriptionfactory.cc index 8eccd65767..beb8d1e5a1 100644 --- a/webrtc/pc/webrtcsessiondescriptionfactory.cc +++ b/webrtc/pc/webrtcsessiondescriptionfactory.cc @@ -30,24 +30,30 @@ static const char kFailedDueToSessionShutdown[] = static const uint64_t kInitSessionVersion = 2; -static bool CompareStream(const MediaSessionOptions::Stream& stream1, - const MediaSessionOptions::Stream& stream2) { - return stream1.id < stream2.id; +static bool CompareSenderOptions(const cricket::SenderOptions& sender1, + const cricket::SenderOptions& sender2) { + return sender1.track_id < sender2.track_id; } -static bool SameId(const MediaSessionOptions::Stream& stream1, - const MediaSessionOptions::Stream& stream2) { - return stream1.id == stream2.id; +static bool SameId(const cricket::SenderOptions& sender1, + const cricket::SenderOptions& sender2) { + return sender1.track_id == sender2.track_id; } -// Checks if each Stream within the |streams| has unique id. -static bool ValidStreams(const MediaSessionOptions::Streams& streams) { - MediaSessionOptions::Streams sorted_streams = streams; - std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream); - MediaSessionOptions::Streams::iterator it = - std::adjacent_find(sorted_streams.begin(), sorted_streams.end(), - SameId); - return it == sorted_streams.end(); +// Check that each sender has a unique ID. +static bool ValidMediaSessionOptions( + const cricket::MediaSessionOptions& session_options) { + std::vector sorted_senders; + for (const cricket::MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + sorted_senders.insert(sorted_senders.end(), + media_description_options.sender_options.begin(), + media_description_options.sender_options.end()); + } + std::sort(sorted_senders.begin(), sorted_senders.end(), CompareSenderOptions); + std::vector::iterator it = + std::adjacent_find(sorted_senders.begin(), sorted_senders.end(), SameId); + return it == sorted_senders.end(); } enum { @@ -128,7 +134,6 @@ WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory( session_id_(session_id), certificate_request_state_(CERTIFICATE_NOT_NEEDED) { RTC_DCHECK(signaling_thread_); - session_desc_factory_.set_add_legacy_streams(false); bool dtls_enabled = cert_generator_ || certificate; // SRTP-SDES is disabled if DTLS is on. SetSdesPolicy(dtls_enabled ? cricket::SEC_DISABLED : cricket::SEC_REQUIRED); @@ -237,8 +242,8 @@ void WebRtcSessionDescriptionFactory::CreateOffer( return; } - if (!ValidStreams(session_options.streams)) { - error += " called with invalid media streams."; + if (!ValidMediaSessionOptions(session_options)) { + error += " called with invalid session options"; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailed(observer, error); return; @@ -279,8 +284,8 @@ void WebRtcSessionDescriptionFactory::CreateAnswer( return; } - if (!ValidStreams(session_options.streams)) { - error += " called with invalid media streams."; + if (!ValidMediaSessionOptions(session_options)) { + error += " called with invalid session options."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailed(observer, error); return; @@ -340,13 +345,12 @@ void WebRtcSessionDescriptionFactory::OnMessage(rtc::Message* msg) { void WebRtcSessionDescriptionFactory::InternalCreateOffer( CreateSessionDescriptionRequest request) { if (session_->local_description()) { - for (const cricket::TransportInfo& transport : - session_->local_description()->description()->transport_infos()) { - // If the needs-ice-restart flag is set as described by JSEP, we should - // generate an offer with a new ufrag/password to trigger an ICE restart. - if (session_->NeedsIceRestart(transport.content_name)) { - request.options.transport_options[transport.content_name].ice_restart = - true; + // If the needs-ice-restart flag is set as described by JSEP, we should + // generate an offer with a new ufrag/password to trigger an ICE restart. + for (cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (session_->NeedsIceRestart(options.mid)) { + options.transport_options.ice_restart = true; } } } @@ -375,13 +379,11 @@ void WebRtcSessionDescriptionFactory::InternalCreateOffer( return; } if (session_->local_description()) { - for (const cricket::ContentInfo& content : - session_->local_description()->description()->contents()) { - // Include all local ICE candidates in the SessionDescription unless - // an ICE restart was requested. - if (!request.options.transport_options[content.name].ice_restart) { + for (const cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (!options.transport_options.ice_restart) { CopyCandidatesFromSessionDescription(session_->local_description(), - content.name, offer); + options.mid, offer); } } } @@ -391,18 +393,18 @@ void WebRtcSessionDescriptionFactory::InternalCreateOffer( void WebRtcSessionDescriptionFactory::InternalCreateAnswer( CreateSessionDescriptionRequest request) { if (session_->remote_description()) { - for (const cricket::ContentInfo& content : - session_->remote_description()->description()->contents()) { + for (cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1 // an answer should also contain new ICE ufrag and password if an offer // has been received with new ufrag and password. - request.options.transport_options[content.name].ice_restart = - session_->IceRestartPending(content.name); + options.transport_options.ice_restart = + session_->IceRestartPending(options.mid); // We should pass the current SSL role to the transport description // factory, if there is already an existing ongoing session. rtc::SSLRole ssl_role; - if (session_->GetSslRole(content.name, &ssl_role)) { - request.options.transport_options[content.name].prefer_passive_role = + if (session_->GetSslRole(options.mid, &ssl_role)) { + options.transport_options.prefer_passive_role = (rtc::SSL_SERVER == ssl_role); } } @@ -433,13 +435,13 @@ void WebRtcSessionDescriptionFactory::InternalCreateAnswer( return; } if (session_->local_description()) { - for (const cricket::ContentInfo& content : - session_->local_description()->description()->contents()) { - // Include all local ICE candidates in the SessionDescription unless - // the remote peer has requested an ICE restart. - if (!request.options.transport_options[content.name].ice_restart) { + // Include all local ICE candidates in the SessionDescription unless + // the remote peer has requested an ICE restart. + for (const cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (!options.transport_options.ice_restart) { CopyCandidatesFromSessionDescription(session_->local_description(), - content.name, answer); + options.mid, answer); } } }