| Index: webrtc/pc/mediasession.cc | 
| diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc | 
| index de8f72c92612714268570a7a2403e2c6d16827c1..4d4744aeb0f369994bc0e18deb0d49f5a07aa414 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,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) | 
| -    return false; | 
| +  size_t msection_index = content - ¤t_description->contents()[0]; | 
|  | 
| -  const TransportDescription* current_tdesc = | 
| -      GetTransportDescription(content_name, current_description); | 
| -  if (!current_tdesc) | 
| +  if (current_description->transport_infos().size() <= msection_index) { | 
| 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<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; | 
| +          return nullptr; | 
| } | 
| -        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; | 
| -        } | 
| -        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 +1444,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 +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<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 +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 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); | 
| } | 
| } | 
|  | 
| @@ -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<const AudioContentDescription*>( | 
| +            current_content->description); | 
| +    for (const AudioCodec& codec : acd->codecs()) { | 
| +      if (FindMatchingCodec<AudioCodec>(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<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| +                                      codec, &found_codec) && | 
| +        !FindMatchingCodec<AudioCodec>(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<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 +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<VideoContentDescription> video(new VideoContentDescription()); | 
| std::vector<std::string> 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<const VideoContentDescription*>( | 
| +            current_content->description); | 
| +    for (const VideoCodec& codec : vcd->codecs()) { | 
| +      if (FindMatchingCodec<VideoCodec>(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<VideoCodec>(video_codecs_, video_codecs, codec, | 
| +                                      &found_codec) && | 
| +        !FindMatchingCodec<VideoCodec>(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<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,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<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; | 
| +  // 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, nullptr)) { | 
| +        filtered_codecs.push_back(codec); | 
| +      } | 
| +    } | 
| +  } | 
| +  // Add other supported audio codecs. | 
| +  AudioCodec found_codec; | 
| +  for (const AudioCodec& codec : supported_audio_codecs) { | 
| +    if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, | 
| +                                      codec, &found_codec) && | 
| +        !FindMatchingCodec<AudioCodec>(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<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; | 
| } | 
|  | 
| +  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<const VideoContentDescription*>( | 
| +            current_content->description); | 
| +    for (const VideoCodec& codec : vcd->codecs()) { | 
| +      if (FindMatchingCodec<VideoCodec>(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<VideoCodec>(video_codecs_, video_codecs, codec, | 
| +                                      &found_codec) && | 
| +        !FindMatchingCodec<VideoCodec>(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<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, | 
| +          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<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 = 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<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 || | 
|  |