blob: 3145fd5850d7cce23b9039f1ca1c5064355f7aad [file] [log] [blame] [edit]
/*
* 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