Chromium Code Reviews| Index: webrtc/pc/mediasession.cc | 
| diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc | 
| index de8f72c92612714268570a7a2403e2c6d16827c1..c32895be73efd7a3759e80078767f071ff8197a0 100644 | 
| --- a/webrtc/pc/mediasession.cc | 
| +++ b/webrtc/pc/mediasession.cc | 
| @@ -188,11 +188,14 @@ bool CreateMediaCryptos(const std::vector<std::string>& 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<const MediaContentDescription*>(content->description) | 
| + ->cryptos()); | 
| } | 
| bool FindMatchingCrypto(const CryptoParamsVec& cryptos, | 
| @@ -428,15 +431,15 @@ class UsedRtpHeaderExtensionIds : public UsedIds<webrtc::RtpExtension> { | 
| 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 <class C> | 
| -static bool AddStreamParams(MediaType media_type, | 
| - const MediaSessionOptions& options, | 
| - StreamParamsVec* current_streams, | 
| - MediaContentDescriptionImpl<C>* content_description, | 
| - const bool add_legacy_stream) { | 
| +static bool AddStreamParams( | 
| + const std::vector<SenderOptions>& sender_options, | 
| + const std::string& rtcp_cname, | 
| + StreamParamsVec* current_streams, | 
| + MediaContentDescriptionImpl<C>* 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<uint32_t> 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<uint32_t> 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 <class C> | 
| static bool CreateMediaContentOffer( | 
| - const MediaSessionOptions& options, | 
| + const std::vector<SenderOptions>& sender_options, | 
| + const MediaSessionOptions& session_options, | 
| const std::vector<C>& codecs, | 
| const SecurePolicy& secure_policy, | 
| const CryptoParamsVec* current_cryptos, | 
| const std::vector<std::string>& crypto_suites, | 
| const RtpHeaderExtensions& rtp_extensions, | 
| - bool add_legacy_stream, | 
| StreamParamsVec* current_streams, | 
| MediaContentDescriptionImpl<C>* 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<C>& 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 <class C> | 
| +static const C* GetAssociatedCodec(const std::vector<C>& 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 <class C> | 
| -static void FindCodecsToOffer( | 
| - const std::vector<C>& reference_codecs, | 
| - std::vector<C>* offered_codecs, | 
| - UsedPayloadTypes* used_pltypes) { | 
| - | 
| +static void MergeCodecs(const std::vector<C>& reference_codecs, | 
| + std::vector<C>* 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<C>(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 <class C> | 
| static bool CreateMediaContentAnswer( | 
| const MediaContentDescriptionImpl<C>* offer, | 
| - const MediaSessionOptions& options, | 
| + const MediaDescriptionOptions& media_description_options, | 
| + const MediaSessionOptions& session_options, | 
| const std::vector<C>& 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<C>* answer) { | 
| std::vector<C> 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,19 @@ 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) | 
| - return false; | 
| - | 
| - const ContentInfo* content = | 
| - current_description->GetContentByName(content_name); | 
| - if (!content) | 
| +static bool IsDtlsActive(const ContentInfo* content, | 
| + const SessionDescription* current_description) { | 
| + if (!content) { | 
| return false; | 
| + } | 
| - const TransportDescription* current_tdesc = | 
| - GetTransportDescription(content_name, current_description); | 
| - if (!current_tdesc) | 
| + if (current_description->transport_infos().size() <= | 
| + content->msection_index) { | 
| return false; | 
| + } | 
| - return current_tdesc->secure(); | 
| + return current_description->transport_infos()[content->msection_index] | 
| + .description.secure(); | 
| } | 
| std::string MediaContentDirectionToString(MediaContentDirection direction) { | 
| @@ -1281,75 +1249,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 +1316,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<SessionDescription> 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 audio_codecs; | 
| - VideoCodecs video_codecs; | 
| - DataCodecs data_codecs; | 
| - GetCodecsToOffer(current_description, supported_audio_codecs, | 
| - video_codecs_, data_codecs_, | 
| - &audio_codecs, &video_codecs, &data_codecs); | 
| + AudioCodecs offer_audio_codecs; | 
| + VideoCodecs offer_video_codecs; | 
| + DataCodecs offer_data_codecs; | 
| + GetCodecsForOffer(current_description, &offer_audio_codecs, | 
| + &offer_video_codecs, &offer_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, | 
| + RTC_DCHECK(current_description->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* current_content = nullptr; | 
| + if (current_description && | 
| + msection_index < | 
| + static_cast<int>(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 NULL; | 
| + return nullptr; | 
| } | 
| - audio_added = true; | 
| - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { | 
| - if (!AddVideoContentForOffer(options, current_description, | 
| - video_rtp_extensions, video_codecs, | 
| + 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 NULL; | 
| - } | 
| - video_added = true; | 
| - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) { | 
| - MediaSessionOptions options_copy(options); | 
| - if (IsSctp(static_cast<const MediaContentDescription*>(it->description) | 
| - ->protocol())) { | 
| - options_copy.data_channel_type = DCT_SCTP; | 
| + return nullptr; | 
| } | 
| - if (!AddDataContentForOffer(options_copy, current_description, | 
| - &data_codecs, ¤t_streams, | 
| + break; | 
| + case MEDIA_TYPE_DATA: | 
| + if (!AddDataContentForOffer(media_description_options, session_options, | 
| + current_content, current_description, | 
| + offer_data_codecs, ¤t_streams, | 
| offer.get())) { | 
| - return NULL; | 
| + return nullptr; | 
| } | 
| - data_added = true; | 
| - } else { | 
| + break; | 
| + default: | 
| RTC_NOTREACHED(); | 
| - } | 
| } | 
| - } | 
| - | 
| - // 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; | 
| + ++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 +1443,81 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( | 
| // Transport info shared by the bundle group. | 
| std::unique_ptr<TransportInfo> 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<int>(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 +1577,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<AudioContentDescription*>(content.description); | 
| + MergeCodecs<AudioCodec>(audio->codecs(), audio_codecs, used_pltypes); | 
| + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) { | 
| + const VideoContentDescription* video = | 
| + static_cast<VideoContentDescription*>(content.description); | 
| + MergeCodecs<VideoCodec>(video->codecs(), video_codecs, used_pltypes); | 
| + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) { | 
| + const DataContentDescription* data = | 
| + static_cast<DataContentDescription*>(content.description); | 
| + MergeCodecs<DataCodec>(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 +1616,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 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); | 
| + } | 
| + | 
| + // Add our codecs that are not in |current_description|. | 
| + MergeCodecs<AudioCodec>(all_audio_codecs_, audio_codecs, &used_pltypes); | 
| + MergeCodecs<VideoCodec>(video_codecs_, video_codecs, &used_pltypes); | 
| + MergeCodecs<DataCodec>(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 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<AudioCodec>(audio_codecs); | 
| - } | 
| - const VideoContentDescription* video = | 
| - GetFirstVideoContentDescription(current_description); | 
| - if (video) { | 
| - *video_codecs = video->codecs(); | 
| - used_pltypes.FindAndSetIdUsed<VideoCodec>(video_codecs); | 
| - } | 
| - const DataContentDescription* data = | 
| - GetFirstDataContentDescription(current_description); | 
| - if (data) { | 
| - *data_codecs = data->codecs(); | 
| - used_pltypes.FindAndSetIdUsed<DataCodec>(data_codecs); | 
| + 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<AudioContentDescription*>(content.description); | 
| + for (const AudioCodec& offered_audio_codec : audio->codecs()) { | 
| + if (!FindMatchingCodec<AudioCodec>(audio->codecs(), | 
| + filtered_offered_audio_codecs, | 
| + offered_audio_codec, nullptr) && | 
| + FindMatchingCodec<AudioCodec>(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<VideoContentDescription*>(content.description); | 
| + for (const VideoCodec& offered_video_codec : video->codecs()) { | 
| + if (!FindMatchingCodec<VideoCodec>(video->codecs(), | 
| + filtered_offered_video_codecs, | 
| + offered_video_codec, nullptr) && | 
| + FindMatchingCodec<VideoCodec>(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<DataContentDescription*>(content.description); | 
| + for (const DataCodec& offered_data_codec : data->codecs()) { | 
| + if (!FindMatchingCodec<DataCodec>(data->codecs(), | 
| + filtered_offered_data_codecs, | 
| + offered_data_codec, nullptr) && | 
| + FindMatchingCodec<DataCodec>(data->codecs(), data_codecs_, | 
| + offered_data_codec, nullptr)) { | 
| + filtered_offered_data_codecs.push_back(offered_data_codec); | 
| + } | 
| + } | 
| } | 
| } | 
| - // Add our codecs that are not in |current_description|. | 
| - FindCodecsToOffer<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| - &used_pltypes); | 
| - FindCodecsToOffer<VideoCodec>(supported_video_codecs, video_codecs, | 
| - &used_pltypes); | 
| - FindCodecsToOffer<DataCodec>(supported_data_codecs, data_codecs, | 
| - &used_pltypes); | 
| + // Add codecs that are not in |current_description| but were in | 
| + // |remote_offer|. | 
| + MergeCodecs<AudioCodec>(filtered_offered_audio_codecs, audio_codecs, | 
| + &used_pltypes); | 
| + MergeCodecs<VideoCodec>(filtered_offered_video_codecs, video_codecs, | 
| + &used_pltypes); | 
| + MergeCodecs<DataCodec>(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<const AudioContentDescription*>(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<const VideoContentDescription*>(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); | 
| } | 
| } | 
| @@ -1744,34 +1814,64 @@ bool MediaSessionDescriptionFactory::AddTransportAnswer( | 
| } | 
| 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, | 
| 
 
Taylor Brandstetter
2017/08/09 17:05:12
As we talked about earlier, it would be helpful to
 
Zhi Huang
2017/08/09 21:51:02
Done.
 
 | 
| 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; | 
| + AudioCodec existing_codec; | 
| + // Add the codecs from current content if exists. | 
| + if (current_content) { | 
| + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); | 
| + const AudioContentDescription* acd = | 
| + static_cast<const AudioContentDescription*>( | 
| + current_content->description); | 
| + for (const AudioCodec& codec : acd->codecs()) { | 
| + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| + codec, &existing_codec)) { | 
| 
 
Taylor Brandstetter
2017/08/09 17:05:12
Based on the description of FindMatchingCodec, I t
 
Zhi Huang
2017/08/09 21:51:02
Done.
 
 | 
| + if (current_description) { | 
| 
 
Taylor Brandstetter
2017/08/09 17:05:12
I still don't understand the "if (current_descript
 
Zhi Huang
2017/08/09 21:51:02
Done.
 
 | 
| + filtered_codecs.push_back(existing_codec); | 
| + } else { | 
| + filtered_codecs.push_back(codec); | 
| + } | 
| + } | 
| + } | 
| + } | 
| + // Add other supported audio codecs. | 
| + for (const AudioCodec& codec : supported_audio_codecs) { | 
| + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| + codec, &existing_codec) && | 
| 
 
Taylor Brandstetter
2017/08/09 17:05:12
nit: "existing_codec" isn't a great name, since it
 
Zhi Huang
2017/08/09 21:51:02
Maybe just call it "found_codec"? I'm not good at
 
 | 
| + !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs, | 
| + codec, nullptr)) { | 
| + if (current_description) { | 
| + filtered_codecs.push_back(existing_codec); | 
| 
 
Taylor Brandstetter
2017/08/09 17:05:12
Again, why is the "if (current_description)" neede
 
Zhi Huang
2017/08/09 21:51:02
Done.
 
 | 
| + } else { | 
| + filtered_codecs.push_back(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<AudioContentDescription> audio(new AudioContentDescription()); | 
| std::vector<std::string> 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 +1879,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 +1894,70 @@ 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<VideoContentDescription> video(new VideoContentDescription()); | 
| std::vector<std::string> crypto_suites; | 
| - GetSupportedVideoSdesCryptoSuiteNames(options.crypto_options, &crypto_suites); | 
| + GetSupportedVideoSdesCryptoSuiteNames(session_options.crypto_options, | 
| + &crypto_suites); | 
| 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, | 
| + video_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<DataContentDescription> 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<const DataContentDescription*>( | 
| + 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<std::string> crypto_suites; | 
| if (is_sctp) { | 
| // SDES doesn't make sense for SCTP, so we disable it, and we only | 
| @@ -1890,32 +1973,29 @@ 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; | 
| } | 
| @@ -1923,194 +2003,216 @@ bool MediaSessionDescriptionFactory::AddDataContentForOffer( | 
| } | 
| 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<const AudioContentDescription*>(audio_content->description); | 
| + const AudioContentDescription* offer_audio_description = | 
| + static_cast<const AudioContentDescription*>(offer_content->description); | 
| std::unique_ptr<TransportDescription> 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; | 
| + AudioCodec existing_codec; | 
| + // Add the codecs from current content if exists. | 
| + if (current_content) { | 
| + RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); | 
| + const AudioContentDescription* acd = | 
| + static_cast<const AudioContentDescription*>( | 
| + current_content->description); | 
| + for (const AudioCodec& codec : acd->codecs()) { | 
| + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| + codec, &existing_codec)) { | 
| + if (current_description) { | 
| + filtered_codecs.push_back(existing_codec); | 
| + } else { | 
| + filtered_codecs.push_back(codec); | 
| + } | 
| + } | 
| + } | 
| + } | 
| + | 
| + // Add other supported audio codecs. | 
| + for (const AudioCodec& codec : supported_audio_codecs) { | 
| + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| + codec, &existing_codec) && | 
| + !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs, | 
| + codec, nullptr)) { | 
| + if (current_description) { | 
| + filtered_codecs.push_back(existing_codec); | 
| + } else { | 
| + filtered_codecs.push_back(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<AudioContentDescription> 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<const VideoContentDescription*>(offer_content->description); | 
| + | 
| std::unique_ptr<TransportDescription> 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; | 
| } | 
| + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && | 
| + session_options.bundle_enabled; | 
| + | 
| std::unique_ptr<VideoContentDescription> 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<const VideoContentDescription*>( | 
| - 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, | 
| + video_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<TransportDescription> 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<DataCodec> data_codecs(data_codecs_); | 
| - FilterDataCodecs(&data_codecs, is_sctp); | 
| std::unique_ptr<DataContentDescription> 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<const DataContentDescription*>( | 
| - 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<const DataContentDescription*>(data_content->description); | 
| + static_cast<const DataContentDescription*>(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 = 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 +2220,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<AudioCodec>(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<AudioCodec>(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 || |