Add IsSameRtpCodec method to Codec.

This is similar to MatchesRtpCodec but not an exact match of parameters, unspecified parameters are treated as default. Use IsSameRtpCodec for comparison when codec is configured via encodings.

Bug: b:299588022
Change-Id: I0ea800e50af6f5666e3e867a928e15b0aa044635
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/365142
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43272}
This commit is contained in:
Åsa Persson 2024-10-18 13:50:27 +00:00 committed by WebRTC LUCI CQ
parent 6d815bdd9b
commit 929c02a479
6 changed files with 265 additions and 7 deletions

View File

@ -49,6 +49,10 @@ std::string GetFmtpParameterOrDefault(const CodecParameterMap& params,
return default_value;
}
bool HasParameter(const CodecParameterMap& params, const std::string& name) {
return params.find(name) != params.end();
}
std::string H264GetPacketizationModeOrDefault(const CodecParameterMap& params) {
// If packetization-mode is not present, default to "0".
// https://tools.ietf.org/html/rfc6184#section-6.2
@ -215,6 +219,66 @@ bool MatchesWithReferenceAttributesAndComparator(
return true; // Not a codec with a PT-valued reference.
}
CodecParameterMap InsertDefaultParams(const std::string& name,
const CodecParameterMap& params) {
CodecParameterMap updated_params = params;
if (absl::EqualsIgnoreCase(name, cricket::kVp9CodecName)) {
if (!HasParameter(params, kVP9FmtpProfileId)) {
if (std::optional<VP9Profile> default_profile =
ParseSdpForVP9Profile({})) {
updated_params.insert(
{kVP9FmtpProfileId, VP9ProfileToString(*default_profile)});
}
}
}
if (absl::EqualsIgnoreCase(name, cricket::kAv1CodecName)) {
if (!HasParameter(params, cricket::kAv1FmtpProfile)) {
if (std::optional<AV1Profile> default_profile =
ParseSdpForAV1Profile({})) {
updated_params.insert({cricket::kAv1FmtpProfile,
AV1ProfileToString(*default_profile).data()});
}
}
if (!HasParameter(params, cricket::kAv1FmtpTier)) {
updated_params.insert({cricket::kAv1FmtpTier, AV1GetTierOrDefault({})});
}
if (!HasParameter(params, cricket::kAv1FmtpLevelIdx)) {
updated_params.insert(
{cricket::kAv1FmtpLevelIdx, AV1GetLevelIdxOrDefault({})});
}
}
if (absl::EqualsIgnoreCase(name, cricket::kH264CodecName)) {
if (!HasParameter(params, cricket::kH264FmtpPacketizationMode)) {
updated_params.insert({cricket::kH264FmtpPacketizationMode,
H264GetPacketizationModeOrDefault({})});
}
}
#ifdef RTC_ENABLE_H265
if (absl::EqualsIgnoreCase(name, cricket::kH265CodecName)) {
if (std::optional<H265ProfileTierLevel> default_params =
ParseSdpForH265ProfileTierLevel({})) {
if (!HasParameter(params, cricket::kH265FmtpProfileId)) {
updated_params.insert({cricket::kH265FmtpProfileId,
H265ProfileToString(default_params->profile)});
}
if (!HasParameter(params, cricket::kH265FmtpLevelId)) {
updated_params.insert({cricket::kH265FmtpLevelId,
H265LevelToString(default_params->level)});
}
if (!HasParameter(params, cricket::kH265FmtpTierFlag)) {
updated_params.insert({cricket::kH265FmtpTierFlag,
H265TierToString(default_params->tier)});
}
}
if (!HasParameter(params, cricket::kH265FmtpTxMode)) {
updated_params.insert(
{cricket::kH265FmtpTxMode, GetH265TxModeOrDefault({})});
}
}
#endif
return updated_params;
}
} // namespace
bool MatchesWithCodecRules(const Codec& left_codec, const Codec& right_codec) {
@ -307,4 +371,15 @@ std::optional<Codec> FindMatchingCodec(const std::vector<Codec>& codecs1,
return std::nullopt;
}
bool IsSameRtpCodec(const Codec& codec, const RtpCodec& rtp_codec) {
RtpCodecParameters rtp_codec2 = codec.ToCodecParameters();
return absl::EqualsIgnoreCase(rtp_codec.name, rtp_codec2.name) &&
rtp_codec.kind == rtp_codec2.kind &&
rtp_codec.num_channels == rtp_codec2.num_channels &&
rtp_codec.clock_rate == rtp_codec2.clock_rate &&
InsertDefaultParams(rtp_codec.name, rtp_codec.parameters) ==
InsertDefaultParams(rtp_codec2.name, rtp_codec2.parameters);
}
} // namespace webrtc

View File

@ -14,6 +14,7 @@
#include <optional>
#include <vector>
#include "api/rtp_parameters.h"
#include "media/base/codec.h"
namespace webrtc {
@ -39,6 +40,10 @@ std::optional<cricket::Codec> FindMatchingCodec(
const std::vector<cricket::Codec>& codecs2,
const cricket::Codec& codec_to_match);
// Similar to `Codec::MatchesRtpCodec` but not an exact match of parameters.
// Unspecified parameters are treated as default.
bool IsSameRtpCodec(const cricket::Codec& codec, const RtpCodec& rtp_codec);
} // namespace webrtc
#endif // MEDIA_BASE_CODEC_COMPARATORS_H_

View File

@ -9,9 +9,14 @@
*/
#include "media/base/codec_comparators.h"
#include <string>
#include "api/audio_codecs/audio_format.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/vp9_profile.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
@ -21,6 +26,8 @@ using cricket::CreateAudioCodec;
using cricket::CreateVideoCodec;
using cricket::kH264CodecName;
using cricket::kH264FmtpPacketizationMode;
using ::testing::TestWithParam;
using ::testing::ValuesIn;
TEST(CodecComparatorsTest, CodecMatchesItself) {
Codec codec = cricket::CreateVideoCodec("custom");
@ -72,4 +79,129 @@ TEST(CodecComparatorsTest, StaticPayloadTypesIgnoreName) {
EXPECT_TRUE(MatchesWithCodecRules(codec_1, codec_2));
}
struct TestParams {
std::string name;
SdpVideoFormat codec1;
SdpVideoFormat codec2;
bool expected_result;
};
using IsSameRtpCodecTest = TestWithParam<TestParams>;
TEST_P(IsSameRtpCodecTest, IsSameRtpCodec) {
TestParams param = GetParam();
Codec codec1 = cricket::CreateVideoCodec(param.codec1);
Codec codec2 = cricket::CreateVideoCodec(param.codec2);
EXPECT_EQ(IsSameRtpCodec(codec1, codec2.ToCodecParameters()),
param.expected_result);
}
INSTANTIATE_TEST_SUITE_P(
CodecTest,
IsSameRtpCodecTest,
ValuesIn<TestParams>({
{.name = "CodecWithDifferentName",
.codec1 = {"VP9", {}},
.codec2 = {"VP8", {}},
.expected_result = false},
{.name = "Vp8WithoutParameters",
.codec1 = {"vp8", {}},
.codec2 = {"VP8", {}},
.expected_result = true},
{.name = "Vp8WithSameParameters",
.codec1 = {"VP8", {{"x", "1"}}},
.codec2 = {"VP8", {{"x", "1"}}},
.expected_result = true},
{.name = "Vp8WithDifferentParameters",
.codec1 = {"VP8", {}},
.codec2 = {"VP8", {{"x", "1"}}},
.expected_result = false},
{.name = "Av1WithoutParameters",
.codec1 = {"AV1", {}},
.codec2 = {"AV1", {}},
.expected_result = true},
{.name = "Av1WithSameProfile",
.codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
.codec2 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
.expected_result = true},
{.name = "Av1WithoutParametersTreatedAsProfile0",
.codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
.codec2 = {"AV1", {}},
.expected_result = true},
{.name = "Av1WithoutProfileTreatedAsProfile0",
.codec1 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "1"}}},
.codec2 = {"AV1", {{"x", "1"}}},
.expected_result = true},
{.name = "Av1WithDifferentProfile",
.codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
.codec2 = {"AV1", SdpVideoFormat::AV1Profile1().parameters},
.expected_result = false},
{.name = "Av1WithDifferentParameters",
.codec1 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "1"}}},
.codec2 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "2"}}},
.expected_result = false},
{.name = "Vp9WithSameProfile",
.codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
.codec2 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
.expected_result = true},
{.name = "Vp9WithoutProfileTreatedAsProfile0",
.codec1 = {"VP9", {{kVP9FmtpProfileId, "0"}, {"x", "1"}}},
.codec2 = {"VP9", {{"x", "1"}}},
.expected_result = true},
{.name = "Vp9WithDifferentProfile",
.codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
.codec2 = {"VP9", SdpVideoFormat::VP9Profile1().parameters},
.expected_result = false},
{.name = "H264WithSamePacketizationMode",
.codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
.codec2 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
.expected_result = true},
{.name = "H264WithoutPacketizationModeTreatedAsMode0",
.codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}, {"x", "1"}}},
.codec2 = {"H264", {{"x", "1"}}},
.expected_result = true},
{.name = "H264WithDifferentPacketizationMode",
.codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
.codec2 = {"H264", {{kH264FmtpPacketizationMode, "1"}}},
.expected_result = false},
#ifdef RTC_ENABLE_H265
{.name = "H265WithSameProfile",
.codec1 = {"H265",
{{cricket::kH265FmtpProfileId, "1"},
{cricket::kH265FmtpTierFlag, "0"},
{cricket::kH265FmtpLevelId, "93"},
{cricket::kH265FmtpTxMode, "SRST"}}},
.codec2 = {"H265",
{{cricket::kH265FmtpProfileId, "1"},
{cricket::kH265FmtpTierFlag, "0"},
{cricket::kH265FmtpLevelId, "93"},
{cricket::kH265FmtpTxMode, "SRST"}}},
.expected_result = true},
{.name = "H265WithoutParametersTreatedAsDefault",
.codec1 = {"H265",
{{cricket::kH265FmtpProfileId, "1"},
{cricket::kH265FmtpTierFlag, "0"},
{cricket::kH265FmtpLevelId, "93"},
{cricket::kH265FmtpTxMode, "SRST"}}},
.codec2 = {"H265", {}},
.expected_result = true},
{.name = "H265WithDifferentProfile",
.codec1 = {"H265",
{{cricket::kH265FmtpProfileId, "1"},
{cricket::kH265FmtpTierFlag, "0"},
{cricket::kH265FmtpLevelId, "93"},
{cricket::kH265FmtpTxMode, "SRST"}}},
.codec2 = {"H265",
{{cricket::kH265FmtpProfileId, "1"},
{cricket::kH265FmtpTierFlag, "1"},
{cricket::kH265FmtpLevelId, "93"},
{cricket::kH265FmtpTxMode, "SRST"}}},
.expected_result = false},
#endif
}),
[](const testing::TestParamInfo<IsSameRtpCodecTest::ParamType>& info) {
return info.param.name;
});
} // namespace webrtc

View File

@ -19,10 +19,24 @@
#include "absl/algorithm/container.h"
#include "api/field_trials_view.h"
#include "api/video/video_bitrate_allocation.h"
#include "media/base/codec_comparators.h"
#include "rtc_base/checks.h"
#include "rtc_base/string_encode.h"
namespace cricket {
namespace {
bool SupportsMode(const cricket::Codec& codec,
std::optional<std::string> scalability_mode) {
if (!scalability_mode.has_value()) {
return true;
}
return absl::c_any_of(
codec.scalability_modes, [&](webrtc::ScalabilityMode mode) {
return ScalabilityModeToString(mode) == *scalability_mode;
});
}
} // namespace
RtpCapabilities::RtpCapabilities() = default;
RtpCapabilities::~RtpCapabilities() = default;
@ -82,7 +96,8 @@ webrtc::RTCError CheckScalabilityModeValues(
if (rtp_parameters.encodings[i].codec) {
bool codecFound = false;
for (const cricket::Codec& codec : send_codecs) {
if (codec.MatchesRtpCodec(*rtp_parameters.encodings[i].codec)) {
if (IsSameRtpCodec(codec, *rtp_parameters.encodings[i].codec) &&
SupportsMode(codec, rtp_parameters.encodings[i].scalability_mode)) {
codecFound = true;
send_codec = codec;
break;

View File

@ -70,6 +70,7 @@
#include "call/video_send_stream.h"
#include "common_video/frame_counts.h"
#include "media/base/codec.h"
#include "media/base/codec_comparators.h"
#include "media/base/media_channel.h"
#include "media/base/media_channel_impl.h"
#include "media/base/media_config.h"
@ -1115,8 +1116,8 @@ bool WebRtcVideoSendChannel::GetChangedSenderParameters(
if (rtp_parameters.encodings[0].codec) {
auto matched_codec =
absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) {
return negotiated_codec.codec.MatchesRtpCodec(
*rtp_parameters.encodings[0].codec);
return IsSameRtpCodec(negotiated_codec.codec,
*rtp_parameters.encodings[0].codec);
});
if (matched_codec != negotiated_codecs.end()) {
force_codec = *matched_codec;
@ -1152,7 +1153,7 @@ bool WebRtcVideoSendChannel::GetChangedSenderParameters(
if (encoding.codec) {
auto matched_codec =
absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) {
return negotiated_codec.codec.MatchesRtpCodec(*encoding.codec);
return IsSameRtpCodec(negotiated_codec.codec, *encoding.codec);
});
if (matched_codec != negotiated_codecs.end()) {
send_codecs.push_back(*matched_codec);
@ -1446,13 +1447,13 @@ webrtc::RTCError WebRtcVideoSendChannel::SetRtpSendParameters(
// the first layer.
// TODO(orphis): Support mixed-codec simulcast
if (parameters.encodings[0].codec && send_codec_ &&
!send_codec_->codec.MatchesRtpCodec(*parameters.encodings[0].codec)) {
!IsSameRtpCodec(send_codec_->codec, *parameters.encodings[0].codec)) {
RTC_LOG(LS_VERBOSE) << "Trying to change codec to "
<< parameters.encodings[0].codec->name;
auto matched_codec =
absl::c_find_if(negotiated_codecs_, [&](auto negotiated_codec) {
return negotiated_codec.codec.MatchesRtpCodec(
*parameters.encodings[0].codec);
return IsSameRtpCodec(negotiated_codec.codec,
*parameters.encodings[0].codec);
});
if (matched_codec == negotiated_codecs_.end()) {
return webrtc::InvokeSetParametersCallback(

View File

@ -1786,6 +1786,36 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, CheckForInvalidEncodingParameters) {
.error()
.type());
init.send_encodings = default_send_encodings;
init.send_encodings[0].scalability_mode = std::nullopt;
init.send_encodings[0].codec =
cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters();
EXPECT_EQ(RTCErrorType::NONE,
caller->pc()
->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init)
.error()
.type());
init.send_encodings = default_send_encodings;
init.send_encodings[0].scalability_mode = "L1T2";
init.send_encodings[0].codec =
cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters();
EXPECT_EQ(RTCErrorType::NONE,
caller->pc()
->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init)
.error()
.type());
init.send_encodings = default_send_encodings;
init.send_encodings[0].scalability_mode = "L2T2";
init.send_encodings[0].codec =
cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters();
EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION,
caller->pc()
->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init)
.error()
.type());
init.send_encodings = default_send_encodings;
}
// Test that AddTransceiver transfers the send_encodings to the sender and they