webrtc_m130/pc/peer_connection_encodings_integrationtest.cc
Henrik Boström ede69fd577 Make IsSameRtpCodecIgnoringLevel work for any codec.
Prior to this CL, IsSameRtpCodecIgnoringLevel() only ignored level IDs
if the codec was H265, incorrectly considering, for example, different
levels of H264 Baseline as not equal.
- This CL fixes that problem by using IsSameCodecSpecific() which is
  already used in other places, reducing the risk of different
  comparisons using different comparison rules.

This also fixes https://crbug.com/webrtc/391340599 where
setParameters() would throw if unrecognized SDP FMTP parameters were
added to a codec as part of SDP negotiation via SDP munging.

This CL makes the following WPT tests pass:
- external/wpt/webrtc/protocol/h264-unidirectional-codec-offer.https.html
- fast/peerconnection/RTCRtpSender-setParameters.html

Bug: chromium:381407888, webrtc:391340599
Change-Id: I5991403b56c86ba97e670996c6687f6315dde304
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/374043
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43797}
2025-01-24 05:37:17 -08:00

3428 lines
152 KiB
C++

/*
* Copyright 2023 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 <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/audio_options.h"
#include "api/field_trials.h"
#include "api/jsep.h"
#include "api/make_ref_counted.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_sender_interface.h"
#include "api/rtp_transceiver_direction.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/stats/rtc_stats_report.h"
#include "api/stats/rtcstats_objects.h"
#include "api/test/rtc_error_matchers.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "media/base/codec.h"
#include "media/engine/fake_webrtc_video_engine.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "pc/test/peer_connection_test_wrapper.h"
#include "pc/test/simulcast_layer_util.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/logging.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/wait_until.h"
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Contains;
using ::testing::Each;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsSupersetOf;
using ::testing::IsTrue;
using ::testing::Key;
using ::testing::Le;
using ::testing::Matcher;
using ::testing::Ne;
using ::testing::NotNull;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::Pointer;
using ::testing::ResultOf;
using ::testing::SizeIs;
using ::testing::StrCaseEq;
using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
namespace webrtc {
namespace {
// Most tests pass in 20-30 seconds, but some tests take longer such as AV1
// requiring additional ramp-up time (https://crbug.com/webrtc/15006) or SVC
// (LxTx_KEY) being slower than simulcast to send top spatial layer.
// TODO(https://crbug.com/webrtc/15076): Remove need for long ramp-up timeouts
// by using simulated time.
constexpr TimeDelta kLongTimeoutForRampingUp = TimeDelta::Minutes(1);
// The max bitrate 1500 kbps may be subject to change in the future. What we're
// interested in here is that all code paths that result in L1T3 result in the
// same target bitrate which does not exceed this limit.
constexpr DataRate kVp9ExpectedMaxBitrateForL1T3 =
DataRate::KilobitsPerSec(1500);
auto EncoderImplementationIs(absl::string_view impl) {
return Field("encoder_implementation",
&RTCOutboundRtpStreamStats::encoder_implementation,
Optional(StrEq(impl)));
}
template <typename M>
auto ScalabilityModeIs(M matcher) {
return Field("scalability_mode", &RTCOutboundRtpStreamStats::scalability_mode,
matcher);
}
template <typename M>
auto CodecIs(M matcher) {
return Field("codec_id", &RTCOutboundRtpStreamStats::codec_id, matcher);
}
template <typename M>
auto RidIs(M matcher) {
return Field("rid", &RTCOutboundRtpStreamStats::rid, matcher);
}
template <typename WidthMatcher, typename HeightMatcher>
auto ResolutionIs(WidthMatcher width_matcher, HeightMatcher height_matcher) {
return AllOf(Field("frame_width", &RTCOutboundRtpStreamStats::frame_width,
width_matcher),
Field("frame_height", &RTCOutboundRtpStreamStats::frame_height,
height_matcher));
}
template <typename M>
auto HeightIs(M matcher) {
return Field("frame_height", &RTCOutboundRtpStreamStats::frame_height,
matcher);
}
template <typename M>
auto BytesSentIs(M matcher) {
return Field("bytes_sent", &RTCOutboundRtpStreamStats::bytes_sent, matcher);
}
template <typename M>
auto FramesEncodedIs(M matcher) {
return Field("frames_encoded", &RTCOutboundRtpStreamStats::frames_encoded,
matcher);
}
auto Active() {
return Field("active", &RTCOutboundRtpStreamStats::active, true);
}
Matcher<scoped_refptr<const RTCStatsReport>> OutboundRtpStatsAre(
Matcher<std::vector<RTCOutboundRtpStreamStats>> matcher) {
return Pointer(ResultOf(
"outbound_rtp",
[&](const RTCStatsReport* report) {
std::vector<const RTCOutboundRtpStreamStats*> stats =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
// Copy to a new vector.
std::vector<RTCOutboundRtpStreamStats> stats_copy;
stats_copy.reserve(stats.size());
for (const auto* stat : stats) {
stats_copy.emplace_back(*stat);
}
return stats_copy;
},
matcher));
}
auto HasOutboundRtpBytesSent(size_t num_layers, size_t num_active_layers) {
return OutboundRtpStatsAre(AllOf(
SizeIs(num_layers),
testing::Contains(
Field("bytes_sent", &RTCOutboundRtpStreamStats::bytes_sent, Gt(0)))
.Times(num_active_layers)));
}
auto HasOutboundRtpBytesSent(size_t num_layers) {
return HasOutboundRtpBytesSent(num_layers, num_layers);
}
flat_map<std::string, RTCOutboundRtpStreamStats> GetOutboundRtpStreamStatsByRid(
scoped_refptr<const RTCStatsReport> report) {
flat_map<std::string, RTCOutboundRtpStreamStats> result;
auto stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
for (const auto* outbound_rtp : stats) {
result.emplace(
std::make_pair(outbound_rtp->rid.value_or(""), *outbound_rtp));
}
return result;
}
struct StringParamToString {
std::string operator()(const ::testing::TestParamInfo<std::string>& info) {
return info.param;
}
};
std::string GetCurrentCodecMimeType(
rtc::scoped_refptr<const RTCStatsReport> report,
const RTCOutboundRtpStreamStats& outbound_rtp) {
return outbound_rtp.codec_id.has_value()
? *report->GetAs<RTCCodecStats>(*outbound_rtp.codec_id)->mime_type
: "";
}
const RTCOutboundRtpStreamStats* FindOutboundRtpByRid(
const std::vector<const RTCOutboundRtpStreamStats*>& outbound_rtps,
const absl::string_view& rid) {
for (const auto* outbound_rtp : outbound_rtps) {
if (outbound_rtp->rid.has_value() && *outbound_rtp->rid == rid) {
return outbound_rtp;
}
}
return nullptr;
}
} // namespace
class PeerConnectionEncodingsIntegrationTest : public ::testing::Test {
public:
PeerConnectionEncodingsIntegrationTest()
: background_thread_(std::make_unique<rtc::Thread>(&pss_)) {
RTC_CHECK(background_thread_->Start());
}
scoped_refptr<PeerConnectionTestWrapper> CreatePc(
std::unique_ptr<FieldTrialsView> field_trials = nullptr) {
auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>(
"pc", &pss_, background_thread_.get(), background_thread_.get());
pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(),
CreateBuiltinAudioDecoderFactory(),
std::move(field_trials));
return pc_wrapper;
}
rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiverWithSimulcastLayers(
rtc::scoped_refptr<PeerConnectionTestWrapper> local,
rtc::scoped_refptr<PeerConnectionTestWrapper> remote,
std::vector<cricket::SimulcastLayer> init_layers) {
rtc::scoped_refptr<MediaStreamInterface> stream = local->GetUserMedia(
/*audio=*/false, cricket::AudioOptions(), /*video=*/true,
{.width = 1280, .height = 720});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
transceiver_or_error = local->pc()->AddTransceiver(
track, CreateTransceiverInit(init_layers));
EXPECT_TRUE(transceiver_or_error.ok());
return transceiver_or_error.value();
}
bool HasReceiverVideoCodecCapability(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
absl::string_view codec_name) {
std::vector<RtpCodecCapability> codecs =
pc_wrapper->pc_factory()
->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
return std::find_if(codecs.begin(), codecs.end(),
[&codec_name](const RtpCodecCapability& codec) {
return absl::EqualsIgnoreCase(codec.name, codec_name);
}) != codecs.end();
}
std::vector<RtpCodecCapability> GetCapabilitiesAndRestrictToCodec(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
absl::string_view codec_name) {
std::vector<RtpCodecCapability> codecs =
pc_wrapper->pc_factory()
->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
codecs.erase(std::remove_if(codecs.begin(), codecs.end(),
[&codec_name](const RtpCodecCapability& codec) {
return !codec.IsResiliencyCodec() &&
!absl::EqualsIgnoreCase(codec.name,
codec_name);
}),
codecs.end());
RTC_DCHECK(std::find_if(codecs.begin(), codecs.end(),
[&codec_name](const RtpCodecCapability& codec) {
return absl::EqualsIgnoreCase(codec.name,
codec_name);
}) != codecs.end());
return codecs;
}
void ExchangeIceCandidates(
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper,
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) {
local_pc_wrapper->SignalOnIceCandidateReady.connect(
remote_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate);
remote_pc_wrapper->SignalOnIceCandidateReady.connect(
local_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate);
}
// Negotiate without any tweaks (does not work for simulcast loopback).
void Negotiate(
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper,
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) {
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p1 =
SetLocalDescription(local_pc_wrapper, offer.get());
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p2 =
SetRemoteDescription(remote_pc_wrapper, offer.get());
EXPECT_TRUE(Await({p1, p2}));
std::unique_ptr<SessionDescriptionInterface> answer =
CreateAnswer(remote_pc_wrapper);
p1 = SetLocalDescription(remote_pc_wrapper, answer.get());
p2 = SetRemoteDescription(local_pc_wrapper, answer.get());
EXPECT_TRUE(Await({p1, p2}));
}
void NegotiateWithSimulcastTweaks(
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper,
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) {
// Create and set offer for `local_pc_wrapper`.
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p1 =
SetLocalDescription(local_pc_wrapper, offer.get());
// Modify the offer before handoff because `remote_pc_wrapper` only supports
// receiving singlecast.
cricket::SimulcastDescription simulcast_description =
RemoveSimulcast(offer.get());
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p2 =
SetRemoteDescription(remote_pc_wrapper, offer.get());
EXPECT_TRUE(Await({p1, p2}));
// Create and set answer for `remote_pc_wrapper`.
std::unique_ptr<SessionDescriptionInterface> answer =
CreateAnswer(remote_pc_wrapper);
EXPECT_TRUE(answer);
p1 = SetLocalDescription(remote_pc_wrapper, answer.get());
// Modify the answer before handoff because `local_pc_wrapper` should still
// send simulcast.
cricket::MediaContentDescription* mcd_answer =
answer->description()->contents()[0].media_description();
mcd_answer->mutable_streams().clear();
std::vector<cricket::SimulcastLayer> simulcast_layers =
simulcast_description.send_layers().GetAllLayers();
cricket::SimulcastLayerList& receive_layers =
mcd_answer->simulcast_description().receive_layers();
for (const auto& layer : simulcast_layers) {
receive_layers.AddLayer(layer);
}
p2 = SetRemoteDescription(local_pc_wrapper, answer.get());
EXPECT_TRUE(Await({p1, p2}));
}
rtc::scoped_refptr<const RTCStatsReport> GetStats(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) {
auto callback = rtc::make_ref_counted<MockRTCStatsCollectorCallback>();
pc_wrapper->pc()->GetStats(callback.get());
RTC_CHECK(WaitUntil([&]() { return callback->called(); }, testing::IsTrue())
.ok());
return callback->report();
}
[[nodiscard]] RTCErrorOr<scoped_refptr<const RTCStatsReport>> GetStatsUntil(
scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
Matcher<scoped_refptr<const RTCStatsReport>> matcher,
WaitUntilSettings settings = {}) {
return WaitUntil([&]() { return GetStats(pc_wrapper); }, std::move(matcher),
settings);
}
protected:
std::unique_ptr<SessionDescriptionInterface> CreateOffer(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) {
auto observer =
rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
pc_wrapper->pc()->CreateOffer(observer.get(), {});
EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()),
IsRtcOk());
return observer->MoveDescription();
}
std::unique_ptr<SessionDescriptionInterface> CreateAnswer(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) {
auto observer =
rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
pc_wrapper->pc()->CreateAnswer(observer.get(), {});
EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()),
IsRtcOk());
return observer->MoveDescription();
}
rtc::scoped_refptr<MockSetSessionDescriptionObserver> SetLocalDescription(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
SessionDescriptionInterface* sdp) {
auto observer = rtc::make_ref_counted<MockSetSessionDescriptionObserver>();
pc_wrapper->pc()->SetLocalDescription(
observer.get(), CloneSessionDescription(sdp).release());
return observer;
}
rtc::scoped_refptr<MockSetSessionDescriptionObserver> SetRemoteDescription(
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
SessionDescriptionInterface* sdp) {
auto observer = rtc::make_ref_counted<MockSetSessionDescriptionObserver>();
pc_wrapper->pc()->SetRemoteDescription(
observer.get(), CloneSessionDescription(sdp).release());
return observer;
}
// To avoid ICE candidates arriving before the remote endpoint has received
// the offer it is important to SetLocalDescription() and
// SetRemoteDescription() are kicked off without awaiting in-between. This
// helper is used to await multiple observers.
bool Await(std::vector<rtc::scoped_refptr<MockSetSessionDescriptionObserver>>
observers) {
for (auto& observer : observers) {
auto result = WaitUntil([&] { return observer->called(); }, IsTrue());
if (!result.ok() || !observer->result()) {
return false;
}
}
return true;
}
rtc::PhysicalSocketServer pss_;
std::unique_ptr<rtc::Thread> background_thread_;
};
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP8_SingleEncodingDefaultsToL1T1) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP8");
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing.
auto stats_result =
GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1));
ASSERT_THAT(stats_result, IsRtcOk());
EXPECT_THAT(GetOutboundRtpStreamStatsByRid(stats_result.value()),
ElementsAre(Pair("", ResolutionIs(1280, 720))));
// Verify codec and scalability mode.
rtc::scoped_refptr<const RTCStatsReport> report = stats_result.value();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(1u));
EXPECT_THAT(outbound_rtps, Contains(ResolutionIs(Le(1280), Le(720))));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
StrCaseEq("video/VP8"));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T1"));
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP8_RejectsSvcAndDefaultsToL1T1) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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);
// Restricting the local receive codecs will restrict what we offer and
// hence the answer if it is a subset of our offer.
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8");
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
// Attempt SVC (L3T3_KEY). This is not possible because only VP8 is up for
// negotiation and VP8 does not support it.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
EXPECT_FALSE(sender->SetParameters(parameters).ok());
// `scalability_mode` remains unset because SetParameters() failed.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt));
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1)),
IsRtcOk());
// When `scalability_mode` is not set, VP8 defaults to L1T1.
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/VP8"));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T1"));
// GetParameters() confirms `scalability_mode` is still not set.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt));
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersWithScalabilityModeNotSupportedBySubsequentNegotiation) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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);
// Restricting the local receive codecs will restrict what we offer and
// hence the answer if it is a subset of our offer.
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8");
transceiver->SetCodecPreferences(codecs);
// Attempt SVC (L3T3_KEY). This is still possible because VP9 might be
// available from the remote end.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
// `scalability_mode` is set to the VP8 default since that is what was
// negotiated.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq("L1T2"));
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing.
auto error_or_stats =
GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1));
ASSERT_THAT(error_or_stats, IsRtcOk());
// When `scalability_mode` is not set, VP8 defaults to L1T1.
rtc::scoped_refptr<const RTCStatsReport> report = error_or_stats.value();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(1u));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
StrCaseEq("video/VP8"));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T2"));
// GetParameters() confirms `scalability_mode` is still not set.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq("L1T2"));
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP8_FallbackFromSvcResultsInL1T2) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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);
// Verify test assumption that VP8 is first in the list, but don't modify the
// codec preferences because we want the sender to think SVC is a possibility.
std::vector<RtpCodecCapability> codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
EXPECT_THAT(codecs[0].name, StrCaseEq("VP8"));
// Attempt SVC (L3T3_KEY), which is not possible with VP8, but the sender does
// not yet know which codec we'll use so the parameters will be accepted.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
// Verify fallback has not happened yet.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L3T3_KEY")));
// Negotiate, this results in VP8 being picked and fallback happening.
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// `scalaiblity_mode` is assigned the fallback value "L1T2" which is different
// than the default of std::nullopt.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L1T2")));
// Wait until media is flowing, no significant time needed because we only
// have one layer.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1u)),
IsRtcOk());
// GetStats() confirms "L1T2" is used which is different than the "L1T1"
// default or the "L3T3_KEY" that was attempted.
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/VP8"));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T2"));
// Now that we know VP8 is used, try setting L3T3 which should fail.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
EXPECT_FALSE(sender->SetParameters(parameters).ok());
}
// The legacy SVC path is triggered when VP9 us used, but `scalability_mode` has
// not been specified.
// TODO(https://crbug.com/webrtc/14889): When legacy VP9 SVC path has been
// deprecated and removed, update this test to assert that simulcast is used
// (i.e. VP9 is not treated differently than VP8).
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_LegacySvcWhenScalabilityModeNotSpecified) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing. We only expect a single RTP stream.
// We expect to see bytes flowing almost immediately on the lowest layer.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1u)),
IsRtcOk());
// Wait until scalability mode is reported and expected resolution reached.
// Ramp up time may be significant.
ASSERT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(Contains(
AllOf(RidIs("f"), ScalabilityModeIs("L3T3_KEY"), HeightIs(720)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// Despite SVC being used on a single RTP stream, GetParameters() returns the
// three encodings that we configured earlier (this is not spec-compliant but
// it is how legacy SVC behaves).
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
std::vector<RtpEncodingParameters> encodings =
sender->GetParameters().encodings;
ASSERT_EQ(encodings.size(), 3u);
// When legacy SVC is used, `scalability_mode` is not specified.
EXPECT_FALSE(encodings[0].scalability_mode.has_value());
EXPECT_FALSE(encodings[1].scalability_mode.has_value());
EXPECT_FALSE(encodings[2].scalability_mode.has_value());
}
// The spec-compliant way to configure SVC for a single stream. The expected
// outcome is the same as for the legacy SVC case except that we only have one
// encoding in GetParameters().
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_StandardSvcWithOnlyOneEncoding) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Configure SVC, a.k.a. "L3T3_KEY".
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing. We only expect a single RTP stream.
// We expect to see bytes flowing almost immediately on the lowest layer.
auto error_or_stats =
GetStatsUntil(local_pc_wrapper,
AllOf(HasOutboundRtpBytesSent(1u),
OutboundRtpStatsAre(Contains(HeightIs(720)))),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_stats, IsRtcOk());
// Verify codec and scalability mode.
scoped_refptr<const RTCStatsReport> report = error_or_stats.value();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(1u));
EXPECT_THAT(outbound_rtps[0], ResolutionIs(1280, 720));
EXPECT_THAT(outbound_rtps[0], RidIs(std::nullopt));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
StrCaseEq("video/VP9"));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L3T3_KEY"));
// GetParameters() is consistent with what we asked for and got.
parameters = sender->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 1u);
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L3T3_KEY")));
}
// The {active,inactive,inactive} case is technically simulcast but since we
// only have one active stream, we're able to do SVC (multiple spatial layers
// is not supported if multiple encodings are active). The expected outcome is
// the same as above except we end up with two inactive RTP streams which are
// observable in GetStats().
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_StandardSvcWithSingleActiveEncoding) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Configure SVC, a.k.a. "L3T3_KEY".
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Since the standard API is configuring simulcast we get three outbound-rtps,
// but only one is active.
// Wait until scalability mode is reported and expected resolution reached.
// Ramp up time is significant.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper,
AllOf(HasOutboundRtpBytesSent(3, 1),
OutboundRtpStatsAre(Contains(AllOf(
RidIs("f"), ScalabilityModeIs("L3T3_KEY"),
HeightIs(720))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// GetParameters() is consistent with what we asked for and got.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(StrEq("L3T3_KEY")));
EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value());
EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value());
}
// Exercise common path where `scalability_mode` is not specified until after
// negotiation, requiring us to recreate the stream when the number of streams
// changes from 1 (legacy SVC) to 3 (standard simulcast).
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_SwitchFromLegacySvcToStandardSingleActiveEncodingSvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// The original negotiation triggers legacy SVC because we didn't specify
// any scalability mode.
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Switch to the standard mode. Despite only having a single active stream in
// both cases, this internally reconfigures from 1 stream to 3 streams.
// Test coverage for https://crbug.com/webrtc/15016.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = true;
parameters.encodings[0].scalability_mode = "L2T2_KEY";
parameters.encodings[0].scale_resolution_down_by = 2.0;
parameters.encodings[1].active = false;
parameters.encodings[1].scalability_mode = std::nullopt;
parameters.encodings[2].active = false;
parameters.encodings[2].scalability_mode = std::nullopt;
sender->SetParameters(parameters);
// Since the standard API is configuring simulcast we get three outbound-rtps,
// but only one is active.
// Wait until scalability mode is reported and expected resolution reached.
// Ramp up time may be significant.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper,
AllOf(HasOutboundRtpBytesSent(3, 1),
OutboundRtpStatsAre(Contains(AllOf(
RidIs("f"), ScalabilityModeIs("L2T2_KEY"),
HeightIs(720 / 2))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L2T2_KEY")));
EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value());
EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_SimulcastDeactiveActiveLayer_StandardSvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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);
constexpr absl::string_view kCodec = "VP9";
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, kCodec);
transceiver->SetCodecPreferences(codecs);
// Switch to the standard mode. Despite only having a single active stream in
// both cases, this internally reconfigures from 1 stream to 3 streams.
// Test coverage for https://crbug.com/webrtc/15016.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = true;
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_by = 4.0;
parameters.encodings[1].active = true;
parameters.encodings[1].scalability_mode = "L1T1";
parameters.encodings[1].scale_resolution_down_by = 2.0;
parameters.encodings[2].active = true;
parameters.encodings[2].scalability_mode = "L1T1";
parameters.encodings[2].scale_resolution_down_by = 1.0;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
// The original negotiation triggers legacy SVC because we didn't specify
// any scalability mode.
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Since the standard API is configuring simulcast we get three outbound-rtps,
// and two are active.
ASSERT_THAT(
WaitUntil(
[&] {
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
GetStats(local_pc_wrapper)
->GetStatsOfType<RTCOutboundRtpStreamStats>();
std::vector<size_t> bytes_sent;
bytes_sent.reserve(outbound_rtps.size());
for (const auto* outbound_rtp : outbound_rtps) {
bytes_sent.push_back(outbound_rtp->bytes_sent.value_or(0));
}
return bytes_sent;
},
AllOf(SizeIs(3), Each(Gt(0))), {.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
ASSERT_TRUE(report);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
EXPECT_THAT(outbound_rtps,
Each(EncoderImplementationIs(
"SimulcastEncoderAdapter (libvpx, libvpx, libvpx)")));
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(StrEq("L1T3")));
EXPECT_THAT(parameters.encodings[1].scalability_mode,
Optional(StrEq("L1T1")));
EXPECT_THAT(parameters.encodings[2].scalability_mode,
Optional(StrEq("L1T1")));
EXPECT_THAT(parameters.encodings[2].scale_resolution_down_by, Eq(1.0));
EXPECT_THAT(parameters.encodings[1].scale_resolution_down_by, Eq(2.0));
EXPECT_THAT(parameters.encodings[0].scale_resolution_down_by, Eq(4.0));
// Deactivate the active layer.
parameters.encodings[2].active = false;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
ASSERT_THAT(WaitUntil(
[&]() {
return GetStats(local_pc_wrapper)
->GetStatsOfType<RTCOutboundRtpStreamStats>();
},
AllOf(Each(EncoderImplementationIs(
"SimulcastEncoderAdapter (libvpx, libvpx)")),
UnorderedElementsAre(ScalabilityModeIs("L1T3"),
ScalabilityModeIs("L1T1"),
ScalabilityModeIs(std::nullopt)))),
IsRtcOk());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_SimulcastMultiplLayersActive_StandardSvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Switch to the standard mode. Despite only having a single active stream in
// both cases, this internally reconfigures from 1 stream to 3 streams.
// Test coverage for https://crbug.com/webrtc/15016.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = true;
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_by = 4.0;
parameters.encodings[1].active = true;
parameters.encodings[1].scalability_mode = "L1T1";
parameters.encodings[1].scale_resolution_down_by = 2.0;
parameters.encodings[2].active = false;
parameters.encodings[2].scalability_mode = std::nullopt;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
// The original negotiation triggers legacy SVC because we didn't specify
// any scalability mode.
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Since the standard API is configuring simulcast we get three outbound-rtps,
// and two are active.
// Wait until scalability mode is reported and expected resolution reached.
// Ramp up time may be significant.
auto error_or_stats = GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(
IsSupersetOf({AllOf(RidIs("q"), ScalabilityModeIs("L1T3"),
HeightIs(720 / 4), BytesSentIs(Gt(0))),
AllOf(RidIs("h"), ScalabilityModeIs("L1T1"),
HeightIs(720 / 2), BytesSentIs(Gt(0)))})),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_stats, IsRtcOk());
rtc::scoped_refptr<const RTCStatsReport> report = error_or_stats.value();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
EXPECT_THAT(outbound_rtps, Each(EncoderImplementationIs(
"SimulcastEncoderAdapter (libvpx, libvpx)")));
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(StrEq("L1T3")));
EXPECT_THAT(parameters.encodings[1].scalability_mode,
Optional(StrEq("L1T1")));
EXPECT_THAT(parameters.encodings[2].scalability_mode, Eq(std::nullopt));
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_Simulcast_SwitchToLegacySvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Switch to the standard mode. Despite only having a single active stream in
// both cases, this internally reconfigures from 1 stream to 3 streams.
// Test coverage for https://crbug.com/webrtc/15016.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = false;
parameters.encodings[1].active = true;
parameters.encodings[1].scalability_mode = "L1T1";
parameters.encodings[1].scale_resolution_down_by = 2.0;
parameters.encodings[2].active = true;
parameters.encodings[2].scalability_mode = "L1T3";
parameters.encodings[2].scale_resolution_down_by = 4.0;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
// The original negotiation triggers legacy SVC because we didn't specify
// any scalability mode.
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Since the standard API is configuring simulcast we get three outbound-rtps,
// and two are active.
// Wait until scalability mode is reported and expected resolution reached.
// Ramp up time may be significant.
auto error_or_stats = GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), ScalabilityModeIs("L1T3"), HeightIs(720 / 4)),
AllOf(RidIs("h"), ScalabilityModeIs("L1T1"), HeightIs(720 / 2)),
AllOf(RidIs("f"), BytesSentIs(AnyOf(0, std::nullopt))))),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_stats, IsRtcOk());
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt));
EXPECT_THAT(parameters.encodings[1].scalability_mode,
Optional(StrEq("L1T1")));
EXPECT_THAT(parameters.encodings[2].scalability_mode,
Optional(StrEq("L1T3")));
// Switch to legacy SVC mode.
parameters.encodings[0].active = true;
parameters.encodings[0].scalability_mode = std::nullopt;
parameters.encodings[0].scale_resolution_down_by = std::nullopt;
parameters.encodings[1].active = true;
parameters.encodings[1].scalability_mode = std::nullopt;
parameters.encodings[1].scale_resolution_down_by = std::nullopt;
parameters.encodings[2].active = false;
parameters.encodings[2].scalability_mode = std::nullopt;
parameters.encodings[2].scale_resolution_down_by = std::nullopt;
EXPECT_TRUE(sender->SetParameters(parameters).ok());
// Ensure that we are getting VGA at L1T3 from the "f" rid.
EXPECT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(Contains(AllOf(
RidIs("f"), ScalabilityModeIs("L2T3_KEY"), HeightIs(720 / 2)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_OneLayerActive_LegacySvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Sending L1T3 with legacy SVC mode means setting 1 layer active.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = true;
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Ensure that we are getting 180P at L1T3 from the "f" rid.
EXPECT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(Contains(
AllOf(RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720 / 4)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_AllLayersInactive_LegacySvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Legacy SVC mode and all layers inactive.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].active = false;
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Ensure no media is flowing (1 second should be enough).
rtc::Thread::Current()->SleepMs(1000);
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_EQ(*outbound_rtps[0]->bytes_sent, 0u);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
VP9_AllLayersInactive_StandardSvc) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// Standard mode and all layers inactive.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L3T3_KEY";
parameters.encodings[0].scale_resolution_down_by = 1;
parameters.encodings[0].active = false;
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Ensure no media is flowing (1 second should be enough).
rtc::Thread::Current()->SleepMs(1000);
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_EQ(*outbound_rtps[0]->bytes_sent, 0u);
EXPECT_EQ(*outbound_rtps[1]->bytes_sent, 0u);
EXPECT_EQ(*outbound_rtps[2]->bytes_sent, 0u);
}
TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_TargetBitrate_LegacyL1T3) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// In legacy SVC, disabling the bottom two layers encodings is interpreted as
// disabling the bottom two spatial layers resulting in L1T3.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
parameters.encodings[0].active = false;
parameters.encodings[1].active = false;
parameters.encodings[2].active = true;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until 720p L1T3 has ramped up to 720p. It may take additional time
// for the target bitrate to reach its maximum.
ASSERT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(Contains(AllOf(
RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// The target bitrate typically reaches `kVp9ExpectedMaxBitrateForL1T3`
// in a short period of time. However to reduce risk of flakiness in bot
// environments, this test only fails if we we exceed the expected target.
rtc::Thread::Current()->SleepMs(1000);
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(1));
DataRate target_bitrate =
DataRate::BitsPerSec(*outbound_rtps[0]->target_bitrate);
EXPECT_LE(target_bitrate.kbps(), kVp9ExpectedMaxBitrateForL1T3.kbps());
}
// Test coverage for https://crbug.com/1455039.
TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_TargetBitrate_StandardL1T3) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
transceiver->SetCodecPreferences(codecs);
// With standard APIs, L1T3 is explicitly specified and the encodings refers
// to the RTP streams, not the spatial layers. The end result should be
// equivalent to the legacy L1T3 case.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
parameters.encodings[0].active = true;
parameters.encodings[0].scale_resolution_down_by = 1.0;
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until 720p L1T3 has ramped up to 720p. It may take additional time
// for the target bitrate to reach its maximum.
ASSERT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(Contains(AllOf(
RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// The target bitrate typically reaches `kVp9ExpectedMaxBitrateForL1T3`
// in a short period of time. However to reduce risk of flakiness in bot
// environments, this test only fails if we we exceed the expected target.
rtc::Thread::Current()->SleepMs(1000);
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(3));
auto* outbound_rtp = FindOutboundRtpByRid(outbound_rtps, "f");
ASSERT_TRUE(outbound_rtp);
DataRate target_bitrate = DataRate::BitsPerSec(*outbound_rtp->target_bitrate);
EXPECT_LE(target_bitrate.kbps(), kVp9ExpectedMaxBitrateForL1T3.kbps());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SimulcastProducesUniqueSsrcAndRtxSsrcs) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP8");
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait until media is flowing on all three layers.
// Ramp up time is needed before all three layers are sending.
auto stats = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(stats, IsRtcOk());
// Verify SSRCs and RTX SSRCs.
rtc::scoped_refptr<const RTCStatsReport> report = stats.MoveValue();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(3u));
std::set<uint32_t> ssrcs;
std::set<uint32_t> rtx_ssrcs;
for (const auto& outbound_rtp : outbound_rtps) {
ASSERT_TRUE(outbound_rtp->ssrc.has_value());
ASSERT_TRUE(outbound_rtp->rtx_ssrc.has_value());
ssrcs.insert(*outbound_rtp->ssrc);
rtx_ssrcs.insert(*outbound_rtp->rtx_ssrc);
}
EXPECT_EQ(ssrcs.size(), 3u);
EXPECT_EQ(rtx_ssrcs.size(), 3u);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsEmptyWhenCreatedAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
EXPECT_FALSE(parameters.encodings[0].codec.has_value());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsEmptyWhenCreatedVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
EXPECT_FALSE(parameters.encodings[0].codec.has_value());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetByAddTransceiverAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/true, {}, /*video=*/false, {});
rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
std::optional<RtpCodecCapability> pcmu =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"pcmu");
ASSERT_TRUE(pcmu);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = pcmu;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(track, init);
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(*parameters.encodings[0].codec, *pcmu);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
ASSERT_TRUE(local_pc_wrapper->WaitForConnection());
ASSERT_TRUE(remote_pc_wrapper->WaitForConnection());
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetByAddTransceiverVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
std::optional<RtpCodecCapability> vp9 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp9");
ASSERT_TRUE(vp9);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = vp9;
encoding_parameters.scalability_mode = "L3T3";
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(track, init);
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(*parameters.encodings[0].codec, *vp9);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
auto error_or_stats =
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(Contains(ScalabilityModeIs("L3T3"))));
ASSERT_THAT(error_or_stats, IsRtcOk());
rtc::scoped_refptr<const RTCStatsReport> report = error_or_stats.MoveValue();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3");
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetBySetParametersBeforeNegotiationAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/true, {}, /*video=*/false, {});
rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
std::optional<RtpCodecCapability> pcmu =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"pcmu");
auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = pcmu;
EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok());
parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, pcmu);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetBySetParametersAfterNegotiationAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/true, {}, /*video=*/false, {});
rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
std::optional<RtpCodecCapability> pcmu =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"pcmu");
auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASENE(("audio/" + pcmu->name).c_str(), codec_name.c_str());
std::string last_codec_id = outbound_rtps[0]->codec_id.value();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = pcmu;
EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok());
parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, pcmu);
auto error_or_stats =
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(Contains(CodecIs(Ne(last_codec_id)))));
ASSERT_THAT(error_or_stats, IsRtcOk());
report = error_or_stats.MoveValue();
outbound_rtps = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetBySetParametersBeforeNegotiationVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
std::optional<RtpCodecCapability> vp9 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp9");
auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = vp9;
parameters.encodings[0].scalability_mode = "L3T3";
EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok());
parameters = video_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, vp9);
EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3");
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
auto error_or_stats = GetStatsUntil(
local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf(
ScalabilityModeIs("L3T3"), CodecIs(Ne(""))))));
ASSERT_THAT(error_or_stats, IsRtcOk());
rtc::scoped_refptr<const RTCStatsReport> report = error_or_stats.MoveValue();
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
EXPECT_EQ(outbound_rtps[0]->scalability_mode.value_or(""), "L3T3");
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsSetBySetParametersAfterNegotiationVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
std::optional<RtpCodecCapability> vp9 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp9");
auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASENE(("audio/" + vp9->name).c_str(), codec_name.c_str());
std::string last_codec_id = outbound_rtps[0]->codec_id.value();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = vp9;
parameters.encodings[0].scalability_mode = "L3T3";
EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok());
parameters = video_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, vp9);
EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3");
auto error_or_stats = GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(Contains(
AllOf(ScalabilityModeIs("L3T3"), CodecIs(Ne(last_codec_id))))));
ASSERT_THAT(error_or_stats, IsRtcOk());
report = error_or_stats.MoveValue();
outbound_rtps = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_EQ(outbound_rtps.size(), 1u);
codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3");
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
AddTransceiverRejectsUnknownCodecParameterAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
RtpCodec dummy_codec;
dummy_codec.kind = cricket::MEDIA_TYPE_AUDIO;
dummy_codec.name = "FOOBAR";
dummy_codec.clock_rate = 90000;
dummy_codec.num_channels = 2;
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = dummy_codec;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
EXPECT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
AddTransceiverRejectsUnknownCodecParameterVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
RtpCodec dummy_codec;
dummy_codec.kind = cricket::MEDIA_TYPE_VIDEO;
dummy_codec.name = "FOOBAR";
dummy_codec.clock_rate = 90000;
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = dummy_codec;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
EXPECT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsUnknownCodecParameterAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
RtpCodec dummy_codec;
dummy_codec.kind = cricket::MEDIA_TYPE_AUDIO;
dummy_codec.name = "FOOBAR";
dummy_codec.clock_rate = 90000;
dummy_codec.num_channels = 2;
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = dummy_codec;
RTCError error = audio_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsUnknownCodecParameterVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
RtpCodec dummy_codec;
dummy_codec.kind = cricket::MEDIA_TYPE_VIDEO;
dummy_codec.name = "FOOBAR";
dummy_codec.clock_rate = 90000;
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = dummy_codec;
RTCError error = video_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsNonNegotiatedCodecParameterAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> opus =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"opus");
ASSERT_TRUE(opus);
std::vector<RtpCodecCapability> not_opus_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
.codecs;
not_opus_codecs.erase(
std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, opus->name);
}),
not_opus_codecs.end());
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = opus;
RTCError error = audio_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsNonRemotelyNegotiatedCodecParameterAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> opus =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"opus");
ASSERT_TRUE(opus);
std::vector<RtpCodecCapability> not_opus_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
.codecs;
not_opus_codecs.erase(
std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, opus->name);
}),
not_opus_codecs.end());
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
// Negotiation, create offer and apply it
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p1 =
SetLocalDescription(local_pc_wrapper, offer.get());
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p2 =
SetRemoteDescription(remote_pc_wrapper, offer.get());
EXPECT_TRUE(Await({p1, p2}));
// Update the remote transceiver to reject Opus
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remote_transceivers =
remote_pc_wrapper->pc()->GetTransceivers();
ASSERT_TRUE(!remote_transceivers.empty());
rtc::scoped_refptr<RtpTransceiverInterface> remote_audio_transceiver =
remote_transceivers[0];
ASSERT_TRUE(
remote_audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
// Create answer and apply it
std::unique_ptr<SessionDescriptionInterface> answer =
CreateAnswer(remote_pc_wrapper);
p1 = SetLocalDescription(remote_pc_wrapper, answer.get());
p2 = SetRemoteDescription(local_pc_wrapper, answer.get());
EXPECT_TRUE(Await({p1, p2}));
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = opus;
RTCError error = audio_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
// Test coverage for https://crbug.com/webrtc/391340599.
// Some web apps add non-standard FMTP parameters to video codecs and because
// they get successfully negotiated due to being ignored by SDP rules, they show
// up in GetParameters().codecs. Using SetParameters() with such codecs should
// still work.
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersAcceptsMungedCodecFromGetParameters) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
// Munge a new parameter for VP8 in the offer.
auto* mcd = offer->description()->contents()[0].media_description();
ASSERT_THAT(mcd, NotNull());
std::vector<cricket::Codec> codecs = mcd->codecs();
ASSERT_THAT(codecs, Contains(Field(&cricket::Codec::name, "VP8")));
auto vp8_codec = absl::c_find_if(
codecs, [](const cricket::Codec& codec) { return codec.name == "VP8"; });
vp8_codec->params.emplace("non-standard-param", "true");
mcd->set_codecs(codecs);
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p1 =
SetLocalDescription(local_pc_wrapper, offer.get());
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p2 =
SetRemoteDescription(remote_pc_wrapper, offer.get());
EXPECT_TRUE(Await({p1, p2}));
// Create answer and apply it
std::unique_ptr<SessionDescriptionInterface> answer =
CreateAnswer(remote_pc_wrapper);
mcd = answer->description()->contents()[0].media_description();
ASSERT_THAT(mcd, NotNull());
codecs = mcd->codecs();
ASSERT_THAT(codecs, Contains(Field(&cricket::Codec::name, "VP8")));
vp8_codec = absl::c_find_if(
codecs, [](const cricket::Codec& codec) { return codec.name == "VP8"; });
vp8_codec->params.emplace("non-standard-param", "true");
mcd->set_codecs(codecs);
p1 = SetLocalDescription(remote_pc_wrapper, answer.get());
p2 = SetRemoteDescription(local_pc_wrapper, answer.get());
EXPECT_TRUE(Await({p1, p2}));
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
auto it = absl::c_find_if(
parameters.codecs, [](const auto& codec) { return codec.name == "VP8"; });
ASSERT_NE(it, parameters.codecs.end());
RtpCodecParameters& vp8_codec_from_parameters = *it;
EXPECT_THAT(vp8_codec_from_parameters.parameters,
Contains(Pair("non-standard-param", "true")));
parameters.encodings[0].codec = vp8_codec_from_parameters;
EXPECT_THAT(video_transceiver->sender()->SetParameters(parameters),
IsRtcOk());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsNonNegotiatedCodecParameterVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
std::vector<RtpCodecCapability> not_vp8_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
not_vp8_codecs.erase(
std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, vp8->name);
}),
not_vp8_codecs.end());
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = vp8;
RTCError error = video_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsNonRemotelyNegotiatedCodecParameterVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
std::vector<RtpCodecCapability> not_vp8_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
not_vp8_codecs.erase(
std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, vp8->name);
}),
not_vp8_codecs.end());
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
// Negotiation, create offer and apply it
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p1 =
SetLocalDescription(local_pc_wrapper, offer.get());
rtc::scoped_refptr<MockSetSessionDescriptionObserver> p2 =
SetRemoteDescription(remote_pc_wrapper, offer.get());
EXPECT_TRUE(Await({p1, p2}));
// Update the remote transceiver to reject VP8
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remote_transceivers =
remote_pc_wrapper->pc()->GetTransceivers();
ASSERT_TRUE(!remote_transceivers.empty());
rtc::scoped_refptr<RtpTransceiverInterface> remote_video_transceiver =
remote_transceivers[0];
ASSERT_TRUE(
remote_video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
// Create answer and apply it
std::unique_ptr<SessionDescriptionInterface> answer =
CreateAnswer(remote_pc_wrapper);
p1 = SetLocalDescription(remote_pc_wrapper, answer.get());
p2 = SetRemoteDescription(local_pc_wrapper, answer.get());
EXPECT_TRUE(Await({p1, p2}));
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].codec = vp8;
RTCError error = video_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParametersCodecRemovedAfterNegotiationAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> opus =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"opus");
ASSERT_TRUE(opus);
std::vector<RtpCodecCapability> not_opus_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
.codecs;
not_opus_codecs.erase(
std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, opus->name);
}),
not_opus_codecs.end());
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = opus;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, opus);
ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
parameters = audio_transceiver->sender()->GetParameters();
EXPECT_FALSE(parameters.encodings[0].codec);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParametersRedEnabledBeforeNegotiationAudio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<RtpCodecCapability> send_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
.codecs;
std::optional<RtpCodecCapability> opus =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"opus");
ASSERT_TRUE(opus);
std::optional<RtpCodecCapability> red =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
"red");
ASSERT_TRUE(red);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = opus;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
transceiver_or_error.MoveValue();
// Preferring RED over Opus should enable RED with Opus encoding.
send_codecs[0] = red.value();
send_codecs[1] = opus.value();
ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, opus);
EXPECT_EQ(parameters.codecs[0].name, red->name);
// Check that it's possible to switch back to Opus without RED.
send_codecs[0] = opus.value();
send_codecs[1] = red.value();
ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
parameters = audio_transceiver->sender()->GetParameters();
EXPECT_EQ(parameters.encodings[0].codec, opus);
EXPECT_EQ(parameters.codecs[0].name, opus->name);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsScalabilityModeForSelectedCodec) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.codec = vp8;
encoding_parameters.scalability_mode = "L1T3";
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
parameters.encodings[0].scalability_mode = "L3T3";
RTCError error = video_transceiver->sender()->SetParameters(parameters);
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParametersCodecRemovedByNegotiationVideo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
std::vector<RtpCodecCapability> not_vp8_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
not_vp8_codecs.erase(
std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
[&](const auto& codec) {
return absl::EqualsIgnoreCase(codec.name, vp8->name);
}),
not_vp8_codecs.end());
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.rid = "h";
encoding_parameters.codec = vp8;
encoding_parameters.scale_resolution_down_by = 2;
init.send_encodings.push_back(encoding_parameters);
encoding_parameters.rid = "f";
encoding_parameters.scale_resolution_down_by = 1;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
transceiver_or_error.MoveValue();
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
RtpParameters parameters = video_transceiver->sender()->GetParameters();
ASSERT_EQ(parameters.encodings.size(), 2u);
EXPECT_EQ(parameters.encodings[0].codec, vp8);
EXPECT_EQ(parameters.encodings[1].codec, vp8);
ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
parameters = video_transceiver->sender()->GetParameters();
EXPECT_FALSE(parameters.encodings[0].codec);
EXPECT_FALSE(parameters.encodings[1].codec);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
AddTransceiverRejectsMixedCodecSimulcast) {
// Mixed Codec Simulcast is not yet supported, so we ensure that we reject
// such parameters.
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
std::optional<RtpCodecCapability> vp9 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp9");
ASSERT_TRUE(vp9);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.rid = "h";
encoding_parameters.codec = vp8;
encoding_parameters.scale_resolution_down_by = 2;
init.send_encodings.push_back(encoding_parameters);
encoding_parameters.rid = "f";
encoding_parameters.codec = vp9;
encoding_parameters.scale_resolution_down_by = 1;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
ASSERT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
AddTransceiverAcceptsMixedCodecSimulcast) {
// Enable WIP mixed codec simulcast support
std::string field_trials = "WebRTC-MixedCodecSimulcast/Enabled/";
scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper =
CreatePc(FieldTrials::CreateNoGlobal(field_trials));
scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper =
CreatePc(FieldTrials::CreateNoGlobal(field_trials));
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::optional<RtpCodecCapability> vp8 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp8");
ASSERT_TRUE(vp8);
std::optional<RtpCodecCapability> vp9 =
local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
"vp9");
ASSERT_TRUE(vp9);
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
RtpEncodingParameters encoding_parameters;
encoding_parameters.rid = "h";
encoding_parameters.codec = vp8;
encoding_parameters.scale_resolution_down_by = 2;
init.send_encodings.push_back(encoding_parameters);
encoding_parameters.rid = "f";
encoding_parameters.codec = vp9;
encoding_parameters.scale_resolution_down_by = 1;
init.send_encodings.push_back(encoding_parameters);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
ASSERT_TRUE(transceiver_or_error.ok());
}
TEST_F(PeerConnectionEncodingsIntegrationTest, ScaleToParameterChecking) {
rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper = CreatePc();
// AddTransceiver: If `scale_resolution_down_to` is specified on any encoding
// it must be specified on all encodings.
RtpTransceiverInit init;
RtpEncodingParameters encoding;
encoding.scale_resolution_down_to = std::nullopt;
init.send_encodings.push_back(encoding);
encoding.scale_resolution_down_to = {.width = 1280, .height = 720};
init.send_encodings.push_back(encoding);
auto transceiver_or_error =
pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
EXPECT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
// AddTransceiver: Width and height must not be zero.
init.send_encodings[0].scale_resolution_down_to = {.width = 1280,
.height = 0};
init.send_encodings[1].scale_resolution_down_to = {.width = 0, .height = 720};
transceiver_or_error =
pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
EXPECT_FALSE(transceiver_or_error.ok());
EXPECT_EQ(transceiver_or_error.error().type(),
RTCErrorType::UNSUPPORTED_OPERATION);
// AddTransceiver: Specifying both `scale_resolution_down_to` and
// `scale_resolution_down_by` is allowed (the latter is ignored).
init.send_encodings[0].scale_resolution_down_to = {.width = 640,
.height = 480};
init.send_encodings[0].scale_resolution_down_by = 1.0;
init.send_encodings[1].scale_resolution_down_to = {.width = 1280,
.height = 720};
init.send_encodings[1].scale_resolution_down_by = 2.0;
transceiver_or_error =
pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
ASSERT_TRUE(transceiver_or_error.ok());
// SetParameters: If `scale_resolution_down_to` is specified on any active
// encoding it must be specified on all active encodings.
auto sender = transceiver_or_error.value()->sender();
auto parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 640,
.height = 480};
parameters.encodings[1].scale_resolution_down_to = std::nullopt;
auto error = sender->SetParameters(parameters);
EXPECT_FALSE(error.ok());
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
// But it's OK not to specify `scale_resolution_down_to` on an inactive
// encoding.
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 640,
.height = 480};
parameters.encodings[1].active = false;
parameters.encodings[1].scale_resolution_down_to = std::nullopt;
error = sender->SetParameters(parameters);
EXPECT_TRUE(error.ok());
// SetParameters: Width and height must not be zero.
sender = transceiver_or_error.value()->sender();
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 1280,
.height = 0};
parameters.encodings[1].active = true;
parameters.encodings[1].scale_resolution_down_to = {.width = 0,
.height = 720};
error = sender->SetParameters(parameters);
EXPECT_FALSE(error.ok());
EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
// SetParameters: Specifying both `scale_resolution_down_to` and
// `scale_resolution_down_by` is allowed (the latter is ignored).
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 640,
.height = 480};
parameters.encodings[0].scale_resolution_down_by = 2.0;
parameters.encodings[1].scale_resolution_down_to = {.width = 1280,
.height = 720};
parameters.encodings[1].scale_resolution_down_by = 1.0;
error = sender->SetParameters(parameters);
EXPECT_TRUE(error.ok());
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
ScaleResolutionDownByIsIgnoredWhenScaleToIsSpecified) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/false, {}, /*video=*/true, {.width = 640, .height = 360});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
// Configure contradicting scaling factors (180p vs 360p).
RtpTransceiverInit init;
RtpEncodingParameters encoding;
encoding.scale_resolution_down_by = 2.0;
encoding.scale_resolution_down_to = {.width = 640, .height = 360};
init.send_encodings.push_back(encoding);
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(track, init);
// Negotiate singlecast.
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
Negotiate(local_pc_wrapper, remote_pc_wrapper);
// Confirm 640x360 is sent.
// If `scale_resolution_down_by` was not ignored we would never ramp up to
// full resolution.
EXPECT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
// Tests that use the standard path (specifying both `scalability_mode` and
// `scale_resolution_down_by` or `scale_resolution_down_to`) should pass for all
// codecs.
class PeerConnectionEncodingsIntegrationParameterizedTest
: public PeerConnectionEncodingsIntegrationTest,
public ::testing::WithParamInterface<std::string> {
public:
PeerConnectionEncodingsIntegrationParameterizedTest()
: codec_name_(GetParam()), mime_type_("video/" + codec_name_) {}
// Work-around for the fact that whether or not AV1 is supported is not known
// at compile-time so we have to skip tests early if missing.
// TODO(https://crbug.com/webrtc/15011): Increase availability of AV1 or make
// it possible to check support at compile-time.
bool SkipTestDueToAv1Missing(
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper) {
if (codec_name_ == "AV1" &&
!HasReceiverVideoCodecCapability(local_pc_wrapper, "AV1")) {
RTC_LOG(LS_WARNING) << "\n***\nAV1 is not available, skipping test.\n***";
return true;
}
return false;
}
protected:
const std::string codec_name_; // E.g. "VP9"
const std::string mime_type_; // E.g. "video/VP9"
};
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, AllLayersInactive) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f", "h", "q"}, /*active=*/true);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
// Standard mode and all layers inactive.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_by = 1;
parameters.encodings[0].active = false;
parameters.encodings[1].active = false;
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Ensure no media is flowing (1 second should be enough).
rtc::Thread::Current()->SleepMs(1000);
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_EQ(*outbound_rtps[0]->bytes_sent, 0u);
EXPECT_EQ(*outbound_rtps[1]->bytes_sent, 0u);
EXPECT_EQ(*outbound_rtps[2]->bytes_sent, 0u);
}
// Configure 4:2:1 using `scale_resolution_down_by`.
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, Simulcast) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_by = 4;
parameters.encodings[1].scalability_mode = "L1T3";
parameters.encodings[1].scale_resolution_down_by = 2;
parameters.encodings[2].scalability_mode = "L1T3";
parameters.encodings[2].scale_resolution_down_by = 1;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L1T3")));
EXPECT_THAT(parameters.encodings[1].scalability_mode,
Optional(std::string("L1T3")));
EXPECT_THAT(parameters.encodings[2].scalability_mode,
Optional(std::string("L1T3")));
// Wait until media is flowing on all three layers.
// Ramp up time is needed before all three layers are sending.
auto error_or_report =
GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_report, IsRtcOk());
// Verify codec and scalability mode.
rtc::scoped_refptr<const RTCStatsReport> report = error_or_report.value();
auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report);
EXPECT_THAT(outbound_rtp_by_rid,
UnorderedElementsAre(Pair("q", ResolutionIs(320, 180)),
Pair("h", ResolutionIs(640, 360)),
Pair("f", ResolutionIs(1280, 720))));
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(3u));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
StrCaseEq(mime_type_));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]),
StrCaseEq(mime_type_));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]),
StrCaseEq(mime_type_));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T3"));
EXPECT_THAT(*outbound_rtps[1]->scalability_mode, StrEq("L1T3"));
EXPECT_THAT(*outbound_rtps[2]->scalability_mode, StrEq("L1T3"));
}
// Configure 4:2:1 using `scale_resolution_down_to`.
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
SimulcastWithScaleTo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_to = {.width = 320,
.height = 180};
parameters.encodings[1].scalability_mode = "L1T3";
parameters.encodings[1].scale_resolution_down_to = {.width = 640,
.height = 360};
parameters.encodings[2].scalability_mode = "L1T3";
parameters.encodings[2].scale_resolution_down_to = {.width = 1280,
.height = 720};
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// GetParameters() does not report any fallback.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
EXPECT_THAT(parameters.encodings[0].scalability_mode,
Optional(std::string("L1T3")));
EXPECT_THAT(parameters.encodings[1].scalability_mode,
Optional(std::string("L1T3")));
EXPECT_THAT(parameters.encodings[2].scalability_mode,
Optional(std::string("L1T3")));
// Wait until media is flowing on all three layers.
// Ramp up time is needed before all three layers are sending.
auto error_or_report =
GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_report, IsRtcOk());
// Verify codec and scalability mode.
rtc::scoped_refptr<const RTCStatsReport> report = error_or_report.value();
auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report);
EXPECT_THAT(outbound_rtp_by_rid,
UnorderedElementsAre(Pair("q", ResolutionIs(320, 180)),
Pair("h", ResolutionIs(640, 360)),
Pair("f", ResolutionIs(1280, 720))));
// Verify codec and scalability mode.
std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
report->GetStatsOfType<RTCOutboundRtpStreamStats>();
ASSERT_THAT(outbound_rtps, SizeIs(3u));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]),
StrCaseEq(mime_type_));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]),
StrCaseEq(mime_type_));
EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]),
StrCaseEq(mime_type_));
EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T3"));
EXPECT_THAT(*outbound_rtps[1]->scalability_mode, StrEq("L1T3"));
EXPECT_THAT(*outbound_rtps[2]->scalability_mode, StrEq("L1T3"));
}
// Simulcast starting in 720p 4:2:1 then changing to {180p, 360p, 540p} using
// the `scale_resolution_down_by` API.
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
SimulcastScaleDownByNoLongerPowerOfTwo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
// Configure {180p, 360p, 720p}.
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L1T1";
parameters.encodings[0].scale_resolution_down_by = 4.0;
parameters.encodings[1].scalability_mode = "L1T1";
parameters.encodings[1].scale_resolution_down_by = 2.0;
parameters.encodings[2].scalability_mode = "L1T1";
parameters.encodings[2].scale_resolution_down_by = 1.0;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait for media to flow on all layers.
// Needed repro step of https://crbug.com/webrtc/369654168: When the same
// LibvpxVp9Encoder instance was used to first produce simulcast and later for
// a single encoding, the previously used simulcast index (= 2) would still be
// set when producing 180p since non-simulcast config does not reset this,
// resulting in the 180p encoding freezing and the 540p encoding having double
// frame rate and toggling between 180p and 540p in resolution.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// Configure {180p, 360p, 540p}.
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_by = 4.0;
parameters.encodings[1].scale_resolution_down_by = 2.0;
parameters.encodings[2].scale_resolution_down_by = 1.333333;
sender->SetParameters(parameters);
// Wait for the new resolutions to be produced.
auto encoding_resolutions_result =
WaitUntil([&] { return GetStats(local_pc_wrapper); },
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), ResolutionIs(320, 180)),
AllOf(RidIs("h"), ResolutionIs(640, 360)),
AllOf(RidIs("f"), ResolutionIs(960, 540)))),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(encoding_resolutions_result, IsRtcOk());
auto outbound_rtp_by_rid =
GetOutboundRtpStreamStatsByRid(encoding_resolutions_result.value());
ASSERT_THAT(outbound_rtp_by_rid,
UnorderedElementsAre(Key("q"), Key("h"), Key("f")));
// Ensure frames continue to be encoded post reconfiguration.
uint64_t frames_encoded_q =
outbound_rtp_by_rid.at("q").frames_encoded.value();
uint64_t frames_encoded_h =
outbound_rtp_by_rid.at("h").frames_encoded.value();
uint64_t frames_encoded_f =
outbound_rtp_by_rid.at("f").frames_encoded.value();
EXPECT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), FramesEncodedIs(Gt(frames_encoded_q))),
AllOf(RidIs("h"), FramesEncodedIs(Gt(frames_encoded_h))),
AllOf(RidIs("f"), FramesEncodedIs(Gt(frames_encoded_f))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
// Simulcast starting in 720p 4:2:1 then changing to {180p, 360p, 540p} using
// the `scale_resolution_down_to` API.
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
SimulcastScaleToNoLongerPowerOfTwo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
// Configure {180p, 360p, 720p}.
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[0].scalability_mode = "L1T1";
parameters.encodings[0].scale_resolution_down_to = {.width = 320,
.height = 180};
parameters.encodings[1].scalability_mode = "L1T1";
parameters.encodings[1].scale_resolution_down_to = {.width = 640,
.height = 360};
parameters.encodings[2].scalability_mode = "L1T1";
parameters.encodings[2].scale_resolution_down_to = {.width = 1280,
.height = 720};
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait for media to flow on all layers.
// Needed repro step of https://crbug.com/webrtc/369654168: When the same
// LibvpxVp9Encoder instance was used to first produce simulcast and later for
// a single encoding, the previously used simulcast index (= 2) would still be
// set when producing 180p since non-simulcast config does not reset this,
// resulting in the 180p encoding freezing and the 540p encoding having double
// frame rate and toggling between 180p and 540p in resolution.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// Configure {180p, 360p, 540p}.
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 320,
.height = 180};
parameters.encodings[1].scale_resolution_down_to = {.width = 640,
.height = 360};
parameters.encodings[2].scale_resolution_down_to = {.width = 960,
.height = 540};
sender->SetParameters(parameters);
// Wait for the new resolutions to be produced.
auto error_or_stats =
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), ResolutionIs(320, 180)),
AllOf(RidIs("h"), ResolutionIs(640, 360)),
AllOf(RidIs("f"), ResolutionIs(960, 540)))),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_stats, IsRtcOk());
auto outbound_rtp_by_rid =
GetOutboundRtpStreamStatsByRid(error_or_stats.value());
ASSERT_THAT(outbound_rtp_by_rid,
UnorderedElementsAre(Pair("q", BytesSentIs(Ne(std::nullopt))),
Pair("h", BytesSentIs(Ne(std::nullopt))),
Pair("f", BytesSentIs(Ne(std::nullopt)))));
// Ensure frames continue to be encoded post reconfiguration.
EXPECT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"),
BytesSentIs(
Gt(outbound_rtp_by_rid.at("q").bytes_sent.value()))),
AllOf(RidIs("h"),
BytesSentIs(
Gt(outbound_rtp_by_rid.at("h").bytes_sent.value()))),
AllOf(RidIs("f"),
BytesSentIs(
Gt(outbound_rtp_by_rid.at("f").bytes_sent.value()))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
// The code path that disables layers based on resolution size should NOT run
// when `scale_resolution_down_to` is specified. (It shouldn't run in any case
// but that is an existing legacy code and non-compliance problem that we don't
// have to repeat here.)
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
LowResolutionSimulcastWithScaleTo) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"q", "h", "f"}, /*active=*/true);
// Configure {20p,40p,80p} with 2:1 aspect ratio.
RtpTransceiverInit init;
RtpEncodingParameters encoding;
encoding.scalability_mode = "L1T3";
encoding.rid = "q";
encoding.scale_resolution_down_to = {.width = 40, .height = 20};
init.send_encodings.push_back(encoding);
encoding.rid = "h";
encoding.scale_resolution_down_to = {.width = 80, .height = 40};
init.send_encodings.push_back(encoding);
encoding.rid = "f";
encoding.scale_resolution_down_to = {.width = 160, .height = 80};
init.send_encodings.push_back(encoding);
rtc::scoped_refptr<MediaStreamInterface> stream =
local_pc_wrapper->GetUserMedia(
/*audio=*/false, {}, /*video=*/true, {.width = 160, .height = 80});
rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
auto transceiver_or_error =
local_pc_wrapper->pc()->AddTransceiver(track, init);
ASSERT_TRUE(transceiver_or_error.ok());
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
transceiver_or_error.value();
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Wait for media to flow on all layers.
ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u)),
IsRtcOk());
// q=20p, h=40p, f=80p.
EXPECT_THAT(GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), ResolutionIs(40, 20)),
AllOf(RidIs("h"), ResolutionIs(80, 40)),
AllOf(RidIs("f"), ResolutionIs(160, 80)))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
SimulcastEncodingStopWhenRtpEncodingChangeToInactive) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
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> codecs =
GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
ASSERT_EQ(parameters.encodings[0].rid, "q");
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_by = 4;
ASSERT_EQ(parameters.encodings[1].rid, "h");
parameters.encodings[1].scalability_mode = "L1T3";
parameters.encodings[1].scale_resolution_down_by = 2;
ASSERT_EQ(parameters.encodings[2].rid, "f");
parameters.encodings[2].scalability_mode = "L1T3";
parameters.encodings[2].scale_resolution_down_by = 1;
sender->SetParameters(parameters);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
ASSERT_THAT(GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(Contains(
AllOf(RidIs("f"), FramesEncodedIs(Gt(0))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// Switch higest layer to Inactive.
parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(3));
parameters.encodings[2].active = false;
sender->SetParameters(parameters);
auto error_or_stats = GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(Contains(AllOf(RidIs("f"), Not(Active())))),
{.timeout = kLongTimeoutForRampingUp});
ASSERT_THAT(error_or_stats, IsRtcOk());
auto outbound_rtp_by_rid =
GetOutboundRtpStreamStatsByRid(error_or_stats.value());
int encoded_frames_f = outbound_rtp_by_rid.at("f").frames_encoded.value();
int encoded_frames_h = outbound_rtp_by_rid.at("h").frames_encoded.value();
int encoded_frames_q = outbound_rtp_by_rid.at("q").frames_encoded.value();
// Wait until the encoder has encoded another 10 frames on lower layers.
ASSERT_THAT(
GetStatsUntil(
local_pc_wrapper,
OutboundRtpStatsAre(UnorderedElementsAre(
AllOf(RidIs("q"), FramesEncodedIs(Gt(encoded_frames_q + 10))),
AllOf(RidIs("h"), FramesEncodedIs(Gt(encoded_frames_h + 10))),
AllOf(RidIs("f"), FramesEncodedIs(Le(encoded_frames_f + 2))))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
ScaleToDownscaleAndThenUpscale) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f"}, /*active=*/true);
// This transceiver receives a 1280x720 source.
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Request 640x360, which is the same as scaling down by 2.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(1));
parameters.encodings[0].scalability_mode = "L1T3";
parameters.encodings[0].scale_resolution_down_to = {.width = 640,
.height = 360};
sender->SetParameters(parameters);
// Confirm 640x360 is sent.
ASSERT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
// Test coverage for https://crbug.com/webrtc/361477261:
// Due initial frame dropping, OnFrameDroppedDueToSize() should have created
// some resolution restrictions by now. With 720p input frame, restriction is
// 540p which is not observable when sending 360p, but it prevents us from
// immediately sending 720p. Restrictions will be lifted after a few seconds
// (when good QP is reported by QualityScaler) and 720p should be sent. The
// bug was not reconfiguring the encoder when restrictions were updated so the
// restrictions at the time of the SetParameter() call were made indefinite.
// Request the full 1280x720 resolution.
parameters = sender->GetParameters();
parameters.encodings[0].scale_resolution_down_to = {.width = 1280,
.height = 720};
sender->SetParameters(parameters);
// Confirm 1280x720 is sent.
EXPECT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(ElementsAre(ResolutionIs(1280, 720))),
{.timeout = kLongTimeoutForRampingUp}),
IsRtcOk());
}
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
ScaleToIsOrientationAgnostic) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f"}, /*active=*/true);
// This transceiver receives a 1280x720 source.
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// 360x640 is the same as 640x360 due to orientation agnosticism.
// The orientation is determined by the frame (1280x720): landscape.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(1));
parameters.encodings[0].scale_resolution_down_to = {.width = 360,
.height = 640};
sender->SetParameters(parameters);
// Confirm 640x360 is sent.
EXPECT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360)))),
IsRtcOk());
}
TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest,
ScaleToMaintainsAspectRatio) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
if (SkipTestDueToAv1Missing(local_pc_wrapper)) {
return;
}
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
std::vector<cricket::SimulcastLayer> layers =
CreateLayers({"f"}, /*active=*/true);
// This transceiver receives a 1280x720 source.
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
layers);
std::vector<RtpCodecCapability> codecs =
GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_);
transceiver->SetCodecPreferences(codecs);
NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
local_pc_wrapper->WaitForConnection();
remote_pc_wrapper->WaitForConnection();
// Restrict height more than width, the scaling factor needed on height should
// also be applied on the width in order to maintain the frame aspect ratio.
rtc::scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
RtpParameters parameters = sender->GetParameters();
ASSERT_THAT(parameters.encodings, SizeIs(1));
parameters.encodings[0].scale_resolution_down_to = {.width = 1280,
.height = 360};
sender->SetParameters(parameters);
// Confirm 640x360 is sent.
EXPECT_THAT(
GetStatsUntil(local_pc_wrapper,
OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360)))),
IsRtcOk());
}
INSTANTIATE_TEST_SUITE_P(StandardPath,
PeerConnectionEncodingsIntegrationParameterizedTest,
::testing::Values("VP8",
"VP9",
#if defined(WEBRTC_USE_H264)
"H264",
#endif // defined(WEBRTC_USE_H264)
"AV1"),
StringParamToString());
// 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:
#ifdef RTC_ENABLE_H265
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;
}
#endif // RTC_ENABLE_H265
// Creates a PC where we have H264 with one sendonly, one recvonly and one
// sendrecv "profile-level-id". The sendrecv one is constrained baseline.
scoped_refptr<PeerConnectionTestWrapper> CreatePcWithUnidirectionalH264(
std::unique_ptr<FieldTrialsView> field_trials = nullptr) {
std::unique_ptr<cricket::FakeWebRtcVideoEncoderFactory>
video_encoder_factory =
std::make_unique<cricket::FakeWebRtcVideoEncoderFactory>();
SdpVideoFormat h264_constrained_baseline =
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "42f00b"}}, // sendrecv
{ScalabilityMode::kL1T1});
video_encoder_factory->AddSupportedVideoCodec(h264_constrained_baseline);
video_encoder_factory->AddSupportedVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "640034"}}, // sendonly
{ScalabilityMode::kL1T1}));
std::unique_ptr<cricket::FakeWebRtcVideoDecoderFactory>
video_decoder_factory =
std::make_unique<cricket::FakeWebRtcVideoDecoderFactory>();
video_decoder_factory->AddSupportedVideoCodec(h264_constrained_baseline);
video_decoder_factory->AddSupportedVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "f4001f"}}, // recvonly
{ScalabilityMode::kL1T1}));
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;
}
std::string LocalDescriptionStr(PeerConnectionTestWrapper* pc_wrapper) {
const SessionDescriptionInterface* local_description =
pc_wrapper->pc()->local_description();
if (!local_description) {
return "";
}
std::string str;
if (!local_description->ToString(&str)) {
return "";
}
return str;
}
};
#ifdef RTC_ENABLE_H265
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
TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest,
H264UnidirectionalNegotiation) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper =
CreatePcWithUnidirectionalH264();
rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper =
CreatePcWithUnidirectionalH264();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
rtc::scoped_refptr<RtpTransceiverInterface> transceiver =
local_pc_wrapper->pc()
->AddTransceiver(cricket::MEDIA_TYPE_VIDEO)
.MoveValue();
// Filter on codec name and assert that sender capabilities have codecs for
// {sendrecv, sendonly} and the receiver capabilities have codecs for
// {sendrecv, recvonly}.
std::vector<RtpCodecCapability> send_codecs =
local_pc_wrapper->pc_factory()
->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
send_codecs.erase(std::remove_if(send_codecs.begin(), send_codecs.end(),
[](const RtpCodecCapability& codec) {
return codec.name != "H264";
}),
send_codecs.end());
std::vector<RtpCodecCapability> recv_codecs =
local_pc_wrapper->pc_factory()
->GetRtpReceiverCapabilities(cricket::MEDIA_TYPE_VIDEO)
.codecs;
recv_codecs.erase(std::remove_if(recv_codecs.begin(), recv_codecs.end(),
[](const RtpCodecCapability& codec) {
RTC_LOG(LS_ERROR) << codec.name;
return codec.name != "H264";
}),
recv_codecs.end());
ASSERT_THAT(send_codecs, SizeIs(2u));
ASSERT_THAT(recv_codecs, SizeIs(2u));
EXPECT_EQ(send_codecs[0], recv_codecs[0]);
EXPECT_NE(send_codecs[1], recv_codecs[1]);
RtpCodecCapability& sendrecv_codec = send_codecs[0];
RtpCodecCapability& sendonly_codec = send_codecs[1];
RtpCodecCapability& recvonly_codec = recv_codecs[1];
// Preferring sendonly + recvonly on a sendrecv transceiver is the same as
// not having any preferences, meaning the sendrecv codec (not listed) is the
// one being negotiated.
std::vector<RtpCodecCapability> preferred_codecs = {sendonly_codec,
recvonly_codec};
EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk());
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
Negotiate(local_pc_wrapper, remote_pc_wrapper);
std::string local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
HasSubstr(sendrecv_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendonly_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
Not(HasSubstr(recvonly_codec.parameters["profile-level-id"])));
// Prefer all codecs and expect that the SDP offer contains the relevant
// codecs after filtering. Complete O/A each time.
preferred_codecs = {sendrecv_codec, sendonly_codec, recvonly_codec};
EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk());
// Transceiver direction: sendrecv.
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
Negotiate(local_pc_wrapper, remote_pc_wrapper);
local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
HasSubstr(sendrecv_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendonly_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
Not(HasSubstr(recvonly_codec.parameters["profile-level-id"])));
// Transceiver direction: sendonly.
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
Negotiate(local_pc_wrapper, remote_pc_wrapper);
local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
HasSubstr(sendrecv_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
HasSubstr(sendonly_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
Not(HasSubstr(recvonly_codec.parameters["profile-level-id"])));
// Transceiver direction: recvonly.
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
Negotiate(local_pc_wrapper, remote_pc_wrapper);
local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
HasSubstr(sendrecv_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendonly_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
HasSubstr(recvonly_codec.parameters["profile-level-id"]));
// Test that offering a sendonly codec on a sendonly transceiver is possible.
// - Note that we don't complete the negotiation this time because we're not
// capable of receiving the codec.
preferred_codecs = {sendonly_codec};
EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk());
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
std::unique_ptr<SessionDescriptionInterface> offer =
CreateOffer(local_pc_wrapper);
EXPECT_TRUE(Await({SetLocalDescription(local_pc_wrapper, offer.get())}));
local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendrecv_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
HasSubstr(sendonly_codec.parameters["profile-level-id"]));
EXPECT_THAT(local_sdp,
Not(HasSubstr(recvonly_codec.parameters["profile-level-id"])));
// Test that offering recvonly codec on a recvonly transceiver is possible.
// - Note that we don't complete the negotiation this time because we're not
// capable of sending the codec.
preferred_codecs = {recvonly_codec};
EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk());
EXPECT_THAT(
transceiver->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
offer = CreateOffer(local_pc_wrapper);
EXPECT_TRUE(Await({SetLocalDescription(local_pc_wrapper, offer.get())}));
local_sdp = LocalDescriptionStr(local_pc_wrapper.get());
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendrecv_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
Not(HasSubstr(sendonly_codec.parameters["profile-level-id"])));
EXPECT_THAT(local_sdp,
HasSubstr(recvonly_codec.parameters["profile-level-id"]));
}
} // namespace webrtc