Move non-PT-assigning codec collection out of VoiceMediaEngine

Bug: webrtc:360058654
Change-Id: I23891ce0ad13a3294bfe4ac594cd5fdc1a8235ba
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/379981
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44056}
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index 5de5b4d..36c0d81 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -29,6 +29,8 @@
 #include "absl/strings/string_view.h"
 #include "api/audio/audio_device.h"
 #include "api/audio_codecs/audio_codec_pair_id.h"
+#include "api/audio_codecs/audio_decoder_factory.h"
+#include "api/audio_codecs/audio_encoder_factory.h"
 #include "api/audio_options.h"
 #include "api/call/audio_sink.h"
 #include "api/crypto/crypto_options.h"
@@ -815,6 +817,12 @@
   // TODO: https://issues.webrtc.org/360058654 - stop faking codecs here.
   const std::vector<Codec>& LegacySendCodecs() const override;
   const std::vector<Codec>& LegacyRecvCodecs() const override;
+  webrtc::AudioEncoderFactory* encoder_factory() const override {
+    return nullptr;
+  }
+  webrtc::AudioDecoderFactory* decoder_factory() const override {
+    return nullptr;
+  }
   void SetCodecs(const std::vector<Codec>& codecs);
   void SetRecvCodecs(const std::vector<Codec>& codecs);
   void SetSendCodecs(const std::vector<Codec>& codecs);
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index a42a69704..6d7f086 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -19,6 +19,8 @@
 #include "api/array_view.h"
 #include "api/audio/audio_device.h"
 #include "api/audio_codecs/audio_codec_pair_id.h"
+#include "api/audio_codecs/audio_decoder_factory.h"
+#include "api/audio_codecs/audio_encoder_factory.h"
 #include "api/audio_options.h"
 #include "api/crypto/crypto_options.h"
 #include "api/field_trials_view.h"
@@ -131,6 +133,9 @@
   virtual const std::vector<Codec>& LegacySendCodecs() const = 0;
   virtual const std::vector<Codec>& LegacyRecvCodecs() const = 0;
 
+  virtual webrtc::AudioEncoderFactory* encoder_factory() const = 0;
+  virtual webrtc::AudioDecoderFactory* decoder_factory() const = 0;
+
   // Starts AEC dump using existing file, a maximum file size in bytes can be
   // specified. Logging is stopped just before the size limit is exceeded.
   // If max_size_bytes is set to a value <= 0, no limit will be used.
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index 30cf90e..1f7f361 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -118,6 +118,13 @@
 
   const std::vector<Codec>& LegacySendCodecs() const override;
   const std::vector<Codec>& LegacyRecvCodecs() const override;
+
+  webrtc::AudioEncoderFactory* encoder_factory() const override {
+    return encoder_factory_.get();
+  }
+  webrtc::AudioDecoderFactory* decoder_factory() const override {
+    return decoder_factory_.get();
+  }
   std::vector<webrtc::RtpHeaderExtensionCapability> GetRtpHeaderExtensions()
       const override;
 
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 63745a9..42b5047 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -422,7 +422,9 @@
     ":media_options",
     ":rtp_media_utils",
     ":session_description",
+    ":typed_codec_vendor",
     ":used_ids",
+    "../api:field_trials_view",
     "../api:rtc_error",
     "../api:rtp_parameters",
     "../api:rtp_transceiver_direction",
@@ -443,6 +445,24 @@
   ]
 }
 
+rtc_library("typed_codec_vendor") {
+  visibility = [ ":*" ]
+  sources = [
+    "typed_codec_vendor.cc",
+    "typed_codec_vendor.h",
+  ]
+  deps = [
+    "../api:field_trials_view",
+    "../api:rtp_parameters",
+    "../api/audio_codecs:audio_codecs_api",
+    "../media:codec",
+    "../media:codec_list",
+    "../media:media_constants",
+    "../media:media_engine",
+    "//third_party/abseil-cpp/absl/strings:string_view",
+  ]
+}
+
 rtc_source_set("media_stream_proxy") {
   visibility = [ ":*" ]
   sources = [ "media_stream_proxy.h" ]
@@ -2086,6 +2106,7 @@
       "../api:audio_options_api",
       "../api:candidate",
       "../api:dtls_transport_interface",
+      "../api:field_trials",
       "../api:ice_transport_factory",
       "../api:ice_transport_interface",
       "../api:libjingle_peerconnection_api",
diff --git a/pc/codec_vendor.cc b/pc/codec_vendor.cc
index 72f5d49..53338e8 100644
--- a/pc/codec_vendor.cc
+++ b/pc/codec_vendor.cc
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2004 The WebRTC project authors. All Rights Reserved.
+ *  Copyright 2025 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
@@ -22,6 +22,7 @@
 #include "absl/algorithm/container.h"
 #include "absl/strings/match.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_parameters.h"
@@ -36,6 +37,7 @@
 #include "pc/media_options.h"
 #include "pc/rtp_media_utils.h"
 #include "pc/session_description.h"
+#include "pc/typed_codec_vendor.h"
 #include "pc/used_ids.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -769,43 +771,25 @@
   return negotiated_codecs.codecs();
 }
 
-TypedCodecVendor::TypedCodecVendor(MediaEngineInterface* media_engine,
-                                   MediaType type,
-                                   bool is_sender,
-                                   bool rtx_enabled) {
-  // TODO: https://issues.webrtc.org/360058654 - move codec selection here
-  // when field trial WebRTC-PayloadTypesInTransport is enabled.
-  if (type == MEDIA_TYPE_AUDIO) {
-    if (is_sender) {
-      codecs_ = CodecList::CreateFromTrustedData(
-          media_engine->voice().LegacySendCodecs());
-    } else {
-      codecs_ = CodecList::CreateFromTrustedData(
-          media_engine->voice().LegacyRecvCodecs());
-    }
-  } else {
-    if (is_sender) {
-      codecs_ = CodecList::CreateFromTrustedData(
-          media_engine->video().LegacySendCodecs(rtx_enabled));
-    } else {
-      codecs_ = CodecList::CreateFromTrustedData(
-          media_engine->video().LegacyRecvCodecs(rtx_enabled));
-    }
-  }
-}
-
-CodecVendor::CodecVendor(MediaEngineInterface* media_engine, bool rtx_enabled) {
-  // Null media_engine is permitted in order to allow unit testing where
+CodecVendor::CodecVendor(MediaEngineInterface* media_engine,
+                         bool rtx_enabled,
+                         const webrtc::FieldTrialsView&
+                             trials) {  // Null media_engine is permitted in
+                                        // order to allow unit testing where
   // the codecs are explicitly set by the test.
   if (media_engine) {
-    audio_send_codecs_ = TypedCodecVendor(media_engine, MEDIA_TYPE_AUDIO,
-                                          /* is_sender= */ true, rtx_enabled);
-    audio_recv_codecs_ = TypedCodecVendor(media_engine, MEDIA_TYPE_AUDIO,
-                                          /* is_sender= */ false, rtx_enabled);
-    video_send_codecs_ = TypedCodecVendor(media_engine, MEDIA_TYPE_VIDEO,
-                                          /* is_sender= */ true, rtx_enabled);
-    video_recv_codecs_ = TypedCodecVendor(media_engine, MEDIA_TYPE_VIDEO,
-                                          /* is_sender= */ false, rtx_enabled);
+    audio_send_codecs_ =
+        TypedCodecVendor(media_engine, MEDIA_TYPE_AUDIO,
+                         /* is_sender= */ true, rtx_enabled, trials);
+    audio_recv_codecs_ =
+        TypedCodecVendor(media_engine, MEDIA_TYPE_AUDIO,
+                         /* is_sender= */ false, rtx_enabled, trials);
+    video_send_codecs_ =
+        TypedCodecVendor(media_engine, MEDIA_TYPE_VIDEO,
+                         /* is_sender= */ true, rtx_enabled, trials);
+    video_recv_codecs_ =
+        TypedCodecVendor(media_engine, MEDIA_TYPE_VIDEO,
+                         /* is_sender= */ false, rtx_enabled, trials);
   }
 }
 
diff --git a/pc/codec_vendor.h b/pc/codec_vendor.h
index 2deebcc..28fae16 100644
--- a/pc/codec_vendor.h
+++ b/pc/codec_vendor.h
@@ -13,6 +13,7 @@
 
 #include <vector>
 
+#include "api/field_trials_view.h"
 #include "api/rtc_error.h"
 #include "api/rtp_transceiver_direction.h"
 #include "call/payload_type.h"
@@ -21,31 +22,10 @@
 #include "media/base/media_engine.h"
 #include "pc/media_options.h"
 #include "pc/session_description.h"
+#include "pc/typed_codec_vendor.h"
 
 namespace cricket {
 
-// This class vends codecs of a specific type only.
-// It is intended to eventually be owned by the RtpSender and RtpReceiver
-// objects.
-class TypedCodecVendor {
- public:
-  // Constructor for the case where media engine is not provided. The resulting
-  // vendor will always return an empty codec list.
-  TypedCodecVendor() {}
-  TypedCodecVendor(MediaEngineInterface* media_engine,
-                   MediaType type,
-                   bool is_sender,
-                   bool rtx_enabled);
-  const CodecList& codecs() const { return codecs_; }
-  void set_codecs(const CodecList& codecs) { codecs_ = codecs; }
-  // For easy initialization, copying is allowed.
-  TypedCodecVendor(const TypedCodecVendor& from) = default;
-  TypedCodecVendor& operator=(const TypedCodecVendor& from) = default;
-
- private:
-  CodecList codecs_;
-};
-
 // This class contains the functions required to compute the list of codecs
 // for SDP offer/answer. It is exposed to MediaSessionDescriptionFactory
 // for the construction of offers and answers.
@@ -61,7 +41,9 @@
 // - Thread guard
 class CodecVendor {
  public:
-  CodecVendor(MediaEngineInterface* media_engine, bool rtx_enabled);
+  CodecVendor(MediaEngineInterface* media_engine,
+              bool rtx_enabled,
+              const webrtc::FieldTrialsView& trials);
 
  public:
   webrtc::RTCError GetCodecsForOffer(
diff --git a/pc/codec_vendor_unittest.cc b/pc/codec_vendor_unittest.cc
index 178c8ee..f25f9d3 100644
--- a/pc/codec_vendor_unittest.cc
+++ b/pc/codec_vendor_unittest.cc
@@ -12,11 +12,12 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "absl/strings/string_view.h"
-#include "call/payload_type.h"
+#include "api/field_trials.h"
 #include "media/base/codec.h"
 #include "media/base/codec_list.h"
 #include "media/base/media_constants.h"
@@ -53,7 +54,9 @@
 };
 
 TEST(CodecVendorTest, TestSetAudioCodecs) {
-  CodecVendor codec_vendor(nullptr, false);
+  std::unique_ptr<webrtc::FieldTrials> trials =
+      webrtc::FieldTrials::CreateNoGlobal("");
+  CodecVendor codec_vendor(nullptr, false, *trials);
   std::vector<Codec> send_codecs = MAKE_VECTOR(kAudioCodecs1);
   std::vector<Codec> recv_codecs = MAKE_VECTOR(kAudioCodecs2);
 
diff --git a/pc/media_session.cc b/pc/media_session.cc
index d8bec5b..8cd77a6 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -49,7 +49,6 @@
 #include "pc/used_ids.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
-#include "rtc_base/strings/string_builder.h"
 #include "rtc_base/unique_id_generator.h"
 
 #ifdef RTC_ENABLE_H265
@@ -693,7 +692,8 @@
           transport_desc_factory_->trials().IsEnabled(
               "WebRTC-PayloadTypesInTransport")) {
   RTC_CHECK(transport_desc_factory_);
-  codec_vendor_ = std::make_unique<CodecVendor>(media_engine, rtx_enabled);
+  codec_vendor_ = std::make_unique<CodecVendor>(
+      media_engine, rtx_enabled, transport_desc_factory_->trials());
 }
 
 RtpHeaderExtensions
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 92f43fe..6dde889 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -1084,7 +1084,8 @@
   std::vector<cricket::Codec> codecs;
   // Gather the current codec capabilities to allow checking scalabilityMode and
   // codec selection against supported values.
-  cricket::CodecVendor codec_vendor(context_->media_engine(), false);
+  cricket::CodecVendor codec_vendor(context_->media_engine(), false,
+                                    context_->env().field_trials());
   if (media_type == cricket::MEDIA_TYPE_VIDEO) {
     codecs = codec_vendor.video_send_codecs().codecs();
   } else {
diff --git a/pc/peer_connection_factory.cc b/pc/peer_connection_factory.cc
index 9c35c86..cb6a581 100644
--- a/pc/peer_connection_factory.cc
+++ b/pc/peer_connection_factory.cc
@@ -147,7 +147,8 @@
 RtpCapabilities PeerConnectionFactory::GetRtpSenderCapabilities(
     cricket::MediaType kind) const {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  cricket::CodecVendor codec_vendor(media_engine(), context_->use_rtx());
+  cricket::CodecVendor codec_vendor(media_engine(), context_->use_rtx(),
+                                    context_->env().field_trials());
   switch (kind) {
     case cricket::MEDIA_TYPE_AUDIO: {
       cricket::Codecs cricket_codecs;
@@ -175,7 +176,8 @@
 RtpCapabilities PeerConnectionFactory::GetRtpReceiverCapabilities(
     cricket::MediaType kind) const {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  cricket::CodecVendor codec_vendor(media_engine(), context_->use_rtx());
+  cricket::CodecVendor codec_vendor(media_engine(), context_->use_rtx(),
+                                    context_->env().field_trials());
   switch (kind) {
     case cricket::MEDIA_TYPE_AUDIO: {
       cricket::Codecs cricket_codecs;
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 1e63d58..620b6aa 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -120,7 +120,9 @@
       unified_plan_(false),
       media_type_(media_type),
       context_(context),
-      codec_vendor_(context->media_engine(), /* use_rtx= */ false) {
+      codec_vendor_(context->media_engine(),
+                    /* use_rtx= */ false,
+                    context->env().field_trials()) {
   RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
              media_type == cricket::MEDIA_TYPE_VIDEO);
   RTC_DCHECK(context_);
@@ -139,7 +141,9 @@
       context_(context),
       header_extensions_to_negotiate_(
           std::move(header_extensions_to_negotiate)),
-      codec_vendor_(context->media_engine(), context->use_rtx()),
+      codec_vendor_(context->media_engine(),
+                    context->use_rtx(),
+                    context->env().field_trials()),
       on_negotiation_needed_(std::move(on_negotiation_needed)) {
   RTC_DCHECK(context_);
   RTC_DCHECK(media_type_ == cricket::MEDIA_TYPE_AUDIO ||
diff --git a/pc/typed_codec_vendor.cc b/pc/typed_codec_vendor.cc
new file mode 100644
index 0000000..fd320cd
--- /dev/null
+++ b/pc/typed_codec_vendor.cc
@@ -0,0 +1,147 @@
+/*
+ *  Copyright 2025 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 <stddef.h>
+
+#include <functional>
+#include <map>
+#include <vector>
+
+#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 "media/base/codec.h"
+#include "media/base/codec_list.h"
+#include "media/base/media_constants.h"
+#include "media/base/media_engine.h"
+
+namespace cricket {
+
+namespace {
+
+// Create the voice codecs. Do not allocate payload types at this time.
+std::vector<Codec> CollectAudioCodecs(
+    const std::vector<webrtc::AudioCodecSpec>& specs) {
+  std::vector<Codec> out;
+
+  // Only generate CN payload types for these clockrates:
+  std::map<int, bool, std::greater<int>> generate_cn = {{8000, false}};
+  // Only generate telephone-event payload types for these clockrates:
+  std::map<int, bool, std::greater<int>> generate_dtmf = {{8000, false},
+                                                          {48000, false}};
+
+  for (const auto& spec : specs) {
+    cricket::Codec codec = CreateAudioCodec(spec.format);
+    if (spec.info.supports_network_adaption) {
+      codec.AddFeedbackParam(
+          FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty));
+    }
+
+    if (spec.info.allow_comfort_noise) {
+      // Generate a CN entry if the decoder allows it and we support the
+      // clockrate.
+      auto cn = generate_cn.find(spec.format.clockrate_hz);
+      if (cn != generate_cn.end()) {
+        cn->second = true;
+      }
+    }
+
+    // Generate a telephone-event entry if we support the clockrate.
+    auto dtmf = generate_dtmf.find(spec.format.clockrate_hz);
+    if (dtmf != generate_dtmf.end()) {
+      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.
+      cricket::Codec red_codec = CreateAudioCodec({kRedCodecName, 48000, 2});
+      out.push_back(red_codec);
+    }
+  }
+
+  // Add CN codecs after "proper" audio codecs.
+  for (const auto& cn : generate_cn) {
+    if (cn.second) {
+      cricket::Codec cn_codec = CreateAudioCodec({kCnCodecName, cn.first, 1});
+      out.push_back(cn_codec);
+    }
+  }
+
+  // Add telephone-event codecs last.
+  for (const auto& dtmf : generate_dtmf) {
+    if (dtmf.second) {
+      cricket::Codec dtmf_codec =
+          CreateAudioCodec({kDtmfCodecName, dtmf.first, 1});
+      out.push_back(dtmf_codec);
+    }
+  }
+  return out;
+}
+
+}  // namespace
+
+TypedCodecVendor::TypedCodecVendor(MediaEngineInterface* media_engine,
+                                   MediaType type,
+                                   bool is_sender,
+                                   bool rtx_enabled,
+                                   const webrtc::FieldTrialsView& trials) {
+  // TODO: https://issues.webrtc.org/360058654 - move codec selection here
+  // when field trial WebRTC-PayloadTypesInTransport is enabled.
+  if (trials.IsEnabled("WebRTC-PayloadTypesInTransport")) {
+    // Get the capabilities from the factory and compute the codecs.
+    // Use legacy mechanisms for getting codecs from media engine.
+    if (type == MEDIA_TYPE_AUDIO) {
+      if (is_sender) {
+        codecs_ = CodecList::CreateFromTrustedData(CollectAudioCodecs(
+            media_engine->voice().encoder_factory()->GetSupportedEncoders()));
+      } else {
+        codecs_ = CodecList::CreateFromTrustedData(CollectAudioCodecs(
+            media_engine->voice().decoder_factory()->GetSupportedDecoders()));
+      }
+    } else {
+      if (is_sender) {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->video().LegacySendCodecs(rtx_enabled));
+      } else {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->video().LegacyRecvCodecs(rtx_enabled));
+      }
+    }
+  } else {
+    // Use current mechanisms for getting codecs from media engine.
+    if (type == MEDIA_TYPE_AUDIO) {
+      if (is_sender) {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->voice().LegacySendCodecs());
+      } else {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->voice().LegacyRecvCodecs());
+      }
+    } else {
+      if (is_sender) {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->video().LegacySendCodecs(rtx_enabled));
+      } else {
+        codecs_ = CodecList::CreateFromTrustedData(
+            media_engine->video().LegacyRecvCodecs(rtx_enabled));
+      }
+    }
+  }
+}
+
+}  // namespace cricket
diff --git a/pc/typed_codec_vendor.h b/pc/typed_codec_vendor.h
new file mode 100644
index 0000000..f3c1403
--- /dev/null
+++ b/pc/typed_codec_vendor.h
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2025 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_TYPED_CODEC_VENDOR_H_
+#define PC_TYPED_CODEC_VENDOR_H_
+
+#include "api/field_trials_view.h"
+#include "api/media_types.h"
+#include "media/base/codec_list.h"
+#include "media/base/media_engine.h"
+
+namespace cricket {
+
+// This class vends codecs of a specific type only.
+// It is intended to eventually be owned by the RtpSender and RtpReceiver
+// objects.
+class TypedCodecVendor {
+ public:
+  // Constructor for the case where media engine is not provided. The resulting
+  // vendor will always return an empty codec list.
+  TypedCodecVendor() {}
+  TypedCodecVendor(MediaEngineInterface* media_engine,
+                   MediaType type,
+                   bool is_sender,
+                   bool rtx_enabled,
+                   const webrtc::FieldTrialsView& trials);
+  const CodecList& codecs() const { return codecs_; }
+  void set_codecs(const CodecList& codecs) { codecs_ = codecs; }
+  // For easy initialization, copying is allowed.
+  TypedCodecVendor(const TypedCodecVendor& from) = default;
+  TypedCodecVendor& operator=(const TypedCodecVendor& from) = default;
+
+ private:
+  CodecList codecs_;
+};
+
+}  // namespace cricket
+
+#endif  // PC_TYPED_CODEC_VENDOR_H_