Initial implementation of PayloadTypePicker

Bug: webrtc:360058654
Change-Id: I3183939a32744e9389ae2431cc04f8aa517d7efa
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359761
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42805}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index fe9bb65..68c1097 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -404,6 +404,18 @@
   ]
 }
 
+rtc_library("payload_type_picker") {
+  sources = [
+    "payload_type_picker.cc",
+    "payload_type_picker.h",
+  ]
+  deps = [
+    "../api:rtc_error",
+    "../media:codec",
+    "../rtc_base:strong_alias",
+  ]
+}
+
 rtc_source_set("peer_connection_factory_proxy") {
   visibility = [ ":*" ]
   sources = [ "peer_connection_factory_proxy.h" ]
@@ -1080,6 +1092,7 @@
     ":ice_server_parsing",
     ":jsep_transport_controller",
     ":legacy_stats_collector",
+    ":payload_type_picker",
     ":peer_connection_internal",
     ":peer_connection_message_handler",
     ":rtc_stats_collector",
@@ -1963,6 +1976,7 @@
       "jsep_transport_controller_unittest.cc",
       "jsep_transport_unittest.cc",
       "media_session_unittest.cc",
+      "payload_type_picker_unittest.cc",
       "rtcp_mux_filter_unittest.cc",
       "rtp_transport_unittest.cc",
       "sctp_transport_unittest.cc",
@@ -1992,6 +2006,7 @@
       ":libjingle_peerconnection",
       ":media_protocol_names",
       ":media_session",
+      ":payload_type_picker",
       ":pc_test_utils",
       ":rtc_pc",
       ":rtcp_mux_filter",
diff --git a/pc/payload_type_picker.cc b/pc/payload_type_picker.cc
new file mode 100644
index 0000000..9a6368e
--- /dev/null
+++ b/pc/payload_type_picker.cc
@@ -0,0 +1,96 @@
+/*
+ *  Copyright 2024 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/payload_type_picker.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "api/rtc_error.h"
+#include "media/base/codec.h"
+
+namespace webrtc {
+
+namespace {
+
+// Note: The only fields we need from a Codec are the type (audio/video),
+// the subtype (vp8/h264/....), the clock rate, the channel count, and the
+// fmtp parameters. The use of cricket::Codec, which contains more fields,
+// is only a temporary measure.
+
+bool MatchesForSdp(const cricket::Codec& codec_1,
+                   const cricket::Codec& codec_2) {
+  return codec_1.name == codec_2.name && codec_1.type == codec_2.type &&
+         codec_1.channels == codec_2.channels &&
+         codec_1.clockrate == codec_2.clockrate &&
+         codec_1.params == codec_2.params;
+}
+
+}  // namespace
+
+RTCErrorOr<PayloadType> PayloadTypePicker::SuggestMapping(
+    cricket::Codec codec) {
+  return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented yet");
+}
+
+RTCError PayloadTypePicker::AddMapping(PayloadType payload_type,
+                                       cricket::Codec codec) {
+  return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented yet");
+}
+
+RTCError PayloadTypeRecorder::AddMapping(PayloadType payload_type,
+                                         cricket::Codec codec) {
+  if (payload_type_to_codec_.find(payload_type) !=
+      payload_type_to_codec_.end()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "Attempt to insert duplicate mapping for PT");
+  }
+  payload_type_to_codec_.emplace(payload_type, codec);
+  suggester_.AddMapping(payload_type, codec);
+  return RTCError::OK();
+}
+
+std::vector<std::pair<PayloadType, cricket::Codec>>
+PayloadTypeRecorder::GetMappings() {
+  return std::vector<std::pair<PayloadType, cricket::Codec>>{};
+}
+
+RTCErrorOr<PayloadType> PayloadTypeRecorder::LookupPayloadType(
+    cricket::Codec codec) {
+  // Note that having multiple PTs mapping to the same codec is NOT an error.
+  // In this case, we return the first found (not deterministic).
+  auto result = std::find_if(
+      payload_type_to_codec_.begin(), payload_type_to_codec_.end(),
+      [codec](const auto& iter) { return MatchesForSdp(iter.second, codec); });
+  if (result == payload_type_to_codec_.end()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER,
+                    "No payload type found for codec");
+  }
+  return result->first;
+}
+
+RTCErrorOr<cricket::Codec> PayloadTypeRecorder::LookupCodec(
+    PayloadType payload_type) {
+  auto result = payload_type_to_codec_.find(payload_type);
+  if (result == payload_type_to_codec_.end()) {
+    return RTCError(RTCErrorType::INVALID_PARAMETER, "No such payload type");
+  }
+  return result->second;
+}
+
+void PayloadTypeRecorder::Checkpoint() {
+  checkpoint_payload_type_to_codec_ = payload_type_to_codec_;
+}
+void PayloadTypeRecorder::Rollback() {
+  payload_type_to_codec_ = checkpoint_payload_type_to_codec_;
+}
+
+}  // namespace webrtc
diff --git a/pc/payload_type_picker.h b/pc/payload_type_picker.h
new file mode 100644
index 0000000..7cbc5ba
--- /dev/null
+++ b/pc/payload_type_picker.h
@@ -0,0 +1,61 @@
+/*
+ *  Copyright 2024 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_PAYLOAD_TYPE_PICKER_H_
+#define PC_PAYLOAD_TYPE_PICKER_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "api/rtc_error.h"
+#include "media/base/codec.h"
+#include "rtc_base/strong_alias.h"
+
+namespace webrtc {
+
+class PayloadType : public StrongAlias<class PayloadTypeTag, uint8_t> {
+ public:
+  // Non-explicit conversions from and to ints are to be deprecated and
+  // removed once calling code is upgraded.
+  PayloadType(uint8_t pt) { value_ = pt; }                // NOLINT: explicit
+  constexpr operator uint8_t() const& { return value_; }  // NOLINT: Explicit
+};
+
+class PayloadTypePicker {
+ public:
+  RTCErrorOr<PayloadType> SuggestMapping(cricket::Codec codec);
+  RTCError AddMapping(PayloadType payload_type, cricket::Codec codec);
+};
+
+class PayloadTypeRecorder {
+ public:
+  explicit PayloadTypeRecorder(PayloadTypePicker& suggester)
+      : suggester_(suggester) {}
+
+  RTCError AddMapping(PayloadType payload_type, cricket::Codec codec);
+  std::vector<std::pair<PayloadType, cricket::Codec>> GetMappings();
+  RTCErrorOr<PayloadType> LookupPayloadType(cricket::Codec codec);
+  RTCErrorOr<cricket::Codec> LookupCodec(PayloadType payload_type);
+  // Transaction support.
+  // Checkpoint() commits previous changes.
+  void Checkpoint();
+  // Rollback() rolls back to the previous checkpoint.
+  void Rollback();
+
+ private:
+  PayloadTypePicker& suggester_;
+  std::map<PayloadType, cricket::Codec> payload_type_to_codec_;
+  std::map<PayloadType, cricket::Codec> checkpoint_payload_type_to_codec_;
+};
+
+}  // namespace webrtc
+
+#endif  //  PC_PAYLOAD_TYPE_PICKER_H_
diff --git a/pc/payload_type_picker_unittest.cc b/pc/payload_type_picker_unittest.cc
new file mode 100644
index 0000000..0e486bf
--- /dev/null
+++ b/pc/payload_type_picker_unittest.cc
@@ -0,0 +1,94 @@
+/*
+ *  Copyright 2024 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/payload_type_picker.h"
+
+#include "media/base/codec.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+TEST(PayloadTypePicker, PayloadTypeAssignmentWorks) {
+  // Note: This behavior is due to be deprecated and removed.
+  PayloadType pt_a(1);
+  PayloadType pt_b = 1;  // Implicit conversion
+  EXPECT_EQ(pt_a, pt_b);
+  int pt_as_int = pt_a;  // Implicit conversion
+  EXPECT_EQ(1, pt_as_int);
+}
+
+TEST(PayloadTypePicker, InstantiateTypes) {
+  PayloadTypePicker picker;
+  PayloadTypeRecorder recorder(picker);
+}
+
+TEST(PayloadTypePicker, StoreAndRecall) {
+  PayloadTypePicker picker;
+  PayloadTypeRecorder recorder(picker);
+  const PayloadType a_payload_type(123);
+  const PayloadType not_a_payload_type(44);
+  cricket::Codec a_codec = cricket::CreateVideoCodec(0, "vp8");
+  auto error = recorder.AddMapping(a_payload_type, a_codec);
+  ASSERT_TRUE(error.ok());
+  auto result = recorder.LookupCodec(a_payload_type);
+  ASSERT_TRUE(result.ok());
+  EXPECT_EQ(result.value(), a_codec);
+  auto result_pt = recorder.LookupPayloadType(a_codec);
+  ASSERT_TRUE(result_pt.ok());
+  EXPECT_EQ(result_pt.value(), a_payload_type);
+  EXPECT_FALSE(recorder.LookupCodec(not_a_payload_type).ok());
+}
+
+TEST(PayloadTypePicker, RollbackAndCommit) {
+  PayloadTypePicker picker;
+  PayloadTypeRecorder recorder(picker);
+  const PayloadType a_payload_type(123);
+  const PayloadType b_payload_type(124);
+  const PayloadType not_a_payload_type(44);
+
+  cricket::Codec a_codec = cricket::CreateVideoCodec(0, "vp8");
+
+  cricket::Codec b_codec = cricket::CreateVideoCodec(0, "vp9");
+  auto error = recorder.AddMapping(a_payload_type, a_codec);
+  ASSERT_TRUE(error.ok());
+  recorder.Checkpoint();
+  ASSERT_TRUE(recorder.AddMapping(b_payload_type, b_codec).ok());
+  {
+    auto result = recorder.LookupCodec(a_payload_type);
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value(), a_codec);
+  }
+  {
+    auto result = recorder.LookupCodec(b_payload_type);
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value(), b_codec);
+  }
+  recorder.Rollback();
+  {
+    auto result = recorder.LookupCodec(a_payload_type);
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value(), a_codec);
+  }
+  {
+    auto result = recorder.LookupCodec(b_payload_type);
+    ASSERT_FALSE(result.ok());
+  }
+  ASSERT_TRUE(recorder.AddMapping(b_payload_type, b_codec).ok());
+  // Rollback after a new checkpoint has no effect.
+  recorder.Checkpoint();
+  recorder.Rollback();
+  {
+    auto result = recorder.LookupCodec(b_payload_type);
+    ASSERT_TRUE(result.ok());
+    EXPECT_EQ(result.value(), b_codec);
+  }
+}
+
+}  // namespace webrtc