Fix setParameters() throwing when level-id does not match.
In order to align with this PR[1], setParameters() should not throw if
the H265 level ID we're trying to send does not match what was
negotiated. This was believed to be fixed by [2] but we were still
throwing due to a check on a different layer (media_engine.cc).
In order to reproduce the issue despite WebRTC lacking SW
encoder/decoder for H265, peer_connection_encodings_integrationtest.cc
gets a new test with real stack but fake encoder/decoder factory. This
allows negotiating H265 and doing SetParameters() even though the codec
is not processing any frames.
- Basic test coverage is added for singlecast and simulcast H265.
- Test coverage for the bug being fixed added.
- In Chrome the equivalent WPTs exists for when real HW is available
here[3]. Those tests PASS with this CL (currently FAIL).
[1] https://github.com/w3c/webrtc-pc/pull/3023
[2] https://webrtc-review.googlesource.com/c/src/+/368781
[3] https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/webrtc/protocol/h265-level-id.https.html
Bug: chromium:381407888
Change-Id: I3619a124586b8b26d3695cfad8890cf40bd475db
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/374164
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Jianlin Qiu <jianlin.qiu@intel.com>
Cr-Commit-Position: refs/heads/main@{#43759}
diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc
index 0ce26ff..eb74466 100644
--- a/media/base/media_engine.cc
+++ b/media/base/media_engine.cc
@@ -96,7 +96,8 @@
if (rtp_parameters.encodings[i].codec) {
bool codecFound = false;
for (const cricket::Codec& codec : send_codecs) {
- if (IsSameRtpCodec(codec, *rtp_parameters.encodings[i].codec) &&
+ if (IsSameRtpCodecIgnoringLevel(codec,
+ *rtp_parameters.encodings[i].codec) &&
SupportsMode(codec, rtp_parameters.encodings[i].scalability_mode)) {
codecFound = true;
send_codec = codec;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 560fb4c..ac0042e 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -1432,7 +1432,8 @@
// the first layer.
// TODO: https://issues.webrtc.org/362277533 - Support mixed-codec simulcast
if (parameters.encodings[0].codec && send_codec_ &&
- !IsSameRtpCodec(send_codec_->codec, *parameters.encodings[0].codec)) {
+ !IsSameRtpCodecIgnoringLevel(send_codec_->codec,
+ *parameters.encodings[0].codec)) {
RTC_LOG(LS_VERBOSE) << "Trying to change codec to "
<< parameters.encodings[0].codec->name;
// Ignore level when matching negotiated codecs against the requested
diff --git a/pc/peer_connection_encodings_integrationtest.cc b/pc/peer_connection_encodings_integrationtest.cc
index f2fb6d8..1b6eeaf 100644
--- a/pc/peer_connection_encodings_integrationtest.cc
+++ b/pc/peer_connection_encodings_integrationtest.cc
@@ -41,6 +41,7 @@
#include "api/test/rtc_error_matchers.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
+#include "media/engine/fake_webrtc_video_engine.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
@@ -2994,4 +2995,169 @@
"AV1"),
StringParamToString());
+#ifdef RTC_ENABLE_H265
+// These tests use fake encoders and decoders, allowing testing of codec
+// preferences, SDP negotiation and get/setParamaters(). But because the codecs
+// implementations are fake, these tests do not encode or decode any frames.
+class PeerConnectionEncodingsFakeCodecsIntegrationTest
+ : public PeerConnectionEncodingsIntegrationTest {
+ public:
+ scoped_refptr<PeerConnectionTestWrapper> CreatePcWithFakeH265(
+ std::unique_ptr<FieldTrialsView> field_trials = nullptr) {
+ std::unique_ptr<cricket::FakeWebRtcVideoEncoderFactory>
+ video_encoder_factory =
+ std::make_unique<cricket::FakeWebRtcVideoEncoderFactory>();
+ video_encoder_factory->AddSupportedVideoCodec(
+ SdpVideoFormat("H265",
+ {{"profile-id", "1"},
+ {"tier-flag", "0"},
+ {"level-id", "156"},
+ {"tx-mode", "SRST"}},
+ {ScalabilityMode::kL1T1}));
+ std::unique_ptr<cricket::FakeWebRtcVideoDecoderFactory>
+ video_decoder_factory =
+ std::make_unique<cricket::FakeWebRtcVideoDecoderFactory>();
+ video_decoder_factory->AddSupportedVideoCodecType("H265");
+ auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>(
+ "pc", &pss_, background_thread_.get(), background_thread_.get());
+ pc_wrapper->CreatePc(
+ {}, CreateBuiltinAudioEncoderFactory(),
+ CreateBuiltinAudioDecoderFactory(), std::move(video_encoder_factory),
+ std::move(video_decoder_factory), std::move(field_trials));
+ return pc_wrapper;
+ }
+};
+
+TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265Singlecast) {
+ rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper =
+ CreatePcWithFakeH265();
+ rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper =
+ CreatePcWithFakeH265();
+ ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+ rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
+ local_pc_wrapper->pc()
+ ->AddTransceiver(cricket::MEDIA_TYPE_VIDEO)
+ .MoveValue();
+ std::vector<RtpCodecCapability> preferred_codecs =
+ GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265");
+ transceiver->SetCodecPreferences(preferred_codecs);
+
+ Negotiate(local_pc_wrapper, remote_pc_wrapper);
+ local_pc_wrapper->WaitForConnection();
+ remote_pc_wrapper->WaitForConnection();
+
+ // Verify codec.
+ rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+ std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+ report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+ ASSERT_THAT(outbound_rtps, SizeIs(1u));
+ EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
+ StrCaseEq("video/H265"));
+}
+
+TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265Simulcast) {
+ rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper =
+ CreatePcWithFakeH265();
+ rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper =
+ CreatePcWithFakeH265();
+ ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+ std::vector<cricket::SimulcastLayer> layers =
+ CreateLayers({"q", "h", "f"}, /*active=*/true);
+
+ rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
+ AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
+ layers);
+ std::vector<RtpCodecCapability> preferred_codecs =
+ GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265");
+ transceiver->SetCodecPreferences(preferred_codecs);
+
+ NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+ local_pc_wrapper->WaitForConnection();
+ remote_pc_wrapper->WaitForConnection();
+
+ // Wait until all outbound RTPs exist.
+ EXPECT_THAT(
+ GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre(
+ AllOf(RidIs("q")), AllOf(RidIs("h")),
+ AllOf(RidIs("f"))))),
+ IsRtcOk());
+
+ // Verify codec.
+ rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+ std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+ report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+ ASSERT_THAT(outbound_rtps, SizeIs(3u));
+ EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
+ StrCaseEq("video/H265"));
+ EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]),
+ StrCaseEq("video/H265"));
+ EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]),
+ StrCaseEq("video/H265"));
+}
+
+TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest,
+ H265SetParametersIgnoresLevelId) {
+ rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper =
+ CreatePcWithFakeH265();
+ rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper =
+ CreatePcWithFakeH265();
+ ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+ std::vector<cricket::SimulcastLayer> layers =
+ CreateLayers({"f"}, /*active=*/true);
+
+ rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
+ AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
+ layers);
+ std::vector<RtpCodecCapability> preferred_codecs =
+ GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265");
+ transceiver->SetCodecPreferences(preferred_codecs);
+ rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
+
+ NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+ local_pc_wrapper->WaitForConnection();
+ remote_pc_wrapper->WaitForConnection();
+
+ // This includes non-codecs like rtx, red and flexfec too so we need to find
+ // H265.
+ std::vector<RtpCodecCapability> sender_codecs =
+ local_pc_wrapper->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+ auto it = std::find_if(sender_codecs.begin(), sender_codecs.end(),
+ [](const RtpCodecCapability codec_capability) {
+ return codec_capability.name == "H265";
+ });
+ ASSERT_NE(it, sender_codecs.end());
+ RtpCodecCapability& h265_codec = *it;
+
+ // SetParameters() without changing level-id.
+ EXPECT_EQ(h265_codec.parameters["level-id"], "156");
+ {
+ RtpParameters parameters = sender->GetParameters();
+ ASSERT_THAT(parameters.encodings, SizeIs(1));
+ parameters.encodings[0].codec = h265_codec;
+ ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk());
+ }
+ // SetParameters() with a lower level-id.
+ h265_codec.parameters["level-id"] = "30";
+ {
+ RtpParameters parameters = sender->GetParameters();
+ ASSERT_THAT(parameters.encodings, SizeIs(1));
+ parameters.encodings[0].codec = h265_codec;
+ ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk());
+ }
+ // SetParameters() with a higher level-id.
+ h265_codec.parameters["level-id"] = "180";
+ {
+ RtpParameters parameters = sender->GetParameters();
+ ASSERT_THAT(parameters.encodings, SizeIs(1));
+ parameters.encodings[0].codec = h265_codec;
+ ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk());
+ }
+}
+#endif // RTC_ENABLE_H265
+
} // namespace webrtc
diff --git a/pc/test/peer_connection_test_wrapper.cc b/pc/test/peer_connection_test_wrapper.cc
index 1774f1b..e458794 100644
--- a/pc/test/peer_connection_test_wrapper.cc
+++ b/pc/test/peer_connection_test_wrapper.cc
@@ -172,6 +172,8 @@
const webrtc::PeerConnectionInterface::RTCConfiguration& config,
rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory,
+ std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory,
+ std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory,
std::unique_ptr<webrtc::FieldTrialsView> field_trials) {
std::unique_ptr<cricket::PortAllocator> port_allocator(
new cricket::FakePortAllocator(
@@ -190,12 +192,7 @@
network_thread_, worker_thread_, rtc::Thread::Current(),
rtc::scoped_refptr<webrtc::AudioDeviceModule>(fake_audio_capture_module_),
audio_encoder_factory, audio_decoder_factory,
- std::make_unique<FuzzyMatchedVideoEncoderFactory>(),
- std::make_unique<webrtc::VideoDecoderFactoryTemplate<
- webrtc::LibvpxVp8DecoderTemplateAdapter,
- webrtc::LibvpxVp9DecoderTemplateAdapter,
- webrtc::OpenH264DecoderTemplateAdapter,
- webrtc::Dav1dDecoderTemplateAdapter>>(),
+ std::move(video_encoder_factory), std::move(video_decoder_factory),
nullptr /* audio_mixer */, nullptr /* audio_processing */, nullptr,
std::move(field_trials));
if (!peer_connection_factory_) {
@@ -217,6 +214,22 @@
}
}
+bool PeerConnectionTestWrapper::CreatePc(
+ const webrtc::PeerConnectionInterface::RTCConfiguration& config,
+ rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
+ rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory,
+ std::unique_ptr<webrtc::FieldTrialsView> field_trials) {
+ return CreatePc(config, std::move(audio_encoder_factory),
+ std::move(audio_decoder_factory),
+ std::make_unique<FuzzyMatchedVideoEncoderFactory>(),
+ std::make_unique<webrtc::VideoDecoderFactoryTemplate<
+ webrtc::LibvpxVp8DecoderTemplateAdapter,
+ webrtc::LibvpxVp9DecoderTemplateAdapter,
+ webrtc::OpenH264DecoderTemplateAdapter,
+ webrtc::Dav1dDecoderTemplateAdapter>>(),
+ std::move(field_trials));
+}
+
rtc::scoped_refptr<webrtc::DataChannelInterface>
PeerConnectionTestWrapper::CreateDataChannel(
const std::string& label,
diff --git a/pc/test/peer_connection_test_wrapper.h b/pc/test/peer_connection_test_wrapper.h
index 0a3a689..5633ea8 100644
--- a/pc/test/peer_connection_test_wrapper.h
+++ b/pc/test/peer_connection_test_wrapper.h
@@ -29,6 +29,8 @@
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/video/resolution.h"
+#include "api/video_codecs/video_decoder_factory.h"
+#include "api/video_codecs/video_encoder_factory.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/fake_periodic_video_source.h"
#include "pc/test/fake_periodic_video_track_source.h"
@@ -55,6 +57,13 @@
rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory,
std::unique_ptr<webrtc::FieldTrialsView> field_trials = nullptr);
+ bool CreatePc(
+ const webrtc::PeerConnectionInterface::RTCConfiguration& config,
+ rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory,
+ rtc::scoped_refptr<webrtc::AudioDecoderFactory> audio_decoder_factory,
+ std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory,
+ std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory,
+ std::unique_ptr<webrtc::FieldTrialsView> field_trials = nullptr);
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory()
const {