webrtc_m130/pc/rtp_transceiver_unittest.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

882 lines
39 KiB
C++

/*
* Copyright 2018 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.
*/
// This file contains tests for `RtpTransceiver`.
#include "pc/rtp_transceiver.h"
#include <memory>
#include <optional>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/environment/environment_factory.h"
#include "api/peer_connection_interface.h"
#include "api/rtp_parameters.h"
#include "api/test/rtc_error_matchers.h"
#include "media/base/codec_comparators.h"
#include "media/base/fake_media_engine.h"
#include "pc/rtp_parameters_conversion.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/mock_channel_interface.h"
#include "pc/test/mock_rtp_receiver_internal.h"
#include "pc/test/mock_rtp_sender_internal.h"
#include "rtc_base/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SizeIs;
namespace webrtc {
namespace {
class RtpTransceiverTest : public testing::Test {
public:
RtpTransceiverTest()
: dependencies_(MakeDependencies()),
context_(
ConnectionContext::Create(CreateEnvironment(), &dependencies_)) {}
protected:
cricket::FakeMediaEngine* media_engine() {
// We know this cast is safe because we supplied the fake implementation
// in MakeDependencies().
return static_cast<cricket::FakeMediaEngine*>(context_->media_engine());
}
ConnectionContext* context() { return context_.get(); }
private:
rtc::AutoThread main_thread_;
static PeerConnectionFactoryDependencies MakeDependencies() {
PeerConnectionFactoryDependencies d;
d.network_thread = rtc::Thread::Current();
d.worker_thread = rtc::Thread::Current();
d.signaling_thread = rtc::Thread::Current();
EnableFakeMedia(d, std::make_unique<cricket::FakeMediaEngine>());
return d;
}
PeerConnectionFactoryDependencies dependencies_;
rtc::scoped_refptr<ConnectionContext> context_;
};
// Checks that a channel cannot be set on a stopped `RtpTransceiver`.
TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
const std::string content_name("my_mid");
auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
cricket::MediaType::MEDIA_TYPE_AUDIO, context());
auto channel1 = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
EXPECT_CALL(*channel1, media_type())
.WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name));
EXPECT_CALL(*channel1, SetFirstPacketReceivedCallback(_));
EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true));
auto channel1_ptr = channel1.get();
transceiver->SetChannel(std::move(channel1), [&](const std::string& mid) {
EXPECT_EQ(mid, content_name);
return nullptr;
});
EXPECT_EQ(channel1_ptr, transceiver->channel());
// Stop the transceiver.
transceiver->StopInternal();
EXPECT_EQ(channel1_ptr, transceiver->channel());
auto channel2 = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
EXPECT_CALL(*channel2, media_type())
.WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
// Clear the current channel - required to allow SetChannel()
EXPECT_CALL(*channel1_ptr, SetFirstPacketReceivedCallback(_));
transceiver->ClearChannel();
ASSERT_EQ(nullptr, transceiver->channel());
// Channel can no longer be set, so this call should be a no-op.
transceiver->SetChannel(std::move(channel2),
[](const std::string&) { return nullptr; });
EXPECT_EQ(nullptr, transceiver->channel());
}
// Checks that a channel can be unset on a stopped `RtpTransceiver`
TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) {
const std::string content_name("my_mid");
auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
cricket::MediaType::MEDIA_TYPE_VIDEO, context());
auto channel = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
EXPECT_CALL(*channel, media_type())
.WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_VIDEO));
EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name));
EXPECT_CALL(*channel, SetFirstPacketReceivedCallback(_))
.WillRepeatedly(testing::Return());
EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
auto channel_ptr = channel.get();
transceiver->SetChannel(std::move(channel), [&](const std::string& mid) {
EXPECT_EQ(mid, content_name);
return nullptr;
});
EXPECT_EQ(channel_ptr, transceiver->channel());
// Stop the transceiver.
transceiver->StopInternal();
EXPECT_EQ(channel_ptr, transceiver->channel());
// Set the channel to `nullptr`.
transceiver->ClearChannel();
EXPECT_EQ(nullptr, transceiver->channel());
}
class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest {
public:
static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver(
cricket::MediaType media_type) {
auto receiver = rtc::make_ref_counted<NiceMock<MockRtpReceiverInternal>>();
EXPECT_CALL(*receiver.get(), media_type())
.WillRepeatedly(Return(media_type));
return receiver;
}
static rtc::scoped_refptr<MockRtpSenderInternal> MockSender(
cricket::MediaType media_type) {
auto sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
EXPECT_CALL(*sender.get(), media_type()).WillRepeatedly(Return(media_type));
return sender;
}
rtc::scoped_refptr<RtpTransceiver> CreateTransceiver(
rtc::scoped_refptr<RtpSenderInternal> sender,
rtc::scoped_refptr<RtpReceiverInternal> receiver) {
return rtc::make_ref_counted<RtpTransceiver>(
RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
rtc::Thread::Current(), std::move(sender)),
RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
rtc::Thread::Current(), rtc::Thread::Current(),
std::move(receiver)),
context(), media_engine()->voice().GetRtpHeaderExtensions(),
/* on_negotiation_needed= */ [] {});
}
protected:
rtc::AutoThread main_thread_;
};
// Basic tests for Stop()
TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) {
rtc::scoped_refptr<MockRtpReceiverInternal> receiver =
MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO);
rtc::scoped_refptr<MockRtpSenderInternal> sender =
MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO);
rtc::scoped_refptr<RtpTransceiver> transceiver =
CreateTransceiver(sender, receiver);
EXPECT_CALL(*receiver.get(), Stop());
EXPECT_CALL(*receiver.get(), SetMediaChannel(_));
EXPECT_CALL(*sender.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender.get(), Stop());
EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction());
EXPECT_FALSE(transceiver->current_direction());
transceiver->StopStandard();
EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
EXPECT_FALSE(transceiver->current_direction());
transceiver->StopTransceiverProcedure();
EXPECT_TRUE(transceiver->current_direction());
EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
EXPECT_EQ(RtpTransceiverDirection::kStopped,
*transceiver->current_direction());
}
class RtpTransceiverFilteredCodecPreferencesTest
: public RtpTransceiverUnifiedPlanTest {
public:
RtpTransceiverFilteredCodecPreferencesTest()
: transceiver_(CreateTransceiver(
MockSender(cricket::MediaType::MEDIA_TYPE_VIDEO),
MockReceiver(cricket::MediaType::MEDIA_TYPE_VIDEO))) {}
struct H264CodecCapabilities {
cricket::Codec cricket_sendrecv_codec;
RtpCodecCapability sendrecv_codec;
cricket::Codec cricket_sendonly_codec;
RtpCodecCapability sendonly_codec;
cricket::Codec cricket_recvonly_codec;
RtpCodecCapability recvonly_codec;
cricket::Codec cricket_rtx_codec;
RtpCodecCapability rtx_codec;
};
// For H264, the profile and level IDs are entangled. This function uses
// profile-level-id values that are not equal even when levels are ignored.
H264CodecCapabilities ConfigureH264CodecCapabilities() {
cricket::Codec cricket_sendrecv_codec = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "42f00b"}},
{ScalabilityMode::kL1T1}));
cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "640034"}},
{ScalabilityMode::kL1T1}));
cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "f4001f"}},
{ScalabilityMode::kL1T1}));
cricket::Codec cricket_rtx_codec = cricket::CreateVideoRtxCodec(
cricket::Codec::kIdNotSet, cricket::Codec::kIdNotSet);
media_engine()->SetVideoSendCodecs(
{cricket_sendrecv_codec, cricket_sendonly_codec, cricket_rtx_codec});
media_engine()->SetVideoRecvCodecs(
{cricket_sendrecv_codec, cricket_recvonly_codec, cricket_rtx_codec});
H264CodecCapabilities capabilities = {
.cricket_sendrecv_codec = cricket_sendrecv_codec,
.sendrecv_codec = ToRtpCodecCapability(cricket_sendrecv_codec),
.cricket_sendonly_codec = cricket_sendonly_codec,
.sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
.cricket_recvonly_codec = cricket_recvonly_codec,
.recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
.cricket_rtx_codec = cricket_rtx_codec,
.rtx_codec = ToRtpCodecCapability(cricket_rtx_codec),
};
EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
capabilities.cricket_sendrecv_codec, capabilities.sendonly_codec));
EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
capabilities.cricket_sendrecv_codec, capabilities.recvonly_codec));
EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
capabilities.cricket_sendonly_codec, capabilities.recvonly_codec));
return capabilities;
}
#ifdef RTC_ENABLE_H265
struct H265CodecCapabilities {
// The level-id from sender getCapabilities() or receiver getCapabilities().
static constexpr const char* kSendOnlyLevel = "180";
static constexpr const char* kRecvOnlyLevel = "156";
// A valid H265 level-id, but one not present in either getCapabilities().
static constexpr const char* kLevelNotInCapabilities = "135";
cricket::Codec cricket_sendonly_codec;
RtpCodecCapability sendonly_codec;
cricket::Codec cricket_recvonly_codec;
RtpCodecCapability recvonly_codec;
};
// For H265, the profile and level IDs are separate and are ignored by
// IsSameRtpCodecIgnoringLevel().
H265CodecCapabilities ConfigureH265CodecCapabilities() {
cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec(
SdpVideoFormat("H265",
{{"profile-id", "1"},
{"tier-flag", "0"},
{"level-id", H265CodecCapabilities::kSendOnlyLevel},
{"tx-mode", "SRST"}},
{ScalabilityMode::kL1T1}));
cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec(
SdpVideoFormat("H265",
{{"profile-id", "1"},
{"tier-flag", "0"},
{"level-id", H265CodecCapabilities::kRecvOnlyLevel},
{"tx-mode", "SRST"}},
{ScalabilityMode::kL1T1}));
media_engine()->SetVideoSendCodecs({cricket_sendonly_codec});
media_engine()->SetVideoRecvCodecs({cricket_recvonly_codec});
return {
.cricket_sendonly_codec = cricket_sendonly_codec,
.sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
.cricket_recvonly_codec = cricket_recvonly_codec,
.recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
};
}
#endif // RTC_ENABLE_H265
protected:
rtc::scoped_refptr<RtpTransceiver> transceiver_;
};
TEST_F(RtpTransceiverFilteredCodecPreferencesTest, EmptyByDefault) {
ConfigureH264CodecCapabilities();
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest, OrderIsMaintained) {
const auto codecs = ConfigureH264CodecCapabilities();
std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendrecv_codec,
codecs.rtx_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0], codec_capabilities[1]));
// Reverse order.
codec_capabilities = {codecs.rtx_codec, codecs.sendrecv_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0], codec_capabilities[1]));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
FiltersCodecsBasedOnDirection) {
const auto codecs = ConfigureH264CodecCapabilities();
std::vector<RtpCodecCapability> codec_capabilities = {
codecs.sendonly_codec, codecs.sendrecv_codec, codecs.recvonly_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.sendrecv_codec));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.sendonly_codec, codecs.sendrecv_codec));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.sendrecv_codec, codecs.recvonly_codec));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.sendrecv_codec));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
RtxIsIncludedAfterFiltering) {
const auto codecs = ConfigureH264CodecCapabilities();
std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
codecs.rtx_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
NoMediaIsTheSameAsNoPreference) {
const auto codecs = ConfigureH264CodecCapabilities();
std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
codecs.rtx_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
// After filtering the only codec that remains is RTX which is not a media
// codec, this is the same as not having any preferences.
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
// But the preferences are remembered in case the direction changes such that
// we do have a media codec.
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
H264LevelIdsIgnoredByFilter) {
// Baseline 3.1 and 5.2 are compatible when ignoring level IDs.
cricket::Codec baseline_3_1 = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "42001f"}},
{ScalabilityMode::kL1T1}));
cricket::Codec baseline_5_2 = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "420034"}},
{ScalabilityMode::kL1T1}));
// High is NOT compatible with baseline.
cricket::Codec high_3_1 = cricket::CreateVideoCodec(
SdpVideoFormat("H264",
{{"level-asymmetry-allowed", "1"},
{"packetization-mode", "1"},
{"profile-level-id", "64001f"}},
{ScalabilityMode::kL1T1}));
// Configure being able to both send and receive Baseline but using different
// level IDs in either direction, while the High profile is "truly" recvonly.
media_engine()->SetVideoSendCodecs({baseline_3_1});
media_engine()->SetVideoRecvCodecs({baseline_5_2, high_3_1});
// Prefer to "sendrecv" Baseline 5.2. Even though we can only send 3.1 this
// codec is not filtered out due to 5.2 and 3.1 being compatible when ignoring
// level IDs.
std::vector<RtpCodecCapability> codec_capabilities = {
ToRtpCodecCapability(baseline_5_2)};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0]));
// Prefer to "sendrecv" High 3.1. This gets filtered out because we cannot
// send it (Baseline 3.1 is not compatible with it).
codec_capabilities = {ToRtpCodecCapability(high_3_1)};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
// Change direction to "recvonly" to avoid High 3.1 being filtered out.
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0]));
}
#ifdef RTC_ENABLE_H265
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
H265LevelIdIsIgnoredByFilter) {
const auto codecs = ConfigureH265CodecCapabilities();
std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendonly_codec,
codecs.recvonly_codec};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
// Regardless of direction, both codecs are preferred due to ignoring levels.
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0], codec_capabilities[1]));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0], codec_capabilities[1]));
EXPECT_THAT(
transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
IsRtcOk());
EXPECT_THAT(transceiver_->filtered_codec_preferences(),
ElementsAre(codec_capabilities[0], codec_capabilities[1]));
}
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
H265LevelIdHasToBeFromSenderOrReceiverCapabilities) {
ConfigureH265CodecCapabilities();
cricket::Codec cricket_codec = cricket::CreateVideoCodec(SdpVideoFormat(
"H265",
{{"profile-id", "1"},
{"tier-flag", "0"},
{"level-id", H265CodecCapabilities::kLevelNotInCapabilities},
{"tx-mode", "SRST"}},
{ScalabilityMode::kL1T1}));
std::vector<RtpCodecCapability> codec_capabilities = {
ToRtpCodecCapability(cricket_codec)};
EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities),
IsRtcErrorWithTypeAndMessage(
RTCErrorType::INVALID_MODIFICATION,
"Invalid codec preferences: Missing codec from codec "
"capabilities."));
}
#endif // RTC_ENABLE_H265
class RtpTransceiverTestForHeaderExtensions
: public RtpTransceiverUnifiedPlanTest {
public:
RtpTransceiverTestForHeaderExtensions()
: extensions_(
{RtpHeaderExtensionCapability("uri1",
1,
RtpTransceiverDirection::kSendOnly),
RtpHeaderExtensionCapability("uri2",
2,
RtpTransceiverDirection::kRecvOnly),
RtpHeaderExtensionCapability(RtpExtension::kMidUri,
3,
RtpTransceiverDirection::kSendRecv),
RtpHeaderExtensionCapability(RtpExtension::kVideoRotationUri,
4,
RtpTransceiverDirection::kSendRecv)}),
transceiver_(rtc::make_ref_counted<RtpTransceiver>(
RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
rtc::Thread::Current(),
sender_),
RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
rtc::Thread::Current(),
rtc::Thread::Current(),
receiver_),
context(),
extensions_,
/* on_negotiation_needed= */ [] {})) {}
void ClearChannel() {
EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
transceiver_->ClearChannel();
}
rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ =
MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO);
rtc::scoped_refptr<MockRtpSenderInternal> sender_ =
MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO);
std::vector<RtpHeaderExtensionCapability> extensions_;
rtc::scoped_refptr<RtpTransceiver> transceiver_;
};
TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto modified_extensions = extensions_;
modified_extensions[0].direction = RtpTransceiverDirection::kSendOnly;
EXPECT_TRUE(
transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
modified_extensions);
modified_extensions[0].direction = RtpTransceiverDirection::kRecvOnly;
EXPECT_TRUE(
transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
modified_extensions);
modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
EXPECT_TRUE(
transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
modified_extensions);
modified_extensions[0].direction = RtpTransceiverDirection::kInactive;
EXPECT_TRUE(
transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
modified_extensions);
}
TEST_F(RtpTransceiverTestForHeaderExtensions, AcceptsStoppedExtension) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto modified_extensions = extensions_;
modified_extensions[0].direction = RtpTransceiverDirection::kStopped;
EXPECT_TRUE(
transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
modified_extensions);
}
TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsDifferentSize) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto modified_extensions = extensions_;
modified_extensions.pop_back();
EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsChangedUri) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto modified_extensions = extensions_;
ASSERT_TRUE(!modified_extensions.empty());
modified_extensions[0].uri = "http://webrtc.org";
EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsReorder) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto modified_extensions = extensions_;
ASSERT_GE(modified_extensions.size(), 2u);
std::swap(modified_extensions[0], modified_extensions[1]);
EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_F(RtpTransceiverTestForHeaderExtensions,
RejectsStoppedMandatoryExtensions) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
std::vector<RtpHeaderExtensionCapability> modified_extensions = extensions_;
// Attempting to stop the mandatory MID extension.
modified_extensions[2].direction = RtpTransceiverDirection::kStopped;
EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}
TEST_F(RtpTransceiverTestForHeaderExtensions,
NoNegotiatedHdrExtsWithoutChannel) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
}
TEST_F(RtpTransceiverTestForHeaderExtensions,
NoNegotiatedHdrExtsWithChannelWithoutNegotiation) {
const std::string content_name("my_mid");
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return());
EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return());
EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto mock_channel =
std::make_unique<NiceMock<cricket::MockChannelInterface>>();
auto mock_channel_ptr = mock_channel.get();
EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
EXPECT_CALL(*mock_channel, media_type())
.WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
EXPECT_CALL(*mock_channel, voice_media_send_channel())
.WillRepeatedly(Return(nullptr));
EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
transceiver_->SetChannel(std::move(mock_channel),
[](const std::string&) { return nullptr; });
EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_));
ClearChannel();
}
TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) {
const std::string content_name("my_mid");
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return());
EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return());
EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
auto mock_channel =
std::make_unique<NiceMock<cricket::MockChannelInterface>>();
auto mock_channel_ptr = mock_channel.get();
EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
EXPECT_CALL(*mock_channel, media_type())
.WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
EXPECT_CALL(*mock_channel, voice_media_send_channel())
.WillRepeatedly(Return(nullptr));
EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
cricket::RtpHeaderExtensions extensions = {RtpExtension("uri1", 1),
RtpExtension("uri2", 2)};
cricket::AudioContentDescription description;
description.set_rtp_header_extensions(extensions);
transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);
transceiver_->SetChannel(std::move(mock_channel),
[](const std::string&) { return nullptr; });
EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_));
ClearChannel();
}
TEST_F(RtpTransceiverTestForHeaderExtensions,
ReturnsNegotiatedHdrExtsSecondTime) {
EXPECT_CALL(*receiver_.get(), Stop());
EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
EXPECT_CALL(*sender_.get(), Stop());
cricket::RtpHeaderExtensions extensions = {RtpExtension("uri1", 1),
RtpExtension("uri2", 2)};
cricket::AudioContentDescription description;
description.set_rtp_header_extensions(extensions);
transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);
EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kSendRecv),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
extensions = {RtpExtension("uri3", 4), RtpExtension("uri5", 6)};
description.set_rtp_header_extensions(extensions);
transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);
EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped),
Field(&RtpHeaderExtensionCapability::direction,
RtpTransceiverDirection::kStopped)));
}
TEST_F(RtpTransceiverTestForHeaderExtensions,
SimulcastOrSvcEnablesExtensionsByDefault) {
std::vector<RtpHeaderExtensionCapability> extensions = {
{RtpExtension::kDependencyDescriptorUri, 1,
RtpTransceiverDirection::kStopped},
{RtpExtension::kVideoLayersAllocationUri, 2,
RtpTransceiverDirection::kStopped},
};
// Default is stopped.
auto sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
rtc::Thread::Current(), sender),
RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
rtc::Thread::Current(), rtc::Thread::Current(), receiver_),
context(), extensions,
/* on_negotiation_needed= */ [] {});
std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions =
transceiver->GetHeaderExtensionsToNegotiate();
ASSERT_EQ(header_extensions.size(), 2u);
EXPECT_EQ(header_extensions[0].uri, RtpExtension::kDependencyDescriptorUri);
EXPECT_EQ(header_extensions[0].direction, RtpTransceiverDirection::kStopped);
EXPECT_EQ(header_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri);
EXPECT_EQ(header_extensions[1].direction, RtpTransceiverDirection::kStopped);
// Simulcast, i.e. more than one encoding.
RtpParameters simulcast_parameters;
simulcast_parameters.encodings.resize(2);
auto simulcast_sender =
rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
EXPECT_CALL(*simulcast_sender, GetParametersInternal())
.WillRepeatedly(Return(simulcast_parameters));
auto simulcast_transceiver = rtc::make_ref_counted<RtpTransceiver>(
RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
rtc::Thread::Current(), simulcast_sender),
RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
rtc::Thread::Current(), rtc::Thread::Current(), receiver_),
context(), extensions,
/* on_negotiation_needed= */ [] {});
auto simulcast_extensions =
simulcast_transceiver->GetHeaderExtensionsToNegotiate();
ASSERT_EQ(simulcast_extensions.size(), 2u);
EXPECT_EQ(simulcast_extensions[0].uri,
RtpExtension::kDependencyDescriptorUri);
EXPECT_EQ(simulcast_extensions[0].direction,
RtpTransceiverDirection::kSendRecv);
EXPECT_EQ(simulcast_extensions[1].uri,
RtpExtension::kVideoLayersAllocationUri);
EXPECT_EQ(simulcast_extensions[1].direction,
RtpTransceiverDirection::kSendRecv);
// SVC, a single encoding with a scalabilityMode other than L1T1.
webrtc::RtpParameters svc_parameters;
svc_parameters.encodings.resize(1);
svc_parameters.encodings[0].scalability_mode = "L3T3";
auto svc_sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
EXPECT_CALL(*svc_sender, GetParametersInternal())
.WillRepeatedly(Return(svc_parameters));
auto svc_transceiver = rtc::make_ref_counted<RtpTransceiver>(
RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
rtc::Thread::Current(), svc_sender),
RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
rtc::Thread::Current(), rtc::Thread::Current(), receiver_),
context(), extensions,
/* on_negotiation_needed= */ [] {});
std::vector<webrtc::RtpHeaderExtensionCapability> svc_extensions =
svc_transceiver->GetHeaderExtensionsToNegotiate();
ASSERT_EQ(svc_extensions.size(), 2u);
EXPECT_EQ(svc_extensions[0].uri, RtpExtension::kDependencyDescriptorUri);
EXPECT_EQ(svc_extensions[0].direction, RtpTransceiverDirection::kSendRecv);
EXPECT_EQ(svc_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri);
EXPECT_EQ(svc_extensions[1].direction, RtpTransceiverDirection::kSendRecv);
}
} // namespace
} // namespace webrtc