Introduce CodecConfiguration and ResiliencyInfo in pc/ This is the first step in the payload type allocation redesign. It introduces a new internal representation that separates codec attributes from their payload type assignments, and captures resiliency requirements (RTX, RED, FEC) explicitly. TypedCodecVendor is updated to populate these configurations for both audio and video when the WebRTC-PayloadTypesInTransport field trial is enabled. Bug: webrtc:360058654 Change-Id: Ibecd98e17cdaafbcc7399ddd035ecfaa158d37b5 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/468100 Commit-Queue: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47617}
diff --git a/media/BUILD.gn b/media/BUILD.gn index 785318b..a8e0165 100644 --- a/media/BUILD.gn +++ b/media/BUILD.gn
@@ -252,6 +252,7 @@ "../api/video:video_codec_constants", "../api/video_codecs:scalability_mode", "../api/video_codecs:scalability_mode_helper", + "../api/video_codecs:video_codecs_api", "../call:call_interfaces", "../rtc_base:checks", "../rtc_base:safe_compare", @@ -375,6 +376,7 @@ ] deps = [ ":media_constants", + "../api:field_trials_view", "../api:payload_type", "../api:rtp_parameters", "../api/audio_codecs:audio_codecs_api",
diff --git a/media/base/codec.cc b/media/base/codec.cc index d78a1a6..9aaac6e 100644 --- a/media/base/codec.cc +++ b/media/base/codec.cc
@@ -21,6 +21,7 @@ #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "api/media_types.h" #include "api/payload_type.h" #include "api/rtp_parameters.h" @@ -403,6 +404,25 @@ } } +void AddDefaultFeedbackParams(Codec* codec, const FieldTrialsView& trials) { + // Don't add any feedback params for RED and ULPFEC. + if (codec->name == kRedCodecName || codec->name == kUlpfecCodecName) + return; + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamRemb, kParamValueEmpty)); + codec->AddFeedbackParam( + FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty)); + // Don't add any more feedback params for FLEXFEC. + if (codec->name == kFlexfecCodecName) + return; + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamCcm, kRtcpFbCcmParamFir)); + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kRtcpFbNackParamPli)); + if (codec->name == kVp8CodecName && + trials.IsEnabled("WebRTC-RtcpLossNotification")) { + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamLntf, kParamValueEmpty)); + } +} + Codec CreateAudioCodec(PayloadType id, const std::string& name, int clockrate,
diff --git a/media/base/codec.h b/media/base/codec.h index 5f19744..31f5999 100644 --- a/media/base/codec.h +++ b/media/base/codec.h
@@ -20,6 +20,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "api/payload_type.h" #include "api/rtp_parameters.h" #include "api/video_codecs/scalability_mode.h" @@ -273,6 +274,11 @@ RTC_EXPORT void AddH264ConstrainedBaselineProfileToSupportedFormats( std::vector<SdpVideoFormat>* supported_formats); + +// This function adds the default RTCP feedback parameters to the codec, +// based on the codec name and the active field trials. +void AddDefaultFeedbackParams(Codec* codec, const FieldTrialsView& trials); + } // namespace webrtc
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc index 074c029..3dcec19 100644 --- a/media/base/fake_media_engine.cc +++ b/media/base/fake_media_engine.cc
@@ -37,6 +37,7 @@ #include "api/video/video_bitrate_allocator_factory.h" #include "api/video/video_sink_interface.h" #include "api/video/video_source_interface.h" +#include "api/video_codecs/sdp_video_format.h" #include "call/audio_state.h" #include "call/call.h" #include "media/base/audio_source.h" @@ -678,7 +679,10 @@ header_extensions_ = std::move(header_extensions); } -FakeVideoEngine::FakeVideoEngine() : capture_(false) { +FakeVideoEngine::FakeVideoEngine() + : encoder_factory_(std::make_unique<FakeVideoEncoderFactory>(this)), + decoder_factory_(std::make_unique<FakeVideoDecoderFactory>(this)), + capture_(false) { // Add a fake video codec. Note that the name must not be "" as there are // sanity checks against that. send_codecs_.push_back(CreateVideoCodec(111, "fake_video_codec")); @@ -733,6 +737,15 @@ return recv_codecs_; } +std::vector<SdpVideoFormat> FakeVideoEngine::GetSupportedFormats( + bool is_decoder) const { + if (is_decoder) { + return decoder_factory_->GetSupportedFormats(); + } else { + return encoder_factory_->GetSupportedFormats(); + } +} + void FakeVideoEngine::SetSendCodecs(const std::vector<Codec>& codecs) { send_codecs_ = codecs; }
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h index 832caac..e6dec46 100644 --- a/media/base/fake_media_engine.h +++ b/media/base/fake_media_engine.h
@@ -55,6 +55,11 @@ #include "api/video/video_bitrate_allocator_factory.h" #include "api/video/video_sink_interface.h" #include "api/video/video_source_interface.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_decoder.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" #include "call/audio_state.h" #include "media/base/audio_source.h" #include "media/base/codec.h" @@ -930,7 +935,59 @@ void SetRtpHeaderExtensions( std::vector<RtpHeaderExtensionCapability> header_extensions); + VideoEncoderFactory* encoder_factory() const override { + return encoder_factory_.get(); + } + VideoDecoderFactory* decoder_factory() const override { + return decoder_factory_.get(); + } + + std::vector<SdpVideoFormat> GetSupportedFormats( + bool is_decoder) const override; + private: + class FakeVideoEncoderFactory : public VideoEncoderFactory { + public: + explicit FakeVideoEncoderFactory(FakeVideoEngine* owner) : owner_(owner) {} + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + std::vector<SdpVideoFormat> formats; + for (const auto& codec : owner_->send_codecs_) { + formats.push_back(SdpVideoFormat(codec.name, codec.params)); + } + return formats; + } + std::unique_ptr<VideoEncoder> Create( + const Environment& env, + const SdpVideoFormat& format) override { + return nullptr; + } + + private: + const FakeVideoEngine* const owner_; + }; + + class FakeVideoDecoderFactory : public VideoDecoderFactory { + public: + explicit FakeVideoDecoderFactory(FakeVideoEngine* owner) : owner_(owner) {} + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + std::vector<SdpVideoFormat> formats; + for (const auto& codec : owner_->recv_codecs_) { + formats.push_back(SdpVideoFormat(codec.name, codec.params)); + } + return formats; + } + std::unique_ptr<VideoDecoder> Create( + const Environment& env, + const SdpVideoFormat& format) override { + return nullptr; + } + + private: + const FakeVideoEngine* const owner_; + }; + + const std::unique_ptr<VideoEncoderFactory> encoder_factory_; + const std::unique_ptr<VideoDecoderFactory> decoder_factory_; std::vector<Codec> send_codecs_; std::vector<Codec> recv_codecs_; bool capture_;
diff --git a/media/base/media_engine.h b/media/base/media_engine.h index c85db96..67cb1f7 100644 --- a/media/base/media_engine.h +++ b/media/base/media_engine.h
@@ -28,6 +28,9 @@ #include "api/rtp_parameters.h" #include "api/scoped_refptr.h" #include "api/video/video_bitrate_allocator_factory.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" #include "call/audio_state.h" #include "media/base/codec.h" #include "media/base/media_channel.h" @@ -186,6 +189,12 @@ [[deprecated]] inline std::vector<Codec> recv_codecs(bool include_rtx) const { return LegacyRecvCodecs(include_rtx); } + + virtual VideoEncoderFactory* encoder_factory() const = 0; + virtual VideoDecoderFactory* decoder_factory() const = 0; + + virtual std::vector<SdpVideoFormat> GetSupportedFormats( + bool is_decoder) const = 0; }; // MediaEngineInterface is an abstraction of a media engine which can be
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index 063c95b..8d5f336 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc
@@ -127,24 +127,6 @@ return nullptr; } -void AddDefaultFeedbackParams(Codec* codec, const FieldTrialsView& trials) { - // Don't add any feedback params for RED and ULPFEC. - if (codec->name == kRedCodecName || codec->name == kUlpfecCodecName) - return; - codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamRemb, kParamValueEmpty)); - codec->AddFeedbackParam( - FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty)); - // Don't add any more feedback params for FLEXFEC. - if (codec->name == kFlexfecCodecName) - return; - codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamCcm, kRtcpFbCcmParamFir)); - codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); - codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kRtcpFbNackParamPli)); - if (codec->name == kVp8CodecName && - trials.IsEnabled("WebRTC-RtcpLossNotification")) { - codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamLntf, kParamValueEmpty)); - } -} // Get the default set of supported codecs. // is_decoder_factory is needed to keep track of the implict assumption that any @@ -859,6 +841,22 @@ include_rtx, trials_); } +std::vector<SdpVideoFormat> WebRtcVideoEngine::GetSupportedFormats( + bool is_decoder) const { + std::vector<SdpVideoFormat> formats; + if (is_decoder) { + formats = GetDefaultSupportedFormats(decoder_factory_.get(), + /*is_decoder_factory=*/true, trials_); + } else { + formats = GetDefaultSupportedFormats(encoder_factory_.get(), + /*is_decoder_factory=*/false, trials_); + } + if (!formats.empty()) { + formats.push_back(SdpVideoFormat(kRtxCodecName)); + } + return formats; +} + std::vector<RtpHeaderExtensionCapability> WebRtcVideoEngine::GetRtpHeaderExtensions( const FieldTrialsView* field_trials) const {
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h index 8b3efd5..d9206d0 100644 --- a/media/engine/webrtc_video_engine.h +++ b/media/engine/webrtc_video_engine.h
@@ -126,6 +126,16 @@ std::vector<Codec> LegacySendCodecs(bool include_rtx) const override; std::vector<Codec> LegacyRecvCodecs(bool include_rtx) const override; + VideoEncoderFactory* encoder_factory() const override { + return encoder_factory_.get(); + } + VideoDecoderFactory* decoder_factory() const override { + return decoder_factory_.get(); + } + + std::vector<SdpVideoFormat> GetSupportedFormats( + bool is_decoder) const override; + std::vector<RtpHeaderExtensionCapability> GetRtpHeaderExtensions( /* optional field trials from PeerConnection that override those from PeerConnectionFactory */
diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 6380b7c..886f787 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn
@@ -443,18 +443,24 @@ rtc_library("typed_codec_vendor") { visibility = [ ":*" ] sources = [ + "codec_configuration.h", "typed_codec_vendor.cc", "typed_codec_vendor.h", ] deps = [ "../api:field_trials_view", + "../api:payload_type", "../api:rtp_parameters", "../api/audio_codecs:audio_codecs_api", + "../api/video_codecs:video_codecs_api", "../media:codec", "../media:codec_list", "../media:media_constants", "../media:media_engine", "../rtc_base:checks", + "../rtc_base/containers:flat_set", + "//third_party/abseil-cpp/absl/base:nullability", + "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", ] } @@ -2252,6 +2258,7 @@ "srtp_transport_unittest.cc", "test/rtp_transport_test_util.h", "test/srtp_test_util.h", + "typed_codec_vendor_unittest.cc", "video_rtp_receiver_unittest.cc", ] @@ -2284,6 +2291,7 @@ ":srtp_session", ":srtp_transport", ":transport_stats", + ":typed_codec_vendor", ":used_ids", ":video_rtp_receiver", "../api:audio_options_api",
diff --git a/pc/codec_configuration.h b/pc/codec_configuration.h new file mode 100644 index 0000000..f563491 --- /dev/null +++ b/pc/codec_configuration.h
@@ -0,0 +1,41 @@ +/* + * Copyright 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PC_CODEC_CONFIGURATION_H_ +#define PC_CODEC_CONFIGURATION_H_ + +#include "media/base/codec.h" + +namespace webrtc { + +// ResiliencyInfo encapsulates the redundancy requirements for a codec. +struct ResiliencyInfo { + bool rtx = false; + bool red = false; + bool ulpfec = false; + bool flexfec = false; + + bool operator==(const ResiliencyInfo& other) const = default; +}; + +// CodecConfiguration stores codec attributes and associated resiliency +// requirements. The payload type (id) in the 'codec' member should be ignored. +struct CodecConfiguration { + Codec codec; + ResiliencyInfo resiliency; + + bool operator==(const CodecConfiguration& other) const { + return codec == other.codec && resiliency == other.resiliency; + } +}; + +} // namespace webrtc + +#endif // PC_CODEC_CONFIGURATION_H_
diff --git a/pc/typed_codec_vendor.cc b/pc/typed_codec_vendor.cc index 5ab4ed4..b0599de 100644 --- a/pc/typed_codec_vendor.cc +++ b/pc/typed_codec_vendor.cc
@@ -12,26 +12,38 @@ #include <functional> #include <map> +#include <string> #include <vector> +#include "absl/base/nullability.h" +#include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "api/audio_codecs/audio_format.h" #include "api/field_trials_view.h" #include "api/media_types.h" +#include "api/payload_type.h" +#include "api/video_codecs/sdp_video_format.h" #include "media/base/codec.h" #include "media/base/codec_list.h" #include "media/base/media_constants.h" #include "media/base/media_engine.h" +#include "pc/codec_configuration.h" #include "rtc_base/checks.h" +#include "rtc_base/containers/flat_set.h" namespace webrtc { namespace { -// Create the voice codecs. Do not allocate payload types at this time. -std::vector<Codec> CollectAudioCodecs( +// Create the voice codec configurations. Do not allocate payload types at this +// time. +std::vector<CodecConfiguration> CollectAudioCodecConfigurations( const std::vector<AudioCodecSpec>& specs) { - std::vector<Codec> out; + std::vector<CodecConfiguration> out; + + // Audio RED is handled by the engine, not the factory, and is always + // available for Opus. + bool has_red = true; // Only generate CN payload types for these clockrates: std::map<int, bool, std::greater<int>> generate_cn = {{8000, false}}; @@ -40,9 +52,14 @@ {48000, false}}; for (const auto& spec : specs) { - Codec codec = CreateAudioCodec(spec.format); + if (absl::EqualsIgnoreCase(spec.format.name, kRedCodecName)) { + continue; + } + + CodecConfiguration config; + config.codec = CreateAudioCodec(spec.format); if (spec.info.supports_network_adaption) { - codec.AddFeedbackParam( + config.codec.AddFeedbackParam( FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty)); } @@ -61,45 +78,138 @@ dtmf->second = true; } - out.push_back(codec); - - // TODO(hta): Don't assign RED codecs until we know that the PT for Opus - // is final - if (codec.name == kOpusCodecName) { - // We don't know the PT to put into the RED fmtp parameter yet. - // Leave it out. - Codec red_codec = CreateAudioCodec({kRedCodecName, 48000, 2}); - out.push_back(red_codec); + if (has_red && config.codec.name == kOpusCodecName) { + config.resiliency.red = true; } + out.push_back(config); } // Add CN codecs after "proper" audio codecs. for (const auto& cn : generate_cn) { if (cn.second) { - Codec cn_codec = CreateAudioCodec({kCnCodecName, cn.first, 1}); - out.push_back(cn_codec); + CodecConfiguration cn_config; + cn_config.codec = CreateAudioCodec({kCnCodecName, cn.first, 1}); + out.push_back(cn_config); } } // Add telephone-event codecs last. for (const auto& dtmf : generate_dtmf) { if (dtmf.second) { - Codec dtmf_codec = CreateAudioCodec({kDtmfCodecName, dtmf.first, 1}); - out.push_back(dtmf_codec); + CodecConfiguration dtmf_config; + dtmf_config.codec = CreateAudioCodec({kDtmfCodecName, dtmf.first, 1}); + out.push_back(dtmf_config); } } return out; } -Codecs AudioCodecsFromFactory(const VoiceEngineInterface& voice, - bool is_sender) { +std::vector<CodecConfiguration> AudioCodecConfigurationsFromFactory( + const VoiceEngineInterface& voice, + bool is_sender) { RTC_DCHECK(!is_sender || voice.encoder_factory()) << "No encoder factory"; RTC_DCHECK(is_sender || voice.decoder_factory()) << "No decoder factory"; - return CollectAudioCodecs( + return CollectAudioCodecConfigurations( is_sender ? voice.encoder_factory()->GetSupportedEncoders() : voice.decoder_factory()->GetSupportedDecoders()); } +std::vector<CodecConfiguration> CollectVideoCodecConfigurations( + const std::vector<SdpVideoFormat>& formats, + bool rtx_enabled, + const FieldTrialsView& trials) { + if (formats.empty()) { + return {}; + } + + bool has_red = false; + bool has_ulpfec = false; + bool has_flexfec = false; + bool has_rtx = false; + + for (const auto& format : formats) { + if (absl::EqualsIgnoreCase(format.name, kRedCodecName)) { + has_red = true; + } else if (absl::EqualsIgnoreCase(format.name, kUlpfecCodecName)) { + has_ulpfec = true; + } else if (absl::EqualsIgnoreCase(format.name, kFlexfecCodecName)) { + has_flexfec = true; + } else if (absl::EqualsIgnoreCase(format.name, kRtxCodecName)) { + has_rtx = true; + } + } + + std::vector<CodecConfiguration> out; + for (const auto& format : formats) { + Codec codec = CreateVideoCodec(format); + if (codec.IsResiliencyCodec()) { + continue; + } + + AddDefaultFeedbackParams(&codec, trials); + + CodecConfiguration config; + config.codec = codec; + config.codec.id = PayloadType::NotSet(); + if (rtx_enabled && has_rtx) { + Codec::ResiliencyType resiliency_type = codec.GetResiliencyType(); + if (resiliency_type != Codec::ResiliencyType::kFlexfec && + resiliency_type != Codec::ResiliencyType::kUlpfec) { + config.resiliency.rtx = true; + } + } + config.resiliency.red = has_red; + config.resiliency.ulpfec = has_ulpfec; + if (trials.IsEnabled("WebRTC-FlexFEC-03-Advertised")) { + config.resiliency.flexfec = has_flexfec; + } + out.push_back(config); + } + return out; +} + +std::vector<CodecConfiguration> VideoCodecConfigurationsFromFactory( + const VideoEngineInterface& video, + bool is_sender, + bool rtx_enabled, + const FieldTrialsView& trials) { + return CollectVideoCodecConfigurations(video.GetSupportedFormats(!is_sender), + rtx_enabled, trials); +} + +Codecs CodecsFromConfigurations( + const std::vector<CodecConfiguration>& configurations, + MediaType type) { + Codecs out; + flat_set<std::string> shared_added; + for (const auto& config : configurations) { + out.push_back(config.codec); + if (type == MediaType::AUDIO) { + if (config.resiliency.red && shared_added.insert(kRedCodecName).second) { + out.push_back(CreateAudioCodec({kRedCodecName, 48000, 2})); + } + } else { + if (config.resiliency.rtx) { + out.push_back(CreateVideoCodec(PayloadType::NotSet(), kRtxCodecName)); + } + 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 (config.resiliency.ulpfec && + shared_added.insert(kUlpfecCodecName).second) { + out.push_back(CreateVideoCodec(kUlpfecCodecName)); + } + if (config.resiliency.flexfec && + shared_added.insert(kFlexfecCodecName).second) { + out.push_back(CreateVideoCodec(kFlexfecCodecName)); + } + } + } + return out; +} + Codecs GetLegacyVideoCodecs(const VideoEngineInterface& video, bool is_sender, bool rtx_enabled) { @@ -110,18 +220,9 @@ Codecs GetCodecs(const MediaEngineInterface* media_engine, MediaType type, bool is_sender, - bool rtx_enabled, - const FieldTrialsView& trials) { + bool rtx_enabled) { const VoiceEngineInterface& voice = media_engine->voice(); const VideoEngineInterface& video = media_engine->video(); - if (trials.IsEnabled("WebRTC-PayloadTypesInTransport")) { - // Use legacy mechanisms for getting codecs from video engine. - // TODO: https://issues.webrtc.org/360058654 - apply late assign to video. - return (type == MediaType::AUDIO) - ? AudioCodecsFromFactory(voice, is_sender) - : GetLegacyVideoCodecs(video, is_sender, rtx_enabled); - } - // Use current mechanisms for getting codecs from media engine. return (type == MediaType::AUDIO) ? (is_sender ? voice.LegacySendCodecs() : voice.LegacyRecvCodecs()) @@ -130,12 +231,28 @@ } // namespace -TypedCodecVendor::TypedCodecVendor(const MediaEngineInterface* media_engine, +TypedCodecVendor::TypedCodecVendor(const MediaEngineInterface* absl_nonnull + media_engine, MediaType type, bool is_sender, bool rtx_enabled, - const FieldTrialsView& trials) - : codecs_(CodecList::CreateFromTrustedData( - GetCodecs(media_engine, type, is_sender, rtx_enabled, trials))) {} + const FieldTrialsView& trials) { + RTC_DCHECK(media_engine != nullptr); + + if (trials.IsEnabled("WebRTC-PayloadTypesInTransport")) { + if (type == MediaType::AUDIO) { + configurations_ = + AudioCodecConfigurationsFromFactory(media_engine->voice(), is_sender); + } else { + configurations_ = VideoCodecConfigurationsFromFactory( + media_engine->video(), is_sender, rtx_enabled, trials); + } + codecs_ = CodecList::CreateFromTrustedData( + CodecsFromConfigurations(configurations_, type)); + } else { + codecs_ = CodecList::CreateFromTrustedData( + GetCodecs(media_engine, type, is_sender, rtx_enabled)); + } +} } // namespace webrtc
diff --git a/pc/typed_codec_vendor.h b/pc/typed_codec_vendor.h index 6d4b92b..a3d187c 100644 --- a/pc/typed_codec_vendor.h +++ b/pc/typed_codec_vendor.h
@@ -12,11 +12,13 @@ #define PC_TYPED_CODEC_VENDOR_H_ #include <utility> +#include <vector> #include "api/field_trials_view.h" #include "api/media_types.h" #include "media/base/codec_list.h" #include "media/base/media_engine.h" +#include "pc/codec_configuration.h" namespace webrtc { @@ -46,10 +48,14 @@ const FieldTrialsView& trials); const CodecList& codecs() const { return codecs_; } + const std::vector<CodecConfiguration>& configurations() const { + return configurations_; + } private: // Effectively const, but not marked as such since that breaks move semantics. CodecList codecs_; + std::vector<CodecConfiguration> configurations_; }; } // namespace webrtc
diff --git a/pc/typed_codec_vendor_unittest.cc b/pc/typed_codec_vendor_unittest.cc new file mode 100644 index 0000000..c9fe878 --- /dev/null +++ b/pc/typed_codec_vendor_unittest.cc
@@ -0,0 +1,149 @@ +/* + * Copyright 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/typed_codec_vendor.h" + +#include <vector> + +#include "api/field_trials.h" +#include "api/media_types.h" +#include "api/payload_type.h" +#include "media/base/codec.h" +#include "media/base/fake_media_engine.h" +#include "pc/codec_configuration.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Contains; +using ::testing::Field; +using ::testing::Property; + +TEST(TypedCodecVendorTest, VideoCodecsFromFactoryWhenTrialEnabled) { + FieldTrials trials( + "WebRTC-PayloadTypesInTransport/Enabled/" + "WebRTC-FlexFEC-03-Advertised/Enabled/"); + FakeMediaEngine media_engine; + std::vector<Codec> video_codecs({ + CreateVideoCodec(97, "vp8"), + CreateVideoRtxCodec(98, 97), + CreateVideoCodec(100, "red"), + CreateVideoCodec(101, "ulpfec"), + CreateVideoCodec(102, "flexfec-03"), + }); + media_engine.SetVideoSendCodecs(video_codecs); + + TypedCodecVendor vendor(&media_engine, MediaType::VIDEO, /*is_sender=*/true, + /*rtx_enabled=*/true, trials); + + const auto& codecs = vendor.codecs().codecs(); + EXPECT_THAT(codecs, Contains(Field("name", &Codec::name, "vp8"))); + + for (const auto& codec : codecs) { + EXPECT_EQ(codec.id, PayloadType::NotSet()); + } + + const auto& configurations = vendor.configurations(); + EXPECT_THAT(configurations, + Contains(Field("codec", &CodecConfiguration::codec, + Field("name", &Codec::name, "vp8")))); + for (const auto& config : configurations) { + if (config.codec.name == "vp8") { + EXPECT_TRUE(config.resiliency.rtx); + EXPECT_TRUE(config.resiliency.red); + EXPECT_TRUE(config.resiliency.flexfec); + // Verify feedback params (added by AddDefaultFeedbackParams) + EXPECT_THAT(config.codec.feedback_params.params(), + Contains(Property(&FeedbackParam::id, "goog-remb"))); + EXPECT_THAT(config.codec.feedback_params.params(), + Contains(Property(&FeedbackParam::id, "transport-cc"))); + } + } +} + +TEST(TypedCodecVendorTest, VideoCodecsFromFactoryWhenResiliencyAbsent) { + FieldTrials trials("WebRTC-PayloadTypesInTransport/Enabled/"); + FakeMediaEngine media_engine; + std::vector<Codec> video_codecs({ + CreateVideoCodec(97, "vp8"), + }); + media_engine.SetVideoSendCodecs(video_codecs); + + TypedCodecVendor vendor(&media_engine, MediaType::VIDEO, /*is_sender=*/true, + /*rtx_enabled=*/true, trials); + + const auto& configurations = vendor.configurations(); + EXPECT_THAT(configurations, + Contains(Field("codec", &CodecConfiguration::codec, + Field("name", &Codec::name, "vp8")))); + for (const auto& config : configurations) { + if (config.codec.name == "vp8") { + EXPECT_FALSE(config.resiliency.rtx); + EXPECT_FALSE(config.resiliency.red); + EXPECT_FALSE(config.resiliency.ulpfec); + EXPECT_FALSE(config.resiliency.flexfec); + } + } +} + +TEST(TypedCodecVendorTest, VideoCodecsLegacyWhenTrialDisabled) { + FieldTrials trials("WebRTC-PayloadTypesInTransport/Disabled/"); + FakeMediaEngine media_engine; + std::vector<Codec> video_codecs({ + CreateVideoCodec(97, "vp8"), + }); + media_engine.SetVideoSendCodecs(video_codecs); + + TypedCodecVendor vendor(&media_engine, MediaType::VIDEO, /*is_sender=*/true, + /*rtx_enabled=*/false, trials); + + const auto& codecs = vendor.codecs().codecs(); + ASSERT_EQ(codecs.size(), 1u); + EXPECT_EQ(codecs[0].name, "vp8"); + EXPECT_EQ(codecs[0].id, PayloadType(97)); + + EXPECT_TRUE(vendor.configurations().empty()); +} + +TEST(TypedCodecVendorTest, AudioCodecsFromFactoryWhenTrialEnabled) { + FieldTrials trials("WebRTC-PayloadTypesInTransport/Enabled/"); + FakeMediaEngine media_engine; + std::vector<Codec> audio_codecs({ + CreateAudioCodec(111, "opus", 48000, 2), + CreateAudioCodec(63, "red", 48000, 2), + }); + media_engine.SetAudioSendCodecs(audio_codecs); + + TypedCodecVendor vendor(&media_engine, MediaType::AUDIO, /*is_sender=*/true, + /*rtx_enabled=*/false, trials); + + const auto& codecs = vendor.codecs().codecs(); + EXPECT_THAT(codecs, Contains(Field("name", &Codec::name, "opus"))); + EXPECT_THAT(codecs, Contains(Field("name", &Codec::name, "red"))); + + for (const auto& codec : codecs) { + EXPECT_EQ(codec.id, PayloadType::NotSet()); + } + + const auto& configurations = vendor.configurations(); + EXPECT_THAT(configurations, + Contains(Field("codec", &CodecConfiguration::codec, + Field("name", &Codec::name, "opus")))); + for (const auto& config : configurations) { + if (config.codec.name == "opus") { + EXPECT_TRUE(config.resiliency.red); + } + } +} + +} // namespace +} // namespace webrtc