Add first iteration of PayloadTypePicker.SuggestPayloadType

Bug: webrtc:360058654
Change-Id: I8f9242a97dc871a39ae72f325b8ca039b2285bae
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/360061
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42828}
This commit is contained in:
Harald Alvestrand 2024-08-21 09:49:09 +00:00 committed by WebRTC LUCI CQ
parent abb638899e
commit e2869de9ef
3 changed files with 189 additions and 9 deletions

View File

@ -22,6 +22,14 @@ namespace webrtc {
namespace { namespace {
// Due to interoperability issues with old Chrome/WebRTC versions that
// ignore the [35, 63] range prefer the lower range for new codecs.
static const int kFirstDynamicPayloadTypeLowerRange = 35;
static const int kLastDynamicPayloadTypeLowerRange = 63;
static const int kFirstDynamicPayloadTypeUpperRange = 96;
static const int kLastDynamicPayloadTypeUpperRange = 127;
// Note: The only fields we need from a Codec are the type (audio/video), // 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 // the subtype (vp8/h264/....), the clock rate, the channel count, and the
// fmtp parameters. The use of cricket::Codec, which contains more fields, // fmtp parameters. The use of cricket::Codec, which contains more fields,
@ -35,16 +43,127 @@ bool MatchesForSdp(const cricket::Codec& codec_1,
codec_1.params == codec_2.params; codec_1.params == codec_2.params;
} }
struct MapTableEntry {
webrtc::SdpAudioFormat format;
int payload_type;
};
RTCErrorOr<PayloadType> FindFreePayloadType(std::set<PayloadType> seen_pt) {
for (auto i = kFirstDynamicPayloadTypeUpperRange;
i < kLastDynamicPayloadTypeUpperRange; i++) {
if (seen_pt.count(PayloadType(i)) == 0) {
return PayloadType(i);
}
}
for (auto i = kFirstDynamicPayloadTypeLowerRange;
i < kLastDynamicPayloadTypeLowerRange; i++) {
if (seen_pt.count(PayloadType(i)) == 0) {
return PayloadType(i);
}
}
return RTCError(RTCErrorType::RESOURCE_EXHAUSTED,
"All available dynamic PTs have been assigned");
}
} // namespace } // namespace
PayloadTypePicker::PayloadTypePicker() {
// Default audio codecs. Duplicates media/engine/payload_type_mapper.cc
const MapTableEntry default_audio_mappings[] = {
// Static payload type assignments according to RFC 3551.
{{cricket::kPcmuCodecName, 8000, 1}, 0},
{{"GSM", 8000, 1}, 3},
{{"G723", 8000, 1}, 4},
{{"DVI4", 8000, 1}, 5},
{{"DVI4", 16000, 1}, 6},
{{"LPC", 8000, 1}, 7},
{{cricket::kPcmaCodecName, 8000, 1}, 8},
{{cricket::kG722CodecName, 8000, 1}, 9},
{{cricket::kL16CodecName, 44100, 2}, 10},
{{cricket::kL16CodecName, 44100, 1}, 11},
{{"QCELP", 8000, 1}, 12},
{{cricket::kCnCodecName, 8000, 1}, 13},
// RFC 4566 is a bit ambiguous on the contents of the "encoding
// parameters" field, which, for audio, encodes the number of
// channels. It is "optional and may be omitted if the number of
// channels is one". Does that necessarily imply that an omitted
// encoding parameter means one channel? Since RFC 3551 doesn't
// specify a value for this parameter for MPA, I've included both 0
// and 1 here, to increase the chances it will be correctly used if
// someone implements an MPEG audio encoder/decoder.
{{"MPA", 90000, 0}, 14},
{{"MPA", 90000, 1}, 14},
{{"G728", 8000, 1}, 15},
{{"DVI4", 11025, 1}, 16},
{{"DVI4", 22050, 1}, 17},
{{"G729", 8000, 1}, 18},
// Payload type assignments currently used by WebRTC.
// Includes data to reduce collisions (and thus reassignments)
{{cricket::kIlbcCodecName, 8000, 1}, 102},
{{cricket::kCnCodecName, 16000, 1}, 105},
{{cricket::kCnCodecName, 32000, 1}, 106},
{{cricket::kOpusCodecName,
48000,
2,
{{cricket::kCodecParamMinPTime, "10"},
{cricket::kCodecParamUseInbandFec, cricket::kParamValueTrue}}},
111},
// RED for opus is assigned in the lower range, starting at the top.
// Note that the FMTP refers to the opus payload type.
{{cricket::kRedCodecName,
48000,
2,
{{cricket::kCodecParamNotInNameValueFormat, "111/111"}}},
63},
// TODO(solenberg): Remove the hard coded 16k,32k,48k DTMF once we
// assign payload types dynamically for send side as well.
{{cricket::kDtmfCodecName, 48000, 1}, 110},
{{cricket::kDtmfCodecName, 32000, 1}, 112},
{{cricket::kDtmfCodecName, 16000, 1}, 113},
{{cricket::kDtmfCodecName, 8000, 1}, 126}};
for (auto entry : default_audio_mappings) {
AddMapping(PayloadType(entry.payload_type),
cricket::CreateAudioCodec(entry.format));
}
}
RTCErrorOr<PayloadType> PayloadTypePicker::SuggestMapping( RTCErrorOr<PayloadType> PayloadTypePicker::SuggestMapping(
cricket::Codec codec) { cricket::Codec codec,
return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented yet"); PayloadTypeRecorder* excluder) {
// The first matching entry is returned, unless excluder
// maps it to something different.
for (auto entry : entries_) {
if (MatchesForSdp(entry.codec(), codec)) {
if (excluder) {
auto result = excluder->LookupCodec(entry.payload_type());
if (result.ok() && !MatchesForSdp(result.value(), codec)) {
continue;
}
}
return entry.payload_type();
}
}
RTCErrorOr<PayloadType> found_pt = FindFreePayloadType(seen_payload_types_);
if (found_pt.ok()) {
AddMapping(found_pt.value(), codec);
}
return found_pt;
} }
RTCError PayloadTypePicker::AddMapping(PayloadType payload_type, RTCError PayloadTypePicker::AddMapping(PayloadType payload_type,
cricket::Codec codec) { cricket::Codec codec) {
return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented yet"); // Completely duplicate mappings are ignored.
// Multiple mappings for the same codec and the same PT are legal;
for (auto entry : entries_) {
if (payload_type == entry.payload_type() &&
MatchesForSdp(codec, entry.codec())) {
return RTCError::OK();
}
}
entries_.emplace_back(MapEntry(payload_type, codec));
seen_payload_types_.emplace(payload_type);
return RTCError::OK();
} }
RTCError PayloadTypeRecorder::AddMapping(PayloadType payload_type, RTCError PayloadTypeRecorder::AddMapping(PayloadType payload_type,
@ -52,9 +171,6 @@ RTCError PayloadTypeRecorder::AddMapping(PayloadType payload_type,
auto existing_codec_it = payload_type_to_codec_.find(payload_type); auto existing_codec_it = payload_type_to_codec_.find(payload_type);
if (existing_codec_it != payload_type_to_codec_.end() && if (existing_codec_it != payload_type_to_codec_.end() &&
!MatchesForSdp(codec, existing_codec_it->second)) { !MatchesForSdp(codec, existing_codec_it->second)) {
RTC_LOG(LS_ERROR) << "DEBUG: AddMapping invalid, PT " << int(payload_type)
<< " new codec " << codec.ToString() << " old codec "
<< existing_codec_it->second.ToString();
return RTCError(RTCErrorType::INVALID_PARAMETER, return RTCError(RTCErrorType::INVALID_PARAMETER,
"Attempt to insert duplicate mapping for PT"); "Attempt to insert duplicate mapping for PT");
} }

View File

@ -12,6 +12,7 @@
#define PC_PAYLOAD_TYPE_PICKER_H_ #define PC_PAYLOAD_TYPE_PICKER_H_
#include <map> #include <map>
#include <set>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -29,16 +30,35 @@ class PayloadType : public StrongAlias<class PayloadTypeTag, uint8_t> {
constexpr operator uint8_t() const& { return value_; } // NOLINT: Explicit constexpr operator uint8_t() const& { return value_; } // NOLINT: Explicit
}; };
class PayloadTypeRecorder;
class PayloadTypePicker { class PayloadTypePicker {
public: public:
PayloadTypePicker() = default; PayloadTypePicker();
PayloadTypePicker(const PayloadTypePicker&) = delete; PayloadTypePicker(const PayloadTypePicker&) = delete;
PayloadTypePicker& operator=(const PayloadTypePicker&) = delete; PayloadTypePicker& operator=(const PayloadTypePicker&) = delete;
PayloadTypePicker(PayloadTypePicker&&) = delete; PayloadTypePicker(PayloadTypePicker&&) = delete;
PayloadTypePicker& operator=(PayloadTypePicker&&) = delete; PayloadTypePicker& operator=(PayloadTypePicker&&) = delete;
// Suggest a payload type for the codec.
RTCErrorOr<PayloadType> SuggestMapping(cricket::Codec codec); // If the excluder maps it to something different, don't suggest it.
RTCErrorOr<PayloadType> SuggestMapping(cricket::Codec codec,
PayloadTypeRecorder* excluder);
RTCError AddMapping(PayloadType payload_type, cricket::Codec codec); RTCError AddMapping(PayloadType payload_type, cricket::Codec codec);
private:
class MapEntry {
public:
MapEntry(PayloadType payload_type, cricket::Codec codec)
: payload_type_(payload_type), codec_(codec) {}
PayloadType payload_type() const { return payload_type_; }
cricket::Codec codec() const { return codec_; }
private:
PayloadType payload_type_;
cricket::Codec codec_;
};
std::vector<MapEntry> entries_;
std::set<PayloadType> seen_payload_types_;
}; };
class PayloadTypeRecorder { class PayloadTypeRecorder {

View File

@ -91,4 +91,48 @@ TEST(PayloadTypePicker, RollbackAndCommit) {
} }
} }
TEST(PayloadTypePicker, StaticValueIsGood) {
PayloadTypePicker picker;
cricket::Codec a_codec =
cricket::CreateAudioCodec(-1, cricket::kPcmuCodecName, 8000, 1);
auto result = picker.SuggestMapping(a_codec, nullptr);
// In the absence of existing mappings, PCMU always has 0 as PT.
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.value(), PayloadType(0));
}
TEST(PayloadTypePicker, DynamicValueIsGood) {
PayloadTypePicker picker;
cricket::Codec a_codec = cricket::CreateAudioCodec(-1, "lyra", 8000, 1);
auto result = picker.SuggestMapping(a_codec, nullptr);
// This should result in a value from the dynamic range; since this is the
// first assignment, it should be in the upper range.
ASSERT_TRUE(result.ok());
EXPECT_GE(result.value(), PayloadType(96));
EXPECT_LE(result.value(), PayloadType(127));
}
TEST(PayloadTypePicker, RecordedValueReturned) {
PayloadTypePicker picker;
PayloadTypeRecorder recorder(picker);
cricket::Codec a_codec = cricket::CreateAudioCodec(-1, "lyra", 8000, 1);
recorder.AddMapping(47, a_codec);
auto result = picker.SuggestMapping(a_codec, &recorder);
ASSERT_TRUE(result.ok());
EXPECT_EQ(47, result.value());
}
TEST(PayloadTypePicker, RecordedValueExcluded) {
PayloadTypePicker picker;
PayloadTypeRecorder recorder1(picker);
PayloadTypeRecorder recorder2(picker);
cricket::Codec a_codec = cricket::CreateAudioCodec(-1, "lyra", 8000, 1);
cricket::Codec b_codec = cricket::CreateAudioCodec(-1, "mlcodec", 8000, 1);
recorder1.AddMapping(47, a_codec);
recorder2.AddMapping(47, b_codec);
auto result = picker.SuggestMapping(b_codec, &recorder1);
ASSERT_TRUE(result.ok());
EXPECT_NE(47, result.value());
}
} // namespace webrtc } // namespace webrtc