[PT Redesign] Implement late payload type allocation for video. This CL implements the late payload type allocation strategy for video codecs, matching the existing implementation for audio. Key changes: - Introduced CodecConfiguration and ResiliencyInfo to track codec metadata without pre-assigned payload types. - Implemented MergeCodecsFromConfigurations in CodecVendor to expand media codecs into their resiliency counterparts (RTX, RED, FEC) and link them only after primary PTs are suggested. - Extracted MergeCodecsByDirection to unify direction-based merging logic across offer and answer paths. - Relaxed RTX matching in CodecComparators to support negotiation before PTs are fully linked. - Updated all call sites in pc/ to handle refactored CodecVendor accessors. - Added integration tests in pc/codec_vendor_redesign_unittest.cc. This work allows payload types to be assigned permanently during SetLocalDescription/SetRemoteDescription, improving stability across renegotiations. Bug: webrtc:360058654 Change-Id: Ifae3f3daca1b18a3ada2131a57046ce4ff3dcd36 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/472060 Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org> Commit-Queue: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47709}
diff --git a/g3doc/todo/payload_type_redesign.md b/g3doc/todo/payload_type_redesign.md index fd11fedf..3f3ea16 100644 --- a/g3doc/todo/payload_type_redesign.md +++ b/g3doc/todo/payload_type_redesign.md
@@ -1,5 +1,7 @@ # Payload type allocation redesign +The bug associated with this work is webrtc:360058654 + ## Background: What a payload type is The payload type is a property of a codec that is established between a sender
diff --git a/media/base/codec_comparators.cc b/media/base/codec_comparators.cc index f088fc4..859cf43 100644 --- a/media/base/codec_comparators.cc +++ b/media/base/codec_comparators.cc
@@ -11,7 +11,6 @@ #include <algorithm> #include <cstddef> -#include <cstdint> #include <optional> #include <string> #include <vector> @@ -32,7 +31,6 @@ #include "media/base/codec.h" #include "media/base/media_constants.h" #include "rtc_base/checks.h" -#include "rtc_base/logging.h" #include "rtc_base/string_encode.h" namespace webrtc { @@ -200,21 +198,18 @@ if (resiliency_type == Codec::ResiliencyType::kRtx) { int apt_value_1_int = 0; int apt_value_2_int = 0; - if (!codec_to_match.GetParam(kCodecParamAssociatedPayloadType, - &apt_value_1_int) || - !potential_match.GetParam(kCodecParamAssociatedPayloadType, - &apt_value_2_int)) { - RTC_LOG(LS_WARNING) << "RTX missing associated payload type."; - return false; - } - PayloadType apt_value_1 = - PayloadType(static_cast<uint8_t>(apt_value_1_int)); - PayloadType apt_value_2 = - PayloadType(static_cast<uint8_t>(apt_value_2_int)); - if (reference_comparator(apt_value_1, apt_value_2)) { + bool has_apt_1 = codec_to_match.GetParam(kCodecParamAssociatedPayloadType, + &apt_value_1_int); + bool has_apt_2 = potential_match.GetParam(kCodecParamAssociatedPayloadType, + &apt_value_2_int); + if (!has_apt_1 && !has_apt_2) { return true; } - return false; + if (!has_apt_1 || !has_apt_2) { + return false; + } + return reference_comparator(PayloadType(apt_value_1_int), + PayloadType(apt_value_2_int)); } if (resiliency_type == Codec::ResiliencyType::kRed) { auto red_parameters_1 =
diff --git a/pc/BUILD.gn b/pc/BUILD.gn index e17db1e..8054c46 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn
@@ -429,6 +429,7 @@ "../rtc_base:macromagic", "../rtc_base:stringutils", "../rtc_base:threading", + "../rtc_base/containers:flat_map", "../rtc_base/system:no_unique_address", "//third_party/abseil-cpp/absl/algorithm:container", "//third_party/abseil-cpp/absl/base:nullability",
diff --git a/pc/codec_vendor.cc b/pc/codec_vendor.cc index 16a791b..96b3ebe 100644 --- a/pc/codec_vendor.cc +++ b/pc/codec_vendor.cc
@@ -10,16 +10,13 @@ #include "pc/codec_vendor.h" #include <cstddef> -#include <map> #include <optional> #include <string> -#include <unordered_map> #include <utility> #include <vector> #include "absl/algorithm/container.h" #include "absl/strings/match.h" -#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "api/field_trials_view.h" #include "api/media_types.h" @@ -35,11 +32,13 @@ #include "media/base/media_constants.h" #include "media/base/media_engine.h" #include "media/base/sdp_video_format_utils.h" +#include "pc/codec_configuration.h" #include "pc/media_options.h" #include "pc/rtp_media_utils.h" #include "pc/session_description.h" #include "pc/typed_codec_vendor.h" #include "rtc_base/checks.h" +#include "rtc_base/containers/flat_map.h" #include "rtc_base/logging.h" #include "rtc_base/string_encode.h" #include "rtc_base/strings/string_builder.h" @@ -52,6 +51,14 @@ namespace webrtc { namespace { +std::optional<PayloadType> PayloadTypeFromString(absl::string_view s) { + int pt; + if (FromString(s, &pt)) { + return PayloadType::Create(pt); + } + return std::nullopt; +} + bool IsRtxCodec(const RtpCodecCapability& capability) { return absl::EqualsIgnoreCase(capability.name, kRtxCodecName); } @@ -89,29 +96,29 @@ const Codec* GetAssociatedCodecForRtx(const CodecList& codec_list, const Codec& rtx_codec) { RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); - std::string associated_pt_str; + int associated_pt_int; if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType, - &associated_pt_str)) { + &associated_pt_int)) { RTC_LOG(LS_WARNING) << "RTX codec " << rtx_codec.id << " is missing an associated payload type."; return nullptr; } - int associated_pt; - if (!FromString(associated_pt_str, &associated_pt)) { - RTC_LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str - << " of RTX codec " << rtx_codec.id - << " to an integer."; + std::optional<PayloadType> associated_pt = + PayloadType::Create(associated_pt_int); + if (!associated_pt) { + RTC_LOG(LS_WARNING) << "Invalid payload type " << associated_pt_int + << " for RTX codec " << rtx_codec.id << "."; return nullptr; } // Find the associated codec for the RTX codec. const Codec* associated_codec = - FindCodecById(codec_list.codecs(), associated_pt); + FindCodecById(codec_list.codecs(), *associated_pt); if (!associated_codec) { RTC_LOG(LS_WARNING) << "Couldn't find associated codec with payload type " - << associated_pt << " for RTX codec " << rtx_codec.id - << "."; + << associated_pt_int << " for RTX codec " + << rtx_codec.id << "."; } return associated_codec; } @@ -142,21 +149,22 @@ } for (size_t index = 0; index < redundant_payloads.size(); ++index) { absl::string_view associated_pt_str = redundant_payloads[index]; - int associated_pt; - if (!FromString(associated_pt_str, &associated_pt)) { + std::optional<PayloadType> associated_pt = + PayloadTypeFromString(associated_pt_str); + if (!associated_pt) { RTC_LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str << " of RED codec " << red_codec - << " to an integer."; + << " to a valid payload type."; return RTCError(RTCErrorType::INTERNAL_ERROR, - "RED codec with non-integer argument"); + "RED codec with invalid payload type argument"); } // Find the associated codec for the RED codec. const Codec* associated_codec = - FindCodecById(codec_list.codecs(), associated_pt); + FindCodecById(codec_list.codecs(), *associated_pt); if (!associated_codec) { RTC_LOG(LS_WARNING) << "Couldn't find associated codec with payload type " - << associated_pt << " for RED codec " << red_codec + << associated_pt_str << " for RED codec " << red_codec << "."; return RTCError(RTCErrorType::INTERNAL_ERROR, "RED codec pointing to nonexistent PT"); @@ -166,14 +174,203 @@ return codecs; } +RTCError MergeRtxCodec(const CodecConfiguration& config, + const Codec& primary_codec, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range) { + if (!config.resiliency.rtx) { + return RTCError::OK(); + } + auto rtx_it = absl::c_find_if(offered_codecs, [&](const Codec& c) { + if (c.name != kRtxCodecName) + return false; + int apt; + return c.GetParam(kCodecParamAssociatedPayloadType, &apt) && + apt == primary_codec.id.value(); + }); + if (rtx_it == offered_codecs.end()) { + Codec rtx = (config.codec.type == Codec::Type::kAudio) + ? CreateAudioCodec({kRtxCodecName, config.codec.clockrate, + config.codec.channels}) + : CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName); + rtx.SetParam(kCodecParamAssociatedPayloadType, primary_codec.id.value()); + auto result = + pt_suggester.SuggestPayloadType(mid, rtx, pick_from_top_of_range); + if (!result.ok()) { + return result.MoveError(); + } + rtx.id = result.value(); + offered_codecs.push_back(rtx); + } + return RTCError::OK(); +} + +RTCError MergeRedCodec(const CodecConfiguration& config, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range) { + if (!config.resiliency.red) { + return RTCError::OK(); + } + auto red_it = absl::c_find_if(offered_codecs, [&](const Codec& c) { + return c.name == kRedCodecName && c.type == config.codec.type; + }); + if (red_it == offered_codecs.end()) { + Codec red = (config.codec.type == Codec::Type::kAudio) + ? CreateAudioCodec({kRedCodecName, 48000, 2}) + : CreateVideoCodec(kRedCodecName); + auto result = + pt_suggester.SuggestPayloadType(mid, red, pick_from_top_of_range); + if (!result.ok()) { + return result.MoveError(); + } + red.id = result.value(); + offered_codecs.push_back(red); + + if (config.codec.type == Codec::Type::kVideo) { + // Video RED also gets an RTX codec. + Codec red_rtx = CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName); + red_rtx.SetParam(kCodecParamAssociatedPayloadType, red.id.value()); + auto rtx_res = + pt_suggester.SuggestPayloadType(mid, red_rtx, pick_from_top_of_range); + if (rtx_res.ok()) { + red_rtx.id = rtx_res.value(); + offered_codecs.push_back(red_rtx); + } else { + // If error is RESOURCE_EXHAUSTED, we ran out of PT numbers. + // In that case, we can ignore the codec altogether. + RTC_LOG(LS_WARNING) + << "Error when assigning RTX codec to RED codec:" << rtx_res; + // In debug mode, check that it's the expected error code. + RTC_DCHECK(rtx_res.error().type() == RTCErrorType::RESOURCE_EXHAUSTED); + } + } + } + return RTCError::OK(); +} + +RTCError MergeUlpfecCodec(const CodecConfiguration& config, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range) { + if (!config.resiliency.ulpfec || config.codec.type != Codec::Type::kVideo) { + return RTCError::OK(); + } + auto fec_it = absl::c_find_if(offered_codecs, [&](const Codec& c) { + return c.name == kUlpfecCodecName; + }); + if (fec_it == offered_codecs.end()) { + Codec fec = CreateVideoCodec(kUlpfecCodecName); + auto result = + pt_suggester.SuggestPayloadType(mid, fec, pick_from_top_of_range); + if (!result.ok()) { + return result.MoveError(); + } + fec.id = result.value(); + offered_codecs.push_back(fec); + } + return RTCError::OK(); +} + +RTCError MergeFlexfecCodec(const CodecConfiguration& config, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + const FieldTrialsView& trials, + bool pick_from_top_of_range) { + if (!config.resiliency.flexfec || config.codec.type != Codec::Type::kVideo || + !trials.IsEnabled("WebRTC-FlexFEC-03-Advertised")) { + return RTCError::OK(); + } + auto fec_it = absl::c_find_if(offered_codecs, [&](const Codec& c) { + return c.name == kFlexfecCodecName; + }); + if (fec_it == offered_codecs.end()) { + Codec fec = CreateVideoCodec(kFlexfecCodecName); + auto result = + pt_suggester.SuggestPayloadType(mid, fec, pick_from_top_of_range); + if (!result.ok()) { + return result.MoveError(); + } + fec.id = result.value(); + offered_codecs.push_back(fec); + } + return RTCError::OK(); +} + +// Adds all codecs from `configurations` to `offered_codecs` that don't +// already exist in `offered_codecs` and ensure the payload types don't +// collide. +RTCError MergeCodecsFromConfigurations( + const std::vector<CodecConfiguration>& configurations, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + const FieldTrialsView& trials, + bool pick_from_top_of_range = false) { + RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); + for (const auto& config : configurations) { + // 1. Find or add primary codec + auto primary_it = absl::c_find_if(offered_codecs, [&](const Codec& c) { + return MatchesWithCodecRules(config.codec, c); + }); + Codec primary_codec; + if (primary_it == offered_codecs.end()) { + primary_codec = config.codec; + auto result = pt_suggester.SuggestPayloadType(mid, primary_codec, + pick_from_top_of_range); + if (!result.ok()) { + return result.MoveError(); + } + primary_codec.id = result.value(); + offered_codecs.PushIfNotPresent(primary_codec); + } else { + primary_codec = *primary_it; + } + + // 2. Handle RTX + RTCError error = MergeRtxCodec(config, primary_codec, mid, offered_codecs, + pt_suggester, pick_from_top_of_range); + if (!error.ok()) { + return error; + } + + // 3. Handle RED + error = MergeRedCodec(config, mid, offered_codecs, pt_suggester, + pick_from_top_of_range); + if (!error.ok()) { + return error; + } + + // 4. Handle ULPFEC + error = MergeUlpfecCodec(config, mid, offered_codecs, pt_suggester, + pick_from_top_of_range); + if (!error.ok()) { + return error; + } + + // 5. Handle FlexFEC + error = MergeFlexfecCodec(config, mid, offered_codecs, pt_suggester, trials, + pick_from_top_of_range); + if (!error.ok()) { + return error; + } + } + return RTCError::OK(); +} + // 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. -RTCError MergeCodecs(const CodecList& reference_codecs, - absl::string_view mid, - CodecList& offered_codecs, - PayloadTypeSuggester& pt_suggester, - bool pick_from_top_of_range = false) { +RTCError MergeCodecsLegacy(const CodecList& reference_codecs, + absl::string_view mid, + CodecList& offered_codecs, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range = false) { RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); // Add all new codecs that are not RTX/RED codecs. // The two-pass splitting of the loops means preferring payload types @@ -213,8 +410,8 @@ continue; } - rtx_codec.params[kCodecParamAssociatedPayloadType] = - absl::StrCat(matching_codec->id); + rtx_codec.SetParam(kCodecParamAssociatedPayloadType, + matching_codec->id.value()); RTCErrorOr<PayloadType> suggestion = pt_suggester.SuggestPayloadType( mid, rtx_codec, pick_from_top_of_range); if (!suggestion.ok()) { @@ -287,7 +484,6 @@ } offered_codecs.CheckConsistency(); - return RTCError::OK(); } @@ -335,14 +531,15 @@ filtered_codecs.push_back(*found_codec_with_correct_pt); red_was_added = is_red_codec ? true : red_was_added; } - std::string id = absl::StrCat(found_codec_with_correct_pt->id); + PayloadType id = found_codec_with_correct_pt->id; // Search for the matching rtx or red codec. if (want_red || want_rtx) { for (const Codec& codec : codecs) { if (want_rtx && codec.GetResiliencyType() == Codec::ResiliencyType::kRtx) { - auto apt = codec.params.find(kCodecParamAssociatedPayloadType); - if (apt != codec.params.end() && apt->second == id) { + int apt; + if (codec.GetParam(kCodecParamAssociatedPayloadType, &apt) && + apt == id.value()) { filtered_codecs.push_back(codec); break; } @@ -355,8 +552,10 @@ if (fmtp != codec.params.end()) { std::vector<absl::string_view> redundant_payloads = split(fmtp->second, '/'); + int first_redundant_pt; if (!redundant_payloads.empty() && - redundant_payloads[0] == id) { + FromString(redundant_payloads[0], &first_redundant_pt) && + first_redundant_pt == id.value()) { if (!red_was_added) { filtered_codecs.push_back(codec); red_was_added = true; @@ -411,7 +610,7 @@ // do not clear define the expected behavior for the level in the offer. #ifdef RTC_ENABLE_H265 if (media_description_options.type == MediaType::VIDEO) { - std::unordered_map<H265Profile, H265Level> supported_h265_profiles; + flat_map<H265Profile, H265Level> supported_h265_profiles; // The assumption here is that H.265 codecs with the same profile and tier // are already with highest level for that profile in both // |supported_codecs| and |filtered_codecs|. @@ -454,7 +653,7 @@ CodecList& negotiated_codecs_out, bool keep_offer_order) { RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); - std::map<int, int> pt_mapping_table; + flat_map<PayloadType, PayloadType> pt_mapping_table; // Since we build the negotiated codec list one entry at a time, // the list will have inconsistencies during building. std::vector<Codec> negotiated_codecs; @@ -504,22 +703,18 @@ if (negotiated.GetResiliencyType() == Codec::ResiliencyType::kRtx) { // Change the apt value according to the pt mapping table. // This avoids changing to apt values that don't exist any more. - std::string apt_str; - if (!negotiated.GetParam(kCodecParamAssociatedPayloadType, &apt_str)) { + int apt_int; + if (!negotiated.GetParam(kCodecParamAssociatedPayloadType, &apt_int)) { RTC_LOG(LS_WARNING) << "No apt value"; continue; } - int apt_value; - if (!FromString(apt_str, &apt_value)) { - RTC_LOG(LS_WARNING) << "Unconvertable apt value"; - continue; - } - if (pt_mapping_table.count(apt_value) != 1) { + PayloadType apt_value(apt_int); + if (!pt_mapping_table.contains(apt_value)) { RTC_LOG(LS_WARNING) << "Unmapped apt value " << apt_value; continue; } negotiated.SetParam(kCodecParamAssociatedPayloadType, - pt_mapping_table.at(apt_value)); + pt_mapping_table.at(apt_value).value()); } } if (keep_offer_order) { @@ -528,7 +723,7 @@ // specific reason, the answerer list formats in the same relative order // they were present in the offer. // This can be skipped when the transceiver has any codec preferences. - std::unordered_map<int, int> payload_type_preferences; + flat_map<PayloadType, int> payload_type_preferences; int preference = static_cast<int>(offered_codecs.size() + 1); for (const Codec& codec : offered_codecs) { payload_type_preferences[codec.id] = preference--; @@ -546,50 +741,89 @@ return RTCError::OK(); } -// Update the ID fields of the codec vector. -// If any codec has an ID with value "kIdNotSet", use the payload type suggester -// to assign and record a payload type for it. // If there is a RED codec without its fmtp parameter, give it the ID of the // first OPUS codec in the codec list. -RTCError AssignCodecIdsAndLinkRed(PayloadTypeSuggester* pt_suggester, - const std::string& mid, - std::vector<Codec>& codecs, - bool pick_from_top_of_range = false) { - RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); - int codec_payload_type = Codec::kIdNotSet; - for (Codec& codec : codecs) { - if (codec.id == PayloadType::NotSet()) { - // Add payload types to codecs, if needed - // This should only happen if WebRTC-PayloadTypesInTransport field trial - // is enabled. - RTC_CHECK(pt_suggester); - RTCErrorOr<PayloadType> result = - pt_suggester->SuggestPayloadType(mid, codec, pick_from_top_of_range); - if (!result.ok()) { - return result.error(); - } - codec.id = result.value(); - } else if (pt_suggester) { - pt_suggester->AddLocalMapping(mid, codec.id, codec); - } - // record first Opus codec id - if (absl::EqualsIgnoreCase(codec.name, kOpusCodecName) && - codec_payload_type == Codec::kIdNotSet) { - codec_payload_type = codec.id.value(); +void LinkRed(std::vector<Codec>& codecs) { + int first_opus_pt = Codec::kIdNotSet; + for (const Codec& codec : codecs) { + if (codec.type == Codec::Type::kAudio && + absl::EqualsIgnoreCase(codec.name, kOpusCodecName)) { + first_opus_pt = codec.id.value(); + break; } } - if (codec_payload_type != Codec::kIdNotSet) { + + if (first_opus_pt != Codec::kIdNotSet) { for (Codec& codec : codecs) { if (codec.type == Codec::Type::kAudio && absl::EqualsIgnoreCase(codec.name, kRedCodecName)) { if (codec.params.empty()) { StringBuilder param; - param << codec_payload_type << "/" << codec_payload_type; + param << first_opus_pt << "/" << first_opus_pt; codec.SetParam(kCodecParamNotInNameValueFormat, param.str()); } } } } +} + +// Update the ID fields of the codec vector in the legacy path +// (payload_types_in_transport_ = false). +// If any codec has an ID with value "kIdNotSet", this is an error. +RTCError RecordCodecIdsAndLinkRed(PayloadTypeSuggester& pt_suggester, + const std::string& mid, + std::vector<Codec>& codecs) { + RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); + for (Codec& codec : codecs) { + RTC_DCHECK(codec.id != PayloadType::NotSet()); + pt_suggester.AddLocalMapping(mid, codec.id, codec); + } + + LinkRed(codecs); + return RTCError::OK(); +} + +// Update the ID fields of the codec vector in the redesigned path. +// If any codec has an ID with value "kIdNotSet", use the payload type suggester +// to assign and record a payload type for it. +RTCError AssignCodecIdsAndLinkRedRefactored( + PayloadTypeSuggester& pt_suggester, + const std::string& mid, + std::vector<Codec>& codecs, + bool pick_from_top_of_range = false) { + RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); + Codec* last_codec_that_can_have_rtx = nullptr; + + for (Codec& codec : codecs) { + if (codec.id == PayloadType::NotSet()) { + auto result = + pt_suggester.SuggestPayloadType(mid, codec, pick_from_top_of_range); + if (!result.ok()) { + return result.error(); + } + codec.id = result.value(); + } else { + pt_suggester.AddLocalMapping(mid, codec.id, codec); + } + + if (codec.GetResiliencyType() == Codec::ResiliencyType::kRtx) { + if (last_codec_that_can_have_rtx && + codec.params.find(kCodecParamAssociatedPayloadType) == + codec.params.end()) { + // When new codecs are added that want RTX, they're added as a pair + // of (media codec, rtx, codec). But since they don't have assigned + // IDs yet, the apt parameter can't be set. This logic ensures that + // the apt parameter points to the immediately preceding codec. + codec.SetParam(kCodecParamAssociatedPayloadType, + last_codec_that_can_have_rtx->id.value()); + } + } else { + // This is a codec that can potentially be associated with an RTX codec. + last_codec_that_can_have_rtx = &codec; + } + } + + LinkRed(codecs); return RTCError::OK(); } @@ -601,8 +835,61 @@ CodecList& offered_codecs, PayloadTypeSuggester& pt_suggester, bool pick_from_top_of_range) { - return MergeCodecs(reference_codecs, mid, offered_codecs, pt_suggester, - pick_from_top_of_range); + // This function is only available for testing the legacy path. + return MergeCodecsLegacy(reference_codecs, mid, offered_codecs, pt_suggester, + pick_from_top_of_range); +} + +RTCError CodecVendor::MergeCodecsByDirection(MediaType type, + RtpTransceiverDirection direction, + absl::string_view mid, + CodecList& codecs_out, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range, + bool favor_send_order) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); + const std::vector<CodecConfiguration>& send_configs = + (type == MediaType::AUDIO) ? audio_send_codecs_.configurations() + : video_send_codecs_.configurations(); + const std::vector<CodecConfiguration>& recv_configs = + (type == MediaType::AUDIO) ? audio_recv_codecs_.configurations() + : video_recv_codecs_.configurations(); + + switch (direction) { + case RtpTransceiverDirection::kSendRecv: + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: { + if (favor_send_order) { + RTCError error = MergeCodecsFromConfigurations( + send_configs, mid, codecs_out, pt_suggester, trials_, + pick_from_top_of_range); + if (!error.ok()) + return error; + return MergeCodecsFromConfigurations(recv_configs, mid, codecs_out, + pt_suggester, trials_, + pick_from_top_of_range); + } else { + RTCError error = MergeCodecsFromConfigurations( + recv_configs, mid, codecs_out, pt_suggester, trials_, + pick_from_top_of_range); + if (!error.ok()) + return error; + return MergeCodecsFromConfigurations(send_configs, mid, codecs_out, + pt_suggester, trials_, + pick_from_top_of_range); + } + } + case RtpTransceiverDirection::kSendOnly: + return MergeCodecsFromConfigurations(send_configs, mid, codecs_out, + pt_suggester, trials_, + pick_from_top_of_range); + case RtpTransceiverDirection::kRecvOnly: + return MergeCodecsFromConfigurations(recv_configs, mid, codecs_out, + pt_suggester, trials_, + pick_from_top_of_range); + } + RTC_CHECK_NOTREACHED(); } RTCErrorOr<std::vector<Codec>> CodecVendor::GetNegotiatedCodecsForOffer( @@ -612,37 +899,68 @@ PayloadTypeSuggester& pt_suggester) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); - CodecList codecs; + std::string mid = media_description_options.mid; - // If current content exists and is not being recycled, use its codecs. - if (current_content && current_content->mid() == mid && - IsMediaContentOfType(current_content, media_description_options.type)) { - RTCErrorOr<CodecList> checked_codec_list = - CodecList::Create(current_content->media_description()->codecs()); - if (!checked_codec_list.ok()) { - return checked_codec_list.MoveError(); + CodecList codecs; + + if (payload_types_in_transport_) { + // REDESIGN path: Assume codecs from TypedCodecVendor are NotSet. + // If current content exists and is not being recycled, use its codecs. + if (current_content && current_content->mid() == mid && + IsMediaContentOfType(current_content, media_description_options.type)) { + RTCErrorOr<CodecList> checked_codec_list = + CodecList::Create(current_content->media_description()->codecs()); + if (!checked_codec_list.ok()) { + return checked_codec_list.MoveError(); + } + codecs = checked_codec_list.MoveValue(); + for (const Codec& codec : codecs) { + pt_suggester.AddLocalMapping(mid, codec.id, codec); + } } - // Use MergeCodecs in order to handle PT clashes. - MergeCodecs(checked_codec_list.value(), mid, codecs, pt_suggester, - /*pick_from_top_of_range=*/true); - } - // Add our codecs that are not in the current description. - if (media_description_options.type == MediaType::AUDIO) { - MergeCodecs(audio_recv_codecs(), mid, codecs, pt_suggester, - /*pick_from_top_of_range=*/true); - MergeCodecs(audio_send_codecs(), mid, codecs, pt_suggester, - /*pick_from_top_of_range=*/true); + MergeCodecsByDirection(media_description_options.type, + RtpTransceiverDirection::kSendRecv, mid, codecs, + pt_suggester, /*pick_from_top_of_range=*/true); } else { - MergeCodecs(video_recv_codecs(), mid, codecs, pt_suggester, - /*pick_from_top_of_range=*/true); - MergeCodecs(video_send_codecs(), mid, codecs, pt_suggester, - /*pick_from_top_of_range=*/true); + // LEGACY path: Assume codecs have PTs. + // If current content exists and is not being recycled, use its codecs. + if (current_content && current_content->mid() == mid && + IsMediaContentOfType(current_content, media_description_options.type)) { + RTCErrorOr<CodecList> checked_codec_list = + CodecList::Create(current_content->media_description()->codecs()); + if (!checked_codec_list.ok()) { + return checked_codec_list.MoveError(); + } + // Use MergeCodecsLegacy in order to handle PT clashes. + MergeCodecsLegacy(checked_codec_list.value(), mid, codecs, pt_suggester, + /*pick_from_top_of_range=*/true); + } + // Add our codecs that are not in the current description. + if (media_description_options.type == MediaType::AUDIO) { + MergeCodecsLegacy(audio_recv_codecs_.codecs(), mid, codecs, pt_suggester, + /*pick_from_top_of_range=*/true); + MergeCodecsLegacy(audio_send_codecs_.codecs(), mid, codecs, pt_suggester, + /*pick_from_top_of_range=*/true); + } else { + MergeCodecsLegacy(video_recv_codecs_.codecs(), mid, codecs, pt_suggester, + /*pick_from_top_of_range=*/true); + MergeCodecsLegacy(video_send_codecs_.codecs(), mid, codecs, pt_suggester, + /*pick_from_top_of_range=*/true); + } } + CodecList filtered_codecs; - CodecList supported_codecs = - media_description_options.type == MediaType::AUDIO - ? GetAudioCodecsForOffer(media_description_options.direction) - : GetVideoCodecsForOffer(media_description_options.direction); + CodecList supported_codecs; + if (payload_types_in_transport_) { + MergeCodecsByDirection( + media_description_options.type, media_description_options.direction, + mid, supported_codecs, pt_suggester, /*pick_from_top_of_range=*/true); + } else { + supported_codecs = + media_description_options.type == MediaType::AUDIO + ? GetAudioCodecsForOffer(media_description_options.direction) + : GetVideoCodecsForOffer(media_description_options.direction); + } if (media_description_options.codecs_to_include.empty()) { if (!media_description_options.codec_preferences.empty()) { @@ -655,14 +973,14 @@ // Add the codecs from current content if it exists and is not rejected // nor recycled. if (current_content && !current_content->rejected && - current_content->mid() == media_description_options.mid) { + current_content->mid() == mid) { if (!IsMediaContentOfType(current_content, media_description_options.type)) { // Can happen if the remote side re-uses a MID while recycling. return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR) << "Media type for content with mid='" << current_content->mid() - << "' does not match previous type."); + << "' does not match expected type."); } const MediaContentDescription* mcd = current_content->media_description(); @@ -689,30 +1007,20 @@ // audio. const Codec* referenced_codec = GetAssociatedCodecForRtx(supported_codecs, codec); - RTC_DCHECK(referenced_codec); - - // Find the codec we should be referencing and point to it. - std::optional<Codec> changed_referenced_codec = FindMatchingCodec( - supported_codecs, filtered_codecs, *referenced_codec); - if (changed_referenced_codec) { - found_codec->SetParam(kCodecParamAssociatedPayloadType, - changed_referenced_codec->id); + if (referenced_codec) { + // Find the codec we should be referencing and point to it. + std::optional<Codec> changed_referenced_codec = FindMatchingCodec( + supported_codecs, filtered_codecs, *referenced_codec); + if (changed_referenced_codec) { + found_codec->SetParam(kCodecParamAssociatedPayloadType, + changed_referenced_codec->id.value()); + } } } - // Ensure pt_suggester is aware of this codec for this MID. - // This should just return the PT already in found_codec->id, - // unless there is a collision in filtered_codecs. - RTCErrorOr<PayloadType> suggestion = pt_suggester.SuggestPayloadType( - mid, *found_codec, /*pick_from_top_of_range=*/true); - if (!suggestion.ok()) { - return suggestion.MoveError(); - } - found_codec->id = suggestion.value(); filtered_codecs.PushIfNotPresent(*found_codec); } } } - if (media_description_options.type == MediaType::AUDIO && !session_options.vad_enabled) { // If application doesn't want CN codecs in offer. @@ -737,10 +1045,14 @@ } filtered_codecs = codecs_from_arg.MoveValue(); } - AssignCodecIdsAndLinkRed(&pt_suggester, mid, - filtered_codecs.writable_codecs(), - /*pick_from_top_of_range=*/true); - + if (payload_types_in_transport_) { + AssignCodecIdsAndLinkRedRefactored(pt_suggester, mid, + filtered_codecs.writable_codecs(), + /*pick_from_top_of_range=*/true); + } else { + RecordCodecIdsAndLinkRed(pt_suggester, mid, + filtered_codecs.writable_codecs()); + } return filtered_codecs.codecs(); } @@ -750,37 +1062,77 @@ RtpTransceiverDirection offer_rtd, RtpTransceiverDirection answer_rtd, const ContentInfo* current_content, - const std::vector<Codec> codecs_from_offer, + std::vector<Codec> codecs_from_offer, PayloadTypeSuggester& pt_suggester) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); + CodecList codecs; std::string mid = media_description_options.mid; - if (current_content && current_content->mid() == mid && - IsMediaContentOfType(current_content, media_description_options.type)) { - RTCErrorOr<CodecList> checked_codec_list = - CodecList::Create(current_content->media_description()->codecs()); - if (!checked_codec_list.ok()) { - return checked_codec_list.MoveError(); + + if (payload_types_in_transport_) { + // REDESIGN path: Assume local codecs from TypedCodecVendor are NotSet. + // If current content exists and is not being recycled, use its codecs. + if (current_content && current_content->mid() == mid && + IsMediaContentOfType(current_content, media_description_options.type)) { + RTCErrorOr<CodecList> checked_codec_list = + CodecList::Create(current_content->media_description()->codecs()); + if (!checked_codec_list.ok()) { + return checked_codec_list.MoveError(); + } + codecs = checked_codec_list.MoveValue(); + for (const Codec& codec : codecs) { + pt_suggester.AddLocalMapping(mid, codec.id, codec); + } } - MergeCodecs(checked_codec_list.value(), mid, codecs, pt_suggester); - } - // Add all our supported codecs - if (media_description_options.type == MediaType::AUDIO) { - MergeCodecs(audio_send_codecs(), mid, codecs, pt_suggester); - MergeCodecs(audio_recv_codecs(), mid, codecs, pt_suggester); + MergeCodecsByDirection(media_description_options.type, + RtpTransceiverDirection::kSendRecv, mid, codecs, + pt_suggester, /*pick_from_top_of_range=*/false, + /*favor_send_order=*/true); } else { - MergeCodecs(video_send_codecs(), mid, codecs, pt_suggester); - MergeCodecs(video_recv_codecs(), mid, codecs, pt_suggester); + // LEGACY path: Assume codecs have PTs. + // If current content exists and is not being recycled, use its codecs. + if (current_content && current_content->mid() == mid && + IsMediaContentOfType(current_content, media_description_options.type)) { + RTCErrorOr<CodecList> checked_codec_list = + CodecList::Create(current_content->media_description()->codecs()); + if (!checked_codec_list.ok()) { + return checked_codec_list.MoveError(); + } + MergeCodecsLegacy(checked_codec_list.value(), mid, codecs, pt_suggester); + } + // Add all our supported codecs + if (media_description_options.type == MediaType::AUDIO) { + MergeCodecsLegacy(audio_send_codecs_.codecs(), mid, codecs, pt_suggester); + MergeCodecsLegacy(audio_recv_codecs_.codecs(), mid, codecs, pt_suggester); + } else { + MergeCodecsLegacy(video_send_codecs_.codecs(), mid, codecs, pt_suggester); + MergeCodecsLegacy(video_recv_codecs_.codecs(), mid, codecs, pt_suggester); + } } + CodecList filtered_codecs; CodecList negotiated_codecs; if (media_description_options.codecs_to_include.empty()) { - const CodecList& supported_codecs = - media_description_options.type == MediaType::AUDIO - ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd) - : GetVideoCodecsForAnswer(offer_rtd, answer_rtd); + CodecList supported_codecs; + if (payload_types_in_transport_) { + RtpTransceiverDirection direction_to_use = answer_rtd; + if (answer_rtd == RtpTransceiverDirection::kSendRecv || + answer_rtd == RtpTransceiverDirection::kStopped || + answer_rtd == RtpTransceiverDirection::kInactive) { + direction_to_use = RtpTransceiverDirectionReversed(offer_rtd); + } + MergeCodecsByDirection(media_description_options.type, direction_to_use, + mid, supported_codecs, pt_suggester, + /*pick_from_top_of_range=*/false); + } else { + supported_codecs = media_description_options.type == MediaType::AUDIO + ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd) + : GetVideoCodecsForAnswer(offer_rtd, answer_rtd); + } if (!media_description_options.codec_preferences.empty()) { + // Add the codecs from the current transceiver's codec preferences. + // They override any existing codecs from previous negotiations. filtered_codecs = MatchCodecPreference(media_description_options.codec_preferences, codecs, supported_codecs); @@ -788,26 +1140,32 @@ // Add the codecs from current content if it exists and is not rejected // nor recycled. if (current_content && !current_content->rejected && - current_content->mid() == media_description_options.mid) { + current_content->mid() == mid) { if (!IsMediaContentOfType(current_content, media_description_options.type)) { // Can happen if the remote side re-uses a MID while recycling. return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR) << "Media type for content with mid='" << current_content->mid() - << "' does not match previous type."); + << "' does not match expected type."); } const MediaContentDescription* mcd = current_content->media_description(); for (const Codec& codec : mcd->codecs()) { - if (std::optional<Codec> found_codec = - FindMatchingCodec(mcd->codecs(), codecs.codecs(), codec)) { - filtered_codecs.push_back(*found_codec); + if (FindMatchingCodec(mcd->codecs(), codecs.codecs(), codec)) { + filtered_codecs.push_back(codec); } } } - // Merge other_codecs into filtered_codecs, resolving PT conflicts. - MergeCodecs(supported_codecs, mid, filtered_codecs, pt_suggester); + if (payload_types_in_transport_) { + // Redesign path: Use configurations to merge supported codecs. + MergeCodecsByDirection(media_description_options.type, answer_rtd, mid, + filtered_codecs, pt_suggester, + /*pick_from_top_of_range=*/false); + } else { + // Merge other_codecs into filtered_codecs, resolving PT conflicts. + MergeCodecsLegacy(supported_codecs, mid, filtered_codecs, pt_suggester); + } } if (media_description_options.type == MediaType::AUDIO && @@ -840,8 +1198,13 @@ } negotiated_codecs = codecs_from_arg.MoveValue(); } - AssignCodecIdsAndLinkRed(&pt_suggester, media_description_options.mid, - negotiated_codecs.writable_codecs()); + if (payload_types_in_transport_) { + AssignCodecIdsAndLinkRedRefactored(pt_suggester, mid, + negotiated_codecs.writable_codecs()); + } else { + RecordCodecIdsAndLinkRed(pt_suggester, mid, + negotiated_codecs.writable_codecs()); + } return negotiated_codecs.codecs(); } @@ -858,7 +1221,8 @@ CodecVendor::CodecVendor(const MediaEngineInterface* media_engine, bool rtx_enabled, const FieldTrialsView& trials) - : audio_send_codecs_(InitTypedCodecVendor(media_engine, + : trials_(trials), + audio_send_codecs_(InitTypedCodecVendor(media_engine, MediaType::AUDIO, /*is_sender=*/true, rtx_enabled, @@ -868,6 +1232,8 @@ /*is_sender=*/false, rtx_enabled, trials)), + payload_types_in_transport_( + trials.IsEnabled("WebRTC-PayloadTypesInTransport")), video_send_codecs_(InitTypedCodecVendor(media_engine, MediaType::VIDEO, /*is_sender=*/true,
diff --git a/pc/codec_vendor.h b/pc/codec_vendor.h index a2ad1c4..a78e0c5 100644 --- a/pc/codec_vendor.h +++ b/pc/codec_vendor.h
@@ -17,6 +17,7 @@ #include "absl/base/nullability.h" #include "absl/strings/string_view.h" #include "api/field_trials_view.h" +#include "api/media_types.h" #include "api/rtc_error.h" #include "api/rtp_transceiver_direction.h" #include "api/sequence_checker.h" @@ -104,14 +105,26 @@ const RtpTransceiverDirection& offer, const RtpTransceiverDirection& answer) const; + RTCError MergeCodecsByDirection(MediaType type, + RtpTransceiverDirection direction, + absl::string_view mid, + CodecList& codecs_out, + PayloadTypeSuggester& pt_suggester, + bool pick_from_top_of_range, + bool favor_send_order = false); + // Makes sure that modifications and reading data is done on the same thread // and to makessure we consistently make calls to GetNegotiatedCodecsForOffer // and GetNegotiatedCodecsForAnswer in the same calling context. RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + const FieldTrialsView& trials_; + const TypedCodecVendor audio_send_codecs_; const TypedCodecVendor audio_recv_codecs_; + const bool payload_types_in_transport_; + // TODO: bugs.webrtc.org/412904801 - Make const. In order to be able to do // that, `ModifyVideoCodecs` needs to be removed. In the meantime, codec // information must be read and modified on the same task queue. @@ -135,6 +148,7 @@ // A helper function to merge codecs numbered in one PT numberspace // into a list numbered in another PT numberspace. Exposed for testing. +// This function is only available for testing the legacy path. RTCError MergeCodecsForTesting(const CodecList& reference_codecs, absl::string_view mid, CodecList& offered_codecs,
diff --git a/pc/codec_vendor_redesign_unittest.cc b/pc/codec_vendor_redesign_unittest.cc index 6f6bba8..b58aa38 100644 --- a/pc/codec_vendor_redesign_unittest.cc +++ b/pc/codec_vendor_redesign_unittest.cc
@@ -107,8 +107,7 @@ EXPECT_EQ(fmtp, absl::StrCat(opus_it->id, "/", opus_it->id)); } -TEST_F(CodecVendorRedesignTest, - DISABLED_VideoOfferIncludesRtxAndRedAndAssignsIds) { +TEST_F(CodecVendorRedesignTest, VideoOfferIncludesRtxAndRedAndAssignsIds) { MediaDescriptionOptions options(MediaType::VIDEO, "video", RtpTransceiverDirection::kSendRecv, /*stopped=*/false);
diff --git a/pc/codec_vendor_unittest.cc b/pc/codec_vendor_unittest.cc index e4f973b..d86306d 100644 --- a/pc/codec_vendor_unittest.cc +++ b/pc/codec_vendor_unittest.cc
@@ -32,6 +32,7 @@ #include "pc/rtp_parameters_conversion.h" #include "pc/session_description.h" #include "rtc_base/checks.h" +#include "test/create_test_field_trials.h" #include "test/gmock.h" #include "test/gtest.h" @@ -133,9 +134,10 @@ media_engine.SetAudioRecvCodecs(no_codecs.codecs()); { CodecVendor codec_vendor(&media_engine, false, trials); - EXPECT_EQ(no_codecs, codec_vendor.audio_send_codecs()); - EXPECT_EQ(no_codecs, codec_vendor.audio_recv_codecs()); - EXPECT_EQ(no_codecs, codec_vendor.audio_sendrecv_codecs()); + EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_send_codecs().codecs()); + EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_recv_codecs().codecs()); + EXPECT_EQ(no_codecs.codecs(), + codec_vendor.audio_sendrecv_codecs().codecs()); } } @@ -266,6 +268,9 @@ } TEST(CodecVendorMergeTest, BasicTestSetup) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -276,6 +281,9 @@ } TEST(CodecVendorMergeTest, IdenticalListsMergeWithNoChange) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -293,6 +301,9 @@ } TEST(CodecVendorMergeTest, MergeRenumbersAdditionalCodecs) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -323,6 +334,9 @@ } TEST(CodecVendorMergeTest, MergeRenumbersRedCodecArgument) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -350,6 +364,9 @@ } TEST(CodecVendorMergeTest, MergeRenumbersRedCodecArgumentAndMerges) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -380,6 +397,9 @@ } TEST(CodecVendorMergeTest, MergeWithBrokenReferenceRedErrors) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; @@ -398,6 +418,9 @@ } TEST(CodecVendorMergeTest, MergeWithCollisionPicksFromTop) { + if (CreateTestFieldTrials().IsEnabled("WebRTC-PayloadTypesInTransport")) { + GTEST_SKIP(); + } CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs;
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc index f27d4c6..664c91a 100644 --- a/pc/media_session_unittest.cc +++ b/pc/media_session_unittest.cc
@@ -6546,8 +6546,9 @@ AttachSenderToMediaDescriptionOptions( kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts); std::vector<RtpCodecCapability> preferences; - for (const Codec& codec : - codec_lookup_helper_offerer_.GetCodecVendor()->video_recv_codecs()) { + for (const Codec& codec : codec_lookup_helper_offerer_.GetCodecVendor() + ->video_recv_codecs() + .codecs()) { preferences.push_back(ToRtpCodecCapability(codec)); } opts.media_description_options[0].codec_preferences = preferences;
diff --git a/pc/typed_codec_vendor.cc b/pc/typed_codec_vendor.cc index b0599de..20b2d66 100644 --- a/pc/typed_codec_vendor.cc +++ b/pc/typed_codec_vendor.cc
@@ -179,7 +179,8 @@ Codecs CodecsFromConfigurations( const std::vector<CodecConfiguration>& configurations, - MediaType type) { + MediaType type, + bool rtx_enabled) { Codecs out; flat_set<std::string> shared_added; for (const auto& config : configurations) { @@ -195,7 +196,9 @@ if (config.resiliency.red && shared_added.insert(kRedCodecName).second) { out.push_back(CreateVideoCodec(kRedCodecName)); // Video RED also gets an RTX codec. - out.push_back(CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName)); + if (rtx_enabled) { + out.push_back(CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName)); + } } if (config.resiliency.ulpfec && shared_added.insert(kUlpfecCodecName).second) { @@ -248,7 +251,7 @@ media_engine->video(), is_sender, rtx_enabled, trials); } codecs_ = CodecList::CreateFromTrustedData( - CodecsFromConfigurations(configurations_, type)); + CodecsFromConfigurations(configurations_, type, rtx_enabled)); } else { codecs_ = CodecList::CreateFromTrustedData( GetCodecs(media_engine, type, is_sender, rtx_enabled));