blob: 7c1bf6e448db55f0fec818b3d873080319bb0e8d [file] [log] [blame]
/*
* Copyright (c) 2004 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 "media/engine/webrtc_video_engine.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/rtp_parameters.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/test/mock_video_bitrate_allocator.h"
#include "api/test/mock_video_bitrate_allocator_factory.h"
#include "api/test/mock_video_decoder_factory.h"
#include "api/test/mock_video_encoder_factory.h"
#include "api/test/video/function_video_decoder_factory.h"
#include "api/transport/field_trial_based_config.h"
#include "api/units/time_delta.h"
#include "api/video/builtin_video_bitrate_allocator_factory.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_bitrate_allocation.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "api/video_codecs/h264_profile_level_id.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_decoder_factory.h"
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_encoder_factory.h"
#include "call/flexfec_receive_stream.h"
#include "media/base/fake_frame_source.h"
#include "media/base/fake_network_interface.h"
#include "media/base/fake_video_renderer.h"
#include "media/base/media_constants.h"
#include "media/base/rtp_utils.h"
#include "media/base/test_utils.h"
#include "media/engine/fake_webrtc_call.h"
#include "media/engine/fake_webrtc_video_engine.h"
#include "media/engine/simulcast.h"
#include "media/engine/webrtc_voice_engine.h"
#include "modules/rtp_rtcp/source/rtp_packet.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/event.h"
#include "rtc_base/experiments/min_video_bitrate_experiment.h"
#include "rtc_base/fake_clock.h"
#include "rtc_base/gunit.h"
#include "rtc_base/numerics/safe_conversions.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/field_trial.h"
#include "test/fake_decoder.h"
#include "test/field_trial.h"
#include "test/frame_forwarder.h"
#include "test/gmock.h"
using ::testing::_;
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::StrNe;
using ::testing::Values;
using ::webrtc::BitrateConstraints;
using ::webrtc::RtpExtension;
using ::webrtc::RtpPacket;
namespace {
static const int kDefaultQpMax = 56;
static const uint8_t kRedRtxPayloadType = 125;
static const uint32_t kTimeout = 5000U;
static const uint32_t kSsrc = 1234u;
static const uint32_t kSsrcs4[] = {1, 2, 3, 4};
static const int kVideoWidth = 640;
static const int kVideoHeight = 360;
static const int kFramerate = 30;
static const uint32_t kSsrcs1[] = {1};
static const uint32_t kSsrcs3[] = {1, 2, 3};
static const uint32_t kRtxSsrcs1[] = {4};
static const uint32_t kFlexfecSsrc = 5;
static const uint32_t kIncomingUnsignalledSsrc = 0xC0FFEE;
static const int64_t kUnsignalledReceiveStreamCooldownMs = 500;
constexpr uint32_t kRtpHeaderSize = 12;
static const char kUnsupportedExtensionName[] =
"urn:ietf:params:rtp-hdrext:unsupported";
cricket::VideoCodec RemoveFeedbackParams(cricket::VideoCodec&& codec) {
codec.feedback_params = cricket::FeedbackParams();
return std::move(codec);
}
void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec,
bool lntf_expected) {
EXPECT_EQ(lntf_expected,
codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamLntf, cricket::kParamValueEmpty)));
EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamNack, cricket::kParamValueEmpty)));
EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamNack, cricket::kRtcpFbNackParamPli)));
EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamRemb, cricket::kParamValueEmpty)));
EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamTransportCc, cricket::kParamValueEmpty)));
EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam(
cricket::kRtcpFbParamCcm, cricket::kRtcpFbCcmParamFir)));
}
// Return true if any codec in `codecs` is an RTX codec with associated payload
// type `payload_type`.
bool HasRtxCodec(const std::vector<cricket::VideoCodec>& codecs,
int payload_type) {
for (const cricket::VideoCodec& codec : codecs) {
int associated_payload_type;
if (absl::EqualsIgnoreCase(codec.name.c_str(), "rtx") &&
codec.GetParam(cricket::kCodecParamAssociatedPayloadType,
&associated_payload_type) &&
associated_payload_type == payload_type) {
return true;
}
}
return false;
}
// TODO(nisse): Duplicated in call.cc.
const int* FindKeyByValue(const std::map<int, int>& m, int v) {
for (const auto& kv : m) {
if (kv.second == v)
return &kv.first;
}
return nullptr;
}
bool HasRtxReceiveAssociation(const webrtc::VideoReceiveStream::Config& config,
int payload_type) {
return FindKeyByValue(config.rtp.rtx_associated_payload_types,
payload_type) != nullptr;
}
// Check that there's an Rtx payload type for each decoder.
bool VerifyRtxReceiveAssociations(
const webrtc::VideoReceiveStream::Config& config) {
for (const auto& decoder : config.decoders) {
if (!HasRtxReceiveAssociation(config, decoder.payload_type))
return false;
}
return true;
}
rtc::scoped_refptr<webrtc::VideoFrameBuffer> CreateBlackFrameBuffer(
int width,
int height) {
rtc::scoped_refptr<webrtc::I420Buffer> buffer =
webrtc::I420Buffer::Create(width, height);
webrtc::I420Buffer::SetBlack(buffer);
return buffer;
}
void VerifySendStreamHasRtxTypes(const webrtc::VideoSendStream::Config& config,
const std::map<int, int>& rtx_types) {
std::map<int, int>::const_iterator it;
it = rtx_types.find(config.rtp.payload_type);
EXPECT_TRUE(it != rtx_types.end() &&
it->second == config.rtp.rtx.payload_type);
if (config.rtp.ulpfec.red_rtx_payload_type != -1) {
it = rtx_types.find(config.rtp.ulpfec.red_payload_type);
EXPECT_TRUE(it != rtx_types.end() &&
it->second == config.rtp.ulpfec.red_rtx_payload_type);
}
}
cricket::MediaConfig GetMediaConfig() {
cricket::MediaConfig media_config;
media_config.video.enable_cpu_adaptation = false;
return media_config;
}
// Values from GetMaxDefaultVideoBitrateKbps in webrtcvideoengine.cc.
int GetMaxDefaultBitrateBps(size_t width, size_t height) {
if (width * height <= 320 * 240) {
return 600000;
} else if (width * height <= 640 * 480) {
return 1700000;
} else if (width * height <= 960 * 540) {
return 2000000;
} else {
return 2500000;
}
}
class MockVideoSource : public rtc::VideoSourceInterface<webrtc::VideoFrame> {
public:
MOCK_METHOD(void,
AddOrUpdateSink,
(rtc::VideoSinkInterface<webrtc::VideoFrame> * sink,
const rtc::VideoSinkWants& wants),
(override));
MOCK_METHOD(void,
RemoveSink,
(rtc::VideoSinkInterface<webrtc::VideoFrame> * sink),
(override));
};
} // namespace
#define EXPECT_FRAME_WAIT(c, w, h, t) \
EXPECT_EQ_WAIT((c), renderer_.num_rendered_frames(), (t)); \
EXPECT_EQ((w), renderer_.width()); \
EXPECT_EQ((h), renderer_.height()); \
EXPECT_EQ(0, renderer_.errors());
#define EXPECT_FRAME_ON_RENDERER_WAIT(r, c, w, h, t) \
EXPECT_EQ_WAIT((c), (r).num_rendered_frames(), (t)); \
EXPECT_EQ((w), (r).width()); \
EXPECT_EQ((h), (r).height()); \
EXPECT_EQ(0, (r).errors());
namespace cricket {
class WebRtcVideoEngineTest : public ::testing::Test {
public:
WebRtcVideoEngineTest() : WebRtcVideoEngineTest("") {}
explicit WebRtcVideoEngineTest(const std::string& field_trials)
: override_field_trials_(
field_trials.empty()
? nullptr
: std::make_unique<webrtc::test::ScopedFieldTrials>(
field_trials)),
task_queue_factory_(webrtc::CreateDefaultTaskQueueFactory()),
call_(webrtc::Call::Create([&] {
webrtc::Call::Config call_config(&event_log_);
call_config.task_queue_factory = task_queue_factory_.get();
call_config.trials = &field_trials_;
return call_config;
}())),
encoder_factory_(new cricket::FakeWebRtcVideoEncoderFactory),
decoder_factory_(new cricket::FakeWebRtcVideoDecoderFactory),
video_bitrate_allocator_factory_(
webrtc::CreateBuiltinVideoBitrateAllocatorFactory()),
engine_(std::unique_ptr<cricket::FakeWebRtcVideoEncoderFactory>(
encoder_factory_),
std::unique_ptr<cricket::FakeWebRtcVideoDecoderFactory>(
decoder_factory_),
field_trials_) {
// Ensure fake clock doesn't return 0, which will cause some initializations
// fail inside RTP senders.
fake_clock_.AdvanceTime(webrtc::TimeDelta::Micros(1));
}
protected:
void AssignDefaultAptRtxTypes();
void AssignDefaultCodec();
// Find the index of the codec in the engine with the given name. The codec
// must be present.
size_t GetEngineCodecIndex(const std::string& name) const;
// Find the codec in the engine with the given name. The codec must be
// present.
cricket::VideoCodec GetEngineCodec(const std::string& name) const;
void AddSupportedVideoCodecType(const std::string& name);
VideoMediaChannel* SetSendParamsWithAllSupportedCodecs();
VideoMediaChannel* SetRecvParamsWithSupportedCodecs(
const std::vector<VideoCodec>& codecs);
void ExpectRtpCapabilitySupport(const char* uri, bool supported) const;
// Has to be the first one, so it is initialized before the call or there is a
// race condition in the clock access.
rtc::ScopedFakeClock fake_clock_;
std::unique_ptr<webrtc::test::ScopedFieldTrials> override_field_trials_;
webrtc::FieldTrialBasedConfig field_trials_;
webrtc::RtcEventLogNull event_log_;
std::unique_ptr<webrtc::TaskQueueFactory> task_queue_factory_;
// Used in WebRtcVideoEngineVoiceTest, but defined here so it's properly
// initialized when the constructor is called.
std::unique_ptr<webrtc::Call> call_;
cricket::FakeWebRtcVideoEncoderFactory* encoder_factory_;
cricket::FakeWebRtcVideoDecoderFactory* decoder_factory_;
std::unique_ptr<webrtc::VideoBitrateAllocatorFactory>
video_bitrate_allocator_factory_;
WebRtcVideoEngine engine_;
VideoCodec default_codec_;
std::map<int, int> default_apt_rtx_types_;
};
TEST_F(WebRtcVideoEngineTest, DefaultRtxCodecHasAssociatedPayloadTypeSet) {
encoder_factory_->AddSupportedVideoCodecType("VP8");
AssignDefaultCodec();
std::vector<VideoCodec> engine_codecs = engine_.send_codecs();
for (size_t i = 0; i < engine_codecs.size(); ++i) {
if (engine_codecs[i].name != kRtxCodecName)
continue;
int associated_payload_type;
EXPECT_TRUE(engine_codecs[i].GetParam(kCodecParamAssociatedPayloadType,
&associated_payload_type));
EXPECT_EQ(default_codec_.id, associated_payload_type);
return;
}
FAIL() << "No RTX codec found among default codecs.";
}
TEST_F(WebRtcVideoEngineTest, SupportsTimestampOffsetHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kTimestampOffsetUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsAbsoluteSenderTimeHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kAbsSendTimeUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsTransportSequenceNumberHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kTransportSequenceNumberUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsVideoRotationHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoRotationUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsPlayoutDelayHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kPlayoutDelayUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsVideoContentTypeHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoContentTypeUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsVideoTimingHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoTimingUri, true);
}
TEST_F(WebRtcVideoEngineTest, SupportsColorSpaceHeaderExtension) {
ExpectRtpCapabilitySupport(RtpExtension::kColorSpaceUri, true);
}
TEST_F(WebRtcVideoEngineTest, AdvertiseGenericDescriptor00) {
ExpectRtpCapabilitySupport(RtpExtension::kGenericFrameDescriptorUri00, false);
}
class WebRtcVideoEngineTestWithGenericDescriptor
: public WebRtcVideoEngineTest {
public:
WebRtcVideoEngineTestWithGenericDescriptor()
: WebRtcVideoEngineTest("WebRTC-GenericDescriptorAdvertised/Enabled/") {}
};
TEST_F(WebRtcVideoEngineTestWithGenericDescriptor,
AdvertiseGenericDescriptor00) {
ExpectRtpCapabilitySupport(RtpExtension::kGenericFrameDescriptorUri00, true);
}
class WebRtcVideoEngineTestWithDependencyDescriptor
: public WebRtcVideoEngineTest {
public:
WebRtcVideoEngineTestWithDependencyDescriptor()
: WebRtcVideoEngineTest(
"WebRTC-DependencyDescriptorAdvertised/Enabled/") {}
};
TEST_F(WebRtcVideoEngineTestWithDependencyDescriptor,
AdvertiseDependencyDescriptor) {
ExpectRtpCapabilitySupport(RtpExtension::kDependencyDescriptorUri, true);
}
TEST_F(WebRtcVideoEngineTest, AdvertiseVideoLayersAllocation) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoLayersAllocationUri, false);
}
class WebRtcVideoEngineTestWithVideoLayersAllocation
: public WebRtcVideoEngineTest {
public:
WebRtcVideoEngineTestWithVideoLayersAllocation()
: WebRtcVideoEngineTest(
"WebRTC-VideoLayersAllocationAdvertised/Enabled/") {}
};
TEST_F(WebRtcVideoEngineTestWithVideoLayersAllocation,
AdvertiseVideoLayersAllocation) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoLayersAllocationUri, true);
}
class WebRtcVideoFrameTrackingId : public WebRtcVideoEngineTest {
public:
WebRtcVideoFrameTrackingId()
: WebRtcVideoEngineTest(
"WebRTC-VideoFrameTrackingIdAdvertised/Enabled/") {}
};
TEST_F(WebRtcVideoFrameTrackingId, AdvertiseVideoFrameTrackingId) {
ExpectRtpCapabilitySupport(RtpExtension::kVideoFrameTrackingIdUri, true);
}
TEST_F(WebRtcVideoEngineTest, CVOSetHeaderExtensionBeforeCapturer) {
// Allocate the source first to prevent early destruction before channel's
// dtor is called.
::testing::NiceMock<MockVideoSource> video_source;
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
EXPECT_TRUE(channel->AddSendStream(StreamParams::CreateLegacy(kSsrc)));
// Add CVO extension.
const int id = 1;
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
parameters.extensions.push_back(
RtpExtension(RtpExtension::kVideoRotationUri, id));
EXPECT_TRUE(channel->SetSendParameters(parameters));
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, false)));
// Set capturer.
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &video_source));
// Verify capturer has turned off applying rotation.
::testing::Mock::VerifyAndClear(&video_source);
// Verify removing header extension turns on applying rotation.
parameters.extensions.clear();
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, true)));
EXPECT_TRUE(channel->SetSendParameters(parameters));
}
TEST_F(WebRtcVideoEngineTest, CVOSetHeaderExtensionBeforeAddSendStream) {
// Allocate the source first to prevent early destruction before channel's
// dtor is called.
::testing::NiceMock<MockVideoSource> video_source;
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
// Add CVO extension.
const int id = 1;
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
parameters.extensions.push_back(
RtpExtension(RtpExtension::kVideoRotationUri, id));
EXPECT_TRUE(channel->SetSendParameters(parameters));
EXPECT_TRUE(channel->AddSendStream(StreamParams::CreateLegacy(kSsrc)));
// Set source.
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, false)));
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &video_source));
}
TEST_F(WebRtcVideoEngineTest, CVOSetHeaderExtensionAfterCapturer) {
::testing::NiceMock<MockVideoSource> video_source;
AddSupportedVideoCodecType("VP8");
AddSupportedVideoCodecType("VP9");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
EXPECT_TRUE(channel->AddSendStream(StreamParams::CreateLegacy(kSsrc)));
// Set capturer.
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, true)));
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &video_source));
// Verify capturer has turned on applying rotation.
::testing::Mock::VerifyAndClear(&video_source);
// Add CVO extension.
const int id = 1;
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
parameters.codecs.push_back(GetEngineCodec("VP9"));
parameters.extensions.push_back(
RtpExtension(RtpExtension::kVideoRotationUri, id));
// Also remove the first codec to trigger a codec change as well.
parameters.codecs.erase(parameters.codecs.begin());
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, false)));
EXPECT_TRUE(channel->SetSendParameters(parameters));
// Verify capturer has turned off applying rotation.
::testing::Mock::VerifyAndClear(&video_source);
// Verify removing header extension turns on applying rotation.
parameters.extensions.clear();
EXPECT_CALL(
video_source,
AddOrUpdateSink(_, Field(&rtc::VideoSinkWants::rotation_applied, true)));
EXPECT_TRUE(channel->SetSendParameters(parameters));
}
TEST_F(WebRtcVideoEngineTest, SetSendFailsBeforeSettingCodecs) {
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
EXPECT_TRUE(channel->AddSendStream(StreamParams::CreateLegacy(123)));
EXPECT_FALSE(channel->SetSend(true))
<< "Channel should not start without codecs.";
EXPECT_TRUE(channel->SetSend(false))
<< "Channel should be stoppable even without set codecs.";
}
TEST_F(WebRtcVideoEngineTest, GetStatsWithoutSendCodecsSetDoesNotCrash) {
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
EXPECT_TRUE(channel->AddSendStream(StreamParams::CreateLegacy(123)));
VideoMediaInfo info;
channel->GetStats(&info);
}
TEST_F(WebRtcVideoEngineTest, UseFactoryForVp8WhenSupported) {
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
channel->OnReadyToSend(true);
EXPECT_TRUE(
channel->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_EQ(0, encoder_factory_->GetNumCreatedEncoders());
EXPECT_TRUE(channel->SetSend(true));
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
// Sending one frame will have allocate the encoder.
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(1));
EXPECT_TRUE_WAIT(encoder_factory_->encoders()[0]->GetNumEncodedFrames() > 0,
kTimeout);
int num_created_encoders = encoder_factory_->GetNumCreatedEncoders();
EXPECT_EQ(num_created_encoders, 1);
// Setting codecs of the same type should not reallocate any encoders
// (expecting a no-op).
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
EXPECT_TRUE(channel->SetSendParameters(parameters));
EXPECT_EQ(num_created_encoders, encoder_factory_->GetNumCreatedEncoders());
// Remove stream previously added to free the external encoder instance.
EXPECT_TRUE(channel->RemoveSendStream(kSsrc));
EXPECT_EQ(0u, encoder_factory_->encoders().size());
}
// Test that when an encoder factory supports H264, we add an RTX
// codec for it.
// TODO(deadbeef): This test should be updated if/when we start
// adding RTX codecs for unrecognized codec names.
TEST_F(WebRtcVideoEngineTest, RtxCodecAddedForH264Codec) {
using webrtc::H264Level;
using webrtc::H264Profile;
using webrtc::H264ProfileLevelId;
using webrtc::H264ProfileLevelIdToString;
webrtc::SdpVideoFormat h264_constrained_baseline("H264");
h264_constrained_baseline.parameters[kH264FmtpProfileLevelId] =
*H264ProfileLevelIdToString(H264ProfileLevelId(
H264Profile::kProfileConstrainedBaseline, H264Level::kLevel1));
webrtc::SdpVideoFormat h264_constrained_high("H264");
h264_constrained_high.parameters[kH264FmtpProfileLevelId] =
*H264ProfileLevelIdToString(H264ProfileLevelId(
H264Profile::kProfileConstrainedHigh, H264Level::kLevel1));
webrtc::SdpVideoFormat h264_high("H264");
h264_high.parameters[kH264FmtpProfileLevelId] = *H264ProfileLevelIdToString(
H264ProfileLevelId(H264Profile::kProfileHigh, H264Level::kLevel1));
encoder_factory_->AddSupportedVideoCodec(h264_constrained_baseline);
encoder_factory_->AddSupportedVideoCodec(h264_constrained_high);
encoder_factory_->AddSupportedVideoCodec(h264_high);
// First figure out what payload types the test codecs got assigned.
const std::vector<cricket::VideoCodec> codecs = engine_.send_codecs();
// Now search for RTX codecs for them. Expect that they all have associated
// RTX codecs.
EXPECT_TRUE(HasRtxCodec(
codecs,
FindMatchingCodec(codecs, cricket::VideoCodec(h264_constrained_baseline))
->id));
EXPECT_TRUE(HasRtxCodec(
codecs,
FindMatchingCodec(codecs, cricket::VideoCodec(h264_constrained_high))
->id));
EXPECT_TRUE(HasRtxCodec(
codecs, FindMatchingCodec(codecs, cricket::VideoCodec(h264_high))->id));
}
#if defined(RTC_ENABLE_VP9)
TEST_F(WebRtcVideoEngineTest, CanConstructDecoderForVp9EncoderFactory) {
AddSupportedVideoCodecType("VP9");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
EXPECT_TRUE(
channel->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
}
#endif // defined(RTC_ENABLE_VP9)
TEST_F(WebRtcVideoEngineTest, PropagatesInputFrameTimestamp) {
AddSupportedVideoCodecType("VP8");
FakeCall* fake_call = new FakeCall();
call_.reset(fake_call);
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
EXPECT_TRUE(
channel->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 60);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &frame_forwarder));
channel->SetSend(true);
FakeVideoSendStream* stream = fake_call->GetVideoSendStreams()[0];
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
int64_t last_timestamp = stream->GetLastTimestamp();
for (int i = 0; i < 10; i++) {
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
int64_t timestamp = stream->GetLastTimestamp();
int64_t interval = timestamp - last_timestamp;
// Precision changes from nanosecond to millisecond.
// Allow error to be no more than 1.
EXPECT_NEAR(cricket::VideoFormat::FpsToInterval(60) / 1E6, interval, 1);
last_timestamp = timestamp;
}
frame_forwarder.IncomingCapturedFrame(
frame_source.GetFrame(1280, 720, webrtc::VideoRotation::kVideoRotation_0,
rtc::kNumMicrosecsPerSec / 30));
last_timestamp = stream->GetLastTimestamp();
for (int i = 0; i < 10; i++) {
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame(
1280, 720, webrtc::VideoRotation::kVideoRotation_0,
rtc::kNumMicrosecsPerSec / 30));
int64_t timestamp = stream->GetLastTimestamp();
int64_t interval = timestamp - last_timestamp;
// Precision changes from nanosecond to millisecond.
// Allow error to be no more than 1.
EXPECT_NEAR(cricket::VideoFormat::FpsToInterval(30) / 1E6, interval, 1);
last_timestamp = timestamp;
}
// Remove stream previously added to free the external encoder instance.
EXPECT_TRUE(channel->RemoveSendStream(kSsrc));
}
void WebRtcVideoEngineTest::AssignDefaultAptRtxTypes() {
std::vector<VideoCodec> engine_codecs = engine_.send_codecs();
RTC_DCHECK(!engine_codecs.empty());
for (const cricket::VideoCodec& codec : engine_codecs) {
if (codec.name == "rtx") {
int associated_payload_type;
if (codec.GetParam(kCodecParamAssociatedPayloadType,
&associated_payload_type)) {
default_apt_rtx_types_[associated_payload_type] = codec.id;
}
}
}
}
void WebRtcVideoEngineTest::AssignDefaultCodec() {
std::vector<VideoCodec> engine_codecs = engine_.send_codecs();
RTC_DCHECK(!engine_codecs.empty());
bool codec_set = false;
for (const cricket::VideoCodec& codec : engine_codecs) {
if (!codec_set && codec.name != "rtx" && codec.name != "red" &&
codec.name != "ulpfec" && codec.name != "flexfec-03") {
default_codec_ = codec;
codec_set = true;
}
}
RTC_DCHECK(codec_set);
}
size_t WebRtcVideoEngineTest::GetEngineCodecIndex(
const std::string& name) const {
const std::vector<cricket::VideoCodec> codecs = engine_.send_codecs();
for (size_t i = 0; i < codecs.size(); ++i) {
const cricket::VideoCodec engine_codec = codecs[i];
if (!absl::EqualsIgnoreCase(name, engine_codec.name))
continue;
// The tests only use H264 Constrained Baseline. Make sure we don't return
// an internal H264 codec from the engine with a different H264 profile.
if (absl::EqualsIgnoreCase(name.c_str(), kH264CodecName)) {
const absl::optional<webrtc::H264ProfileLevelId> profile_level_id =
webrtc::ParseSdpForH264ProfileLevelId(engine_codec.params);
if (profile_level_id->profile !=
webrtc::H264Profile::kProfileConstrainedBaseline) {
continue;
}
}
return i;
}
// This point should never be reached.
ADD_FAILURE() << "Unrecognized codec name: " << name;
return -1;
}
cricket::VideoCodec WebRtcVideoEngineTest::GetEngineCodec(
const std::string& name) const {
return engine_.send_codecs()[GetEngineCodecIndex(name)];
}
void WebRtcVideoEngineTest::AddSupportedVideoCodecType(
const std::string& name) {
encoder_factory_->AddSupportedVideoCodecType(name);
decoder_factory_->AddSupportedVideoCodecType(name);
}
VideoMediaChannel*
WebRtcVideoEngineTest::SetSendParamsWithAllSupportedCodecs() {
VideoMediaChannel* channel = engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get());
cricket::VideoSendParameters parameters;
// We need to look up the codec in the engine to get the correct payload type.
for (const webrtc::SdpVideoFormat& format :
encoder_factory_->GetSupportedFormats()) {
cricket::VideoCodec engine_codec = GetEngineCodec(format.name);
if (!absl::c_linear_search(parameters.codecs, engine_codec)) {
parameters.codecs.push_back(engine_codec);
}
}
EXPECT_TRUE(channel->SetSendParameters(parameters));
return channel;
}
VideoMediaChannel* WebRtcVideoEngineTest::SetRecvParamsWithSupportedCodecs(
const std::vector<VideoCodec>& codecs) {
VideoMediaChannel* channel = engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get());
cricket::VideoRecvParameters parameters;
parameters.codecs = codecs;
EXPECT_TRUE(channel->SetRecvParameters(parameters));
return channel;
}
void WebRtcVideoEngineTest::ExpectRtpCapabilitySupport(const char* uri,
bool supported) const {
const std::vector<webrtc::RtpExtension> header_extensions =
GetDefaultEnabledRtpHeaderExtensions(engine_);
if (supported) {
EXPECT_THAT(header_extensions, Contains(Field(&RtpExtension::uri, uri)));
} else {
EXPECT_THAT(header_extensions, Each(Field(&RtpExtension::uri, StrNe(uri))));
}
}
TEST_F(WebRtcVideoEngineTest, UsesSimulcastAdapterForVp8Factories) {
AddSupportedVideoCodecType("VP8");
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
std::vector<uint32_t> ssrcs = MAKE_VECTOR(kSsrcs3);
EXPECT_TRUE(channel->AddSendStream(CreateSimStreamParams("cname", ssrcs)));
EXPECT_TRUE(channel->SetSend(true));
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 60);
EXPECT_TRUE(channel->SetVideoSend(ssrcs.front(), nullptr, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(2));
// Verify that encoders are configured for simulcast through adapter
// (increasing resolution and only configured to send one stream each).
int prev_width = -1;
for (size_t i = 0; i < encoder_factory_->encoders().size(); ++i) {
ASSERT_TRUE(encoder_factory_->encoders()[i]->WaitForInitEncode());
webrtc::VideoCodec codec_settings =
encoder_factory_->encoders()[i]->GetCodecSettings();
EXPECT_EQ(0, codec_settings.numberOfSimulcastStreams);
EXPECT_GT(codec_settings.width, prev_width);
prev_width = codec_settings.width;
}
EXPECT_TRUE(channel->SetVideoSend(ssrcs.front(), nullptr, nullptr));
channel.reset();
ASSERT_EQ(0u, encoder_factory_->encoders().size());
}
TEST_F(WebRtcVideoEngineTest, ChannelWithH264CanChangeToVp8) {
AddSupportedVideoCodecType("VP8");
AddSupportedVideoCodecType("H264");
// Frame source.
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("H264"));
EXPECT_TRUE(channel->SetSendParameters(parameters));
EXPECT_TRUE(
channel->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &frame_forwarder));
// Sending one frame will have allocate the encoder.
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_EQ_WAIT(1u, encoder_factory_->encoders().size(), kTimeout);
cricket::VideoSendParameters new_parameters;
new_parameters.codecs.push_back(GetEngineCodec("VP8"));
EXPECT_TRUE(channel->SetSendParameters(new_parameters));
// Sending one frame will switch encoder.
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
EXPECT_EQ_WAIT(1u, encoder_factory_->encoders().size(), kTimeout);
}
TEST_F(WebRtcVideoEngineTest,
UsesSimulcastAdapterForVp8WithCombinedVP8AndH264Factory) {
AddSupportedVideoCodecType("VP8");
AddSupportedVideoCodecType("H264");
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
EXPECT_TRUE(channel->SetSendParameters(parameters));
std::vector<uint32_t> ssrcs = MAKE_VECTOR(kSsrcs3);
EXPECT_TRUE(channel->AddSendStream(CreateSimStreamParams("cname", ssrcs)));
EXPECT_TRUE(channel->SetSend(true));
// Send a fake frame, or else the media engine will configure the simulcast
// encoder adapter at a low-enough size that it'll only create a single
// encoder layer.
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
EXPECT_TRUE(channel->SetVideoSend(ssrcs.front(), nullptr, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(2));
ASSERT_TRUE(encoder_factory_->encoders()[0]->WaitForInitEncode());
EXPECT_EQ(webrtc::kVideoCodecVP8,
encoder_factory_->encoders()[0]->GetCodecSettings().codecType);
channel.reset();
// Make sure DestroyVideoEncoder was called on the factory.
EXPECT_EQ(0u, encoder_factory_->encoders().size());
}
TEST_F(WebRtcVideoEngineTest,
DestroysNonSimulcastEncoderFromCombinedVP8AndH264Factory) {
AddSupportedVideoCodecType("VP8");
AddSupportedVideoCodecType("H264");
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("H264"));
EXPECT_TRUE(channel->SetSendParameters(parameters));
EXPECT_TRUE(
channel->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
// Send a frame of 720p. This should trigger a "real" encoder initialization.
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, nullptr, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(1));
ASSERT_EQ(1u, encoder_factory_->encoders().size());
ASSERT_TRUE(encoder_factory_->encoders()[0]->WaitForInitEncode());
EXPECT_EQ(webrtc::kVideoCodecH264,
encoder_factory_->encoders()[0]->GetCodecSettings().codecType);
channel.reset();
// Make sure DestroyVideoEncoder was called on the factory.
ASSERT_EQ(0u, encoder_factory_->encoders().size());
}
TEST_F(WebRtcVideoEngineTest, SimulcastEnabledForH264BehindFieldTrial) {
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-H264Simulcast/Enabled/");
AddSupportedVideoCodecType("H264");
std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("H264"));
EXPECT_TRUE(channel->SetSendParameters(parameters));
const std::vector<uint32_t> ssrcs = MAKE_VECTOR(kSsrcs3);
EXPECT_TRUE(
channel->AddSendStream(cricket::CreateSimStreamParams("cname", ssrcs)));
// Send a frame of 720p. This should trigger a "real" encoder initialization.
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
EXPECT_TRUE(channel->SetVideoSend(ssrcs[0], nullptr, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(1));
ASSERT_EQ(1u, encoder_factory_->encoders().size());
FakeWebRtcVideoEncoder* encoder = encoder_factory_->encoders()[0];
ASSERT_TRUE(encoder_factory_->encoders()[0]->WaitForInitEncode());
EXPECT_EQ(webrtc::kVideoCodecH264, encoder->GetCodecSettings().codecType);
EXPECT_LT(1u, encoder->GetCodecSettings().numberOfSimulcastStreams);
EXPECT_TRUE(channel->SetVideoSend(ssrcs[0], nullptr, nullptr));
}
// Test that FlexFEC is not supported as a send video codec by default.
// Only enabling field trial should allow advertising FlexFEC send codec.
TEST_F(WebRtcVideoEngineTest, Flexfec03SendCodecEnablesWithFieldTrial) {
encoder_factory_->AddSupportedVideoCodecType("VP8");
auto flexfec = Field("name", &VideoCodec::name, "flexfec-03");
EXPECT_THAT(engine_.send_codecs(), Not(Contains(flexfec)));
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-FlexFEC-03-Advertised/Enabled/");
EXPECT_THAT(engine_.send_codecs(), Contains(flexfec));
}
// Test that FlexFEC is supported as a receive video codec by default.
// Disabling field trial should prevent advertising FlexFEC receive codec.
TEST_F(WebRtcVideoEngineTest, Flexfec03ReceiveCodecDisablesWithFieldTrial) {
decoder_factory_->AddSupportedVideoCodecType("VP8");
auto flexfec = Field("name", &VideoCodec::name, "flexfec-03");
EXPECT_THAT(engine_.recv_codecs(), Contains(flexfec));
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-FlexFEC-03-Advertised/Disabled/");
EXPECT_THAT(engine_.recv_codecs(), Not(Contains(flexfec)));
}
// Test that the FlexFEC "codec" gets assigned to the lower payload type range
TEST_F(WebRtcVideoEngineTest, Flexfec03LowerPayloadTypeRange) {
encoder_factory_->AddSupportedVideoCodecType("VP8");
auto flexfec = Field("name", &VideoCodec::name, "flexfec-03");
// FlexFEC is active with field trial.
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-FlexFEC-03-Advertised/Enabled/");
auto send_codecs = engine_.send_codecs();
auto it = std::find_if(send_codecs.begin(), send_codecs.end(),
[](const cricket::VideoCodec& codec) {
return codec.name == "flexfec-03";
});
ASSERT_NE(it, send_codecs.end());
EXPECT_LE(35, it->id);
EXPECT_GE(65, it->id);
}
// Test that codecs are added in the order they are reported from the factory.
TEST_F(WebRtcVideoEngineTest, ReportSupportedCodecs) {
encoder_factory_->AddSupportedVideoCodecType("VP8");
const char* kFakeCodecName = "FakeCodec";
encoder_factory_->AddSupportedVideoCodecType(kFakeCodecName);
// The last reported codec should appear after the first codec in the vector.
const size_t vp8_index = GetEngineCodecIndex("VP8");
const size_t fake_codec_index = GetEngineCodecIndex(kFakeCodecName);
EXPECT_LT(vp8_index, fake_codec_index);
}
// Test that a codec that was added after the engine was initialized
// does show up in the codec list after it was added.
TEST_F(WebRtcVideoEngineTest, ReportSupportedAddedCodec) {
const char* kFakeExternalCodecName1 = "FakeExternalCodec1";
const char* kFakeExternalCodecName2 = "FakeExternalCodec2";
// Set up external encoder factory with first codec, and initialize engine.
encoder_factory_->AddSupportedVideoCodecType(kFakeExternalCodecName1);
std::vector<cricket::VideoCodec> codecs_before(engine_.send_codecs());
// Add second codec.
encoder_factory_->AddSupportedVideoCodecType(kFakeExternalCodecName2);
std::vector<cricket::VideoCodec> codecs_after(engine_.send_codecs());
// The codec itself and RTX should have been added.
EXPECT_EQ(codecs_before.size() + 2, codecs_after.size());
// Check that both fake codecs are present and that the second fake codec
// appears after the first fake codec.
const size_t fake_codec_index1 = GetEngineCodecIndex(kFakeExternalCodecName1);
const size_t fake_codec_index2 = GetEngineCodecIndex(kFakeExternalCodecName2);
EXPECT_LT(fake_codec_index1, fake_codec_index2);
}
TEST_F(WebRtcVideoEngineTest, ReportRtxForExternalCodec) {
const char* kFakeCodecName = "FakeCodec";
encoder_factory_->AddSupportedVideoCodecType(kFakeCodecName);
const size_t fake_codec_index = GetEngineCodecIndex(kFakeCodecName);
EXPECT_EQ("rtx", engine_.send_codecs().at(fake_codec_index + 1).name);
}
TEST_F(WebRtcVideoEngineTest, RegisterDecodersIfSupported) {
AddSupportedVideoCodecType("VP8");
cricket::VideoRecvParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
std::unique_ptr<VideoMediaChannel> channel(
SetRecvParamsWithSupportedCodecs(parameters.codecs));
EXPECT_TRUE(
channel->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
ASSERT_EQ(1u, decoder_factory_->decoders().size());
// Setting codecs of the same type should not reallocate the decoder.
EXPECT_TRUE(channel->SetRecvParameters(parameters));
EXPECT_EQ(1, decoder_factory_->GetNumCreatedDecoders());
// Remove stream previously added to free the external decoder instance.
EXPECT_TRUE(channel->RemoveRecvStream(kSsrc));
EXPECT_EQ(0u, decoder_factory_->decoders().size());
}
// Verifies that we can set up decoders.
TEST_F(WebRtcVideoEngineTest, RegisterH264DecoderIfSupported) {
// TODO(pbos): Do not assume that encoder/decoder support is symmetric. We
// can't even query the WebRtcVideoDecoderFactory for supported codecs.
// For now we add a FakeWebRtcVideoEncoderFactory to add H264 to supported
// codecs.
AddSupportedVideoCodecType("H264");
std::vector<cricket::VideoCodec> codecs;
codecs.push_back(GetEngineCodec("H264"));
std::unique_ptr<VideoMediaChannel> channel(
SetRecvParamsWithSupportedCodecs(codecs));
EXPECT_TRUE(
channel->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
ASSERT_EQ(1u, decoder_factory_->decoders().size());
}
// Tests when GetSources is called with non-existing ssrc, it will return an
// empty list of RtpSource without crashing.
TEST_F(WebRtcVideoEngineTest, GetSourcesWithNonExistingSsrc) {
// Setup an recv stream with `kSsrc`.
AddSupportedVideoCodecType("VP8");
cricket::VideoRecvParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
std::unique_ptr<VideoMediaChannel> channel(
SetRecvParamsWithSupportedCodecs(parameters.codecs));
EXPECT_TRUE(
channel->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
// Call GetSources with |kSsrc + 1| which doesn't exist.
std::vector<webrtc::RtpSource> sources = channel->GetSources(kSsrc + 1);
EXPECT_EQ(0u, sources.size());
}
TEST(WebRtcVideoEngineNewVideoCodecFactoryTest, NullFactories) {
std::unique_ptr<webrtc::VideoEncoderFactory> encoder_factory;
std::unique_ptr<webrtc::VideoDecoderFactory> decoder_factory;
webrtc::FieldTrialBasedConfig trials;
WebRtcVideoEngine engine(std::move(encoder_factory),
std::move(decoder_factory), trials);
EXPECT_EQ(0u, engine.send_codecs().size());
EXPECT_EQ(0u, engine.recv_codecs().size());
}
TEST(WebRtcVideoEngineNewVideoCodecFactoryTest, EmptyFactories) {
// `engine` take ownership of the factories.
webrtc::MockVideoEncoderFactory* encoder_factory =
new webrtc::MockVideoEncoderFactory();
webrtc::MockVideoDecoderFactory* decoder_factory =
new webrtc::MockVideoDecoderFactory();
webrtc::FieldTrialBasedConfig trials;
WebRtcVideoEngine engine(
(std::unique_ptr<webrtc::VideoEncoderFactory>(encoder_factory)),
(std::unique_ptr<webrtc::VideoDecoderFactory>(decoder_factory)), trials);
// TODO(kron): Change to Times(1) once send and receive codecs are changed
// to be treated independently.
EXPECT_CALL(*encoder_factory, GetSupportedFormats()).Times(1);
EXPECT_EQ(0u, engine.send_codecs().size());
EXPECT_EQ(0u, engine.recv_codecs().size());
EXPECT_CALL(*encoder_factory, Die());
EXPECT_CALL(*decoder_factory, Die());
}
// Test full behavior in the video engine when video codec factories of the new
// type are injected supporting the single codec Vp8. Check the returned codecs
// from the engine and that we will create a Vp8 encoder and decoder using the
// new factories.
TEST(WebRtcVideoEngineNewVideoCodecFactoryTest, Vp8) {
// `engine` take ownership of the factories.
webrtc::MockVideoEncoderFactory* encoder_factory =
new webrtc::MockVideoEncoderFactory();
webrtc::MockVideoDecoderFactory* decoder_factory =
new webrtc::MockVideoDecoderFactory();
std::unique_ptr<webrtc::MockVideoBitrateAllocatorFactory>
rate_allocator_factory =
std::make_unique<webrtc::MockVideoBitrateAllocatorFactory>();
EXPECT_CALL(*rate_allocator_factory,
CreateVideoBitrateAllocator(Field(&webrtc::VideoCodec::codecType,
webrtc::kVideoCodecVP8)))
.WillOnce(
[] { return std::make_unique<webrtc::MockVideoBitrateAllocator>(); });
webrtc::FieldTrialBasedConfig trials;
WebRtcVideoEngine engine(
(std::unique_ptr<webrtc::VideoEncoderFactory>(encoder_factory)),
(std::unique_ptr<webrtc::VideoDecoderFactory>(decoder_factory)), trials);
const webrtc::SdpVideoFormat vp8_format("VP8");
const std::vector<webrtc::SdpVideoFormat> supported_formats = {vp8_format};
EXPECT_CALL(*encoder_factory, GetSupportedFormats())
.WillRepeatedly(Return(supported_formats));
EXPECT_CALL(*decoder_factory, GetSupportedFormats())
.WillRepeatedly(Return(supported_formats));
// Verify the codecs from the engine.
const std::vector<VideoCodec> engine_codecs = engine.send_codecs();
// Verify default codecs has been added correctly.
EXPECT_EQ(5u, engine_codecs.size());
EXPECT_EQ("VP8", engine_codecs.at(0).name);
// RTX codec for VP8.
EXPECT_EQ("rtx", engine_codecs.at(1).name);
int vp8_associated_payload;
EXPECT_TRUE(engine_codecs.at(1).GetParam(kCodecParamAssociatedPayloadType,
&vp8_associated_payload));
EXPECT_EQ(vp8_associated_payload, engine_codecs.at(0).id);
EXPECT_EQ(kRedCodecName, engine_codecs.at(2).name);
// RTX codec for RED.
EXPECT_EQ("rtx", engine_codecs.at(3).name);
int red_associated_payload;
EXPECT_TRUE(engine_codecs.at(3).GetParam(kCodecParamAssociatedPayloadType,
&red_associated_payload));
EXPECT_EQ(red_associated_payload, engine_codecs.at(2).id);
EXPECT_EQ(kUlpfecCodecName, engine_codecs.at(4).name);
int associated_payload_type;
EXPECT_TRUE(engine_codecs.at(1).GetParam(
cricket::kCodecParamAssociatedPayloadType, &associated_payload_type));
EXPECT_EQ(engine_codecs.at(0).id, associated_payload_type);
// Verify default parameters has been added to the VP8 codec.
VerifyCodecHasDefaultFeedbackParams(engine_codecs.at(0),
/*lntf_expected=*/false);
// Mock encoder creation. `engine` take ownership of the encoder.
webrtc::VideoEncoderFactory::CodecInfo codec_info;
codec_info.has_internal_source = false;
const webrtc::SdpVideoFormat format("VP8");
EXPECT_CALL(*encoder_factory, QueryVideoEncoder(format))
.WillRepeatedly(Return(codec_info));
rtc::Event encoder_created;
EXPECT_CALL(*encoder_factory, CreateVideoEncoder(format)).WillOnce([&] {
encoder_created.Set();
return std::make_unique<FakeWebRtcVideoEncoder>(nullptr);
});
// Mock decoder creation. `engine` take ownership of the decoder.
EXPECT_CALL(*decoder_factory, CreateVideoDecoder(format)).WillOnce([] {
return std::make_unique<FakeWebRtcVideoDecoder>(nullptr);
});
// Create a call.
webrtc::RtcEventLogNull event_log;
auto task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
webrtc::Call::Config call_config(&event_log);
webrtc::FieldTrialBasedConfig field_trials;
call_config.trials = &field_trials;
call_config.task_queue_factory = task_queue_factory.get();
const auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
// Create send channel.
const int send_ssrc = 123;
std::unique_ptr<VideoMediaChannel> send_channel(engine.CreateMediaChannel(
call.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
rate_allocator_factory.get()));
cricket::VideoSendParameters send_parameters;
send_parameters.codecs.push_back(engine_codecs.at(0));
EXPECT_TRUE(send_channel->SetSendParameters(send_parameters));
send_channel->OnReadyToSend(true);
EXPECT_TRUE(
send_channel->AddSendStream(StreamParams::CreateLegacy(send_ssrc)));
EXPECT_TRUE(send_channel->SetSend(true));
// Set capturer.
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
EXPECT_TRUE(send_channel->SetVideoSend(send_ssrc, nullptr, &frame_forwarder));
// Sending one frame will allocate the encoder.
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
encoder_created.Wait(kTimeout);
// Create recv channel.
const int recv_ssrc = 321;
std::unique_ptr<VideoMediaChannel> recv_channel(engine.CreateMediaChannel(
call.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
rate_allocator_factory.get()));
cricket::VideoRecvParameters recv_parameters;
recv_parameters.codecs.push_back(engine_codecs.at(0));
EXPECT_TRUE(recv_channel->SetRecvParameters(recv_parameters));
EXPECT_TRUE(recv_channel->AddRecvStream(
cricket::StreamParams::CreateLegacy(recv_ssrc)));
// Remove streams previously added to free the encoder and decoder instance.
EXPECT_CALL(*encoder_factory, Die());
EXPECT_CALL(*decoder_factory, Die());
EXPECT_CALL(*rate_allocator_factory, Die());
EXPECT_TRUE(send_channel->RemoveSendStream(send_ssrc));
EXPECT_TRUE(recv_channel->RemoveRecvStream(recv_ssrc));
}
// Test behavior when decoder factory fails to create a decoder (returns null).
TEST(WebRtcVideoEngineNewVideoCodecFactoryTest, NullDecoder) {
// `engine` take ownership of the factories.
webrtc::MockVideoEncoderFactory* encoder_factory =
new webrtc::MockVideoEncoderFactory();
webrtc::MockVideoDecoderFactory* decoder_factory =
new webrtc::MockVideoDecoderFactory();
std::unique_ptr<webrtc::MockVideoBitrateAllocatorFactory>
rate_allocator_factory =
std::make_unique<webrtc::MockVideoBitrateAllocatorFactory>();
webrtc::FieldTrialBasedConfig trials;
WebRtcVideoEngine engine(
(std::unique_ptr<webrtc::VideoEncoderFactory>(encoder_factory)),
(std::unique_ptr<webrtc::VideoDecoderFactory>(decoder_factory)), trials);
const webrtc::SdpVideoFormat vp8_format("VP8");
const std::vector<webrtc::SdpVideoFormat> supported_formats = {vp8_format};
EXPECT_CALL(*encoder_factory, GetSupportedFormats())
.WillRepeatedly(Return(supported_formats));
// Decoder creation fails.
EXPECT_CALL(*decoder_factory, CreateVideoDecoder).WillOnce([] {
return nullptr;
});
// Create a call.
webrtc::RtcEventLogNull event_log;
auto task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
webrtc::Call::Config call_config(&event_log);
webrtc::FieldTrialBasedConfig field_trials;
call_config.trials = &field_trials;
call_config.task_queue_factory = task_queue_factory.get();
const auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
// Create recv channel.
EXPECT_CALL(*decoder_factory, GetSupportedFormats())
.WillRepeatedly(::testing::Return(supported_formats));
const int recv_ssrc = 321;
std::unique_ptr<VideoMediaChannel> recv_channel(engine.CreateMediaChannel(
call.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
rate_allocator_factory.get()));
cricket::VideoRecvParameters recv_parameters;
recv_parameters.codecs.push_back(engine.recv_codecs().front());
EXPECT_TRUE(recv_channel->SetRecvParameters(recv_parameters));
EXPECT_TRUE(recv_channel->AddRecvStream(
cricket::StreamParams::CreateLegacy(recv_ssrc)));
// Remove streams previously added to free the encoder and decoder instance.
EXPECT_TRUE(recv_channel->RemoveRecvStream(recv_ssrc));
}
TEST_F(WebRtcVideoEngineTest, DISABLED_RecreatesEncoderOnContentTypeChange) {
encoder_factory_->AddSupportedVideoCodecType("VP8");
std::unique_ptr<FakeCall> fake_call(new FakeCall());
std::unique_ptr<VideoMediaChannel> channel(
SetSendParamsWithAllSupportedCodecs());
ASSERT_TRUE(
channel->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
cricket::VideoCodec codec = GetEngineCodec("VP8");
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(codec);
channel->OnReadyToSend(true);
channel->SetSend(true);
ASSERT_TRUE(channel->SetSendParameters(parameters));
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(1280, 720,
rtc::kNumMicrosecsPerSec / 30);
VideoOptions options;
EXPECT_TRUE(channel->SetVideoSend(kSsrc, &options, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(1));
EXPECT_EQ(webrtc::VideoCodecMode::kRealtimeVideo,
encoder_factory_->encoders().back()->GetCodecSettings().mode);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, &options, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
// No change in content type, keep current encoder.
EXPECT_EQ(1, encoder_factory_->GetNumCreatedEncoders());
options.is_screencast.emplace(true);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, &options, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
// Change to screen content, recreate encoder. For the simulcast encoder
// adapter case, this will result in two calls since InitEncode triggers a
// a new instance.
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(2));
EXPECT_EQ(webrtc::VideoCodecMode::kScreensharing,
encoder_factory_->encoders().back()->GetCodecSettings().mode);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, &options, &frame_forwarder));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
// Still screen content, no need to update encoder.
EXPECT_EQ(2, encoder_factory_->GetNumCreatedEncoders());
options.is_screencast.emplace(false);
options.video_noise_reduction.emplace(false);
EXPECT_TRUE(channel->SetVideoSend(kSsrc, &options, &frame_forwarder));
// Change back to regular video content, update encoder. Also change
// a non `is_screencast` option just to verify it doesn't affect recreation.
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
ASSERT_TRUE(encoder_factory_->WaitForCreatedVideoEncoders(3));
EXPECT_EQ(webrtc::VideoCodecMode::kRealtimeVideo,
encoder_factory_->encoders().back()->GetCodecSettings().mode);
// Remove stream previously added to free the external encoder instance.
EXPECT_TRUE(channel->RemoveSendStream(kSsrc));
EXPECT_EQ(0u, encoder_factory_->encoders().size());
}
class WebRtcVideoChannelEncodedFrameCallbackTest : public ::testing::Test {
protected:
webrtc::Call::Config GetCallConfig(
webrtc::RtcEventLogNull* event_log,
webrtc::TaskQueueFactory* task_queue_factory) {
webrtc::Call::Config call_config(event_log);
call_config.task_queue_factory = task_queue_factory;
call_config.trials = &field_trials_;
return call_config;
}
WebRtcVideoChannelEncodedFrameCallbackTest()
: task_queue_factory_(webrtc::CreateDefaultTaskQueueFactory()),
call_(absl::WrapUnique(webrtc::Call::Create(
GetCallConfig(&event_log_, task_queue_factory_.get())))),
video_bitrate_allocator_factory_(
webrtc::CreateBuiltinVideoBitrateAllocatorFactory()),
engine_(
webrtc::CreateBuiltinVideoEncoderFactory(),
std::make_unique<webrtc::test::FunctionVideoDecoderFactory>(
[]() { return std::make_unique<webrtc::test::FakeDecoder>(); },
kSdpVideoFormats),
field_trials_),
channel_(absl::WrapUnique(static_cast<cricket::WebRtcVideoChannel*>(
engine_.CreateMediaChannel(
call_.get(),
cricket::MediaConfig(),
cricket::VideoOptions(),
webrtc::CryptoOptions(),
video_bitrate_allocator_factory_.get())))) {
network_interface_.SetDestination(channel_.get());
channel_->SetInterface(&network_interface_);
cricket::VideoRecvParameters parameters;
parameters.codecs = engine_.recv_codecs();
channel_->SetRecvParameters(parameters);
}
~WebRtcVideoChannelEncodedFrameCallbackTest() override {
channel_->SetInterface(nullptr);
}
void DeliverKeyFrame(uint32_t ssrc) {
RtpPacket packet;
packet.SetMarker(true);
packet.SetPayloadType(96); // VP8
packet.SetSsrc(ssrc);
// VP8 Keyframe + 1 byte payload
uint8_t* buf_ptr = packet.AllocatePayload(11);
memset(buf_ptr, 0, 11); // Pass MSAN (don't care about bytes 1-9)
buf_ptr[0] = 0x10; // Partition ID 0 + beginning of partition.
constexpr unsigned width = 1080;
constexpr unsigned height = 720;
buf_ptr[6] = width & 255;
buf_ptr[7] = width >> 8;
buf_ptr[8] = height & 255;
buf_ptr[9] = height >> 8;
call_->Receiver()->DeliverPacket(webrtc::MediaType::VIDEO, packet.Buffer(),
/*packet_time_us=*/0);
}
void DeliverKeyFrameAndWait(uint32_t ssrc) {
DeliverKeyFrame(ssrc);
EXPECT_EQ_WAIT(1, renderer_.num_rendered_frames(), kTimeout);
EXPECT_EQ(0, renderer_.errors());
}
static const std::vector<webrtc::SdpVideoFormat> kSdpVideoFormats;
webrtc::FieldTrialBasedConfig field_trials_;
webrtc::RtcEventLogNull event_log_;
std::unique_ptr<webrtc::TaskQueueFactory> task_queue_factory_;
std::unique_ptr<webrtc::Call> call_;
std::unique_ptr<webrtc::VideoBitrateAllocatorFactory>
video_bitrate_allocator_factory_;
WebRtcVideoEngine engine_;
std::unique_ptr<WebRtcVideoChannel> channel_;
cricket::FakeNetworkInterface network_interface_;
cricket::FakeVideoRenderer renderer_;
};
const std::vector<webrtc::SdpVideoFormat>
WebRtcVideoChannelEncodedFrameCallbackTest::kSdpVideoFormats = {
webrtc::SdpVideoFormat("VP8")};
TEST_F(WebRtcVideoChannelEncodedFrameCallbackTest,
SetEncodedFrameBufferFunction_DefaultStream) {
testing::MockFunction<void(const webrtc::RecordableEncodedFrame&)> callback;
EXPECT_CALL(callback, Call);
EXPECT_TRUE(channel_->AddRecvStream(
cricket::StreamParams::CreateLegacy(kSsrc), /*is_default_stream=*/true));
channel_->SetRecordableEncodedFrameCallback(/*ssrc=*/0,
callback.AsStdFunction());
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
DeliverKeyFrame(kSsrc);
EXPECT_EQ_WAIT(1, renderer_.num_rendered_frames(), kTimeout);
EXPECT_EQ(0, renderer_.errors());
channel_->RemoveRecvStream(kSsrc);
}
TEST_F(WebRtcVideoChannelEncodedFrameCallbackTest,
SetEncodedFrameBufferFunction_MatchSsrcWithDefaultStream) {
testing::MockFunction<void(const webrtc::RecordableEncodedFrame&)> callback;
EXPECT_CALL(callback, Call);
EXPECT_TRUE(channel_->AddRecvStream(
cricket::StreamParams::CreateLegacy(kSsrc), /*is_default_stream=*/true));
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
channel_->SetRecordableEncodedFrameCallback(kSsrc, callback.AsStdFunction());
DeliverKeyFrame(kSsrc);
EXPECT_EQ_WAIT(1, renderer_.num_rendered_frames(), kTimeout);
EXPECT_EQ(0, renderer_.errors());
channel_->RemoveRecvStream(kSsrc);
}
TEST_F(WebRtcVideoChannelEncodedFrameCallbackTest,
SetEncodedFrameBufferFunction_MatchSsrc) {
testing::MockFunction<void(const webrtc::RecordableEncodedFrame&)> callback;
EXPECT_CALL(callback, Call);
EXPECT_TRUE(channel_->AddRecvStream(
cricket::StreamParams::CreateLegacy(kSsrc), /*is_default_stream=*/false));
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
channel_->SetRecordableEncodedFrameCallback(kSsrc, callback.AsStdFunction());
DeliverKeyFrame(kSsrc);
EXPECT_EQ_WAIT(1, renderer_.num_rendered_frames(), kTimeout);
EXPECT_EQ(0, renderer_.errors());
channel_->RemoveRecvStream(kSsrc);
}
TEST_F(WebRtcVideoChannelEncodedFrameCallbackTest,
SetEncodedFrameBufferFunction_MismatchSsrc) {
testing::StrictMock<
testing::MockFunction<void(const webrtc::RecordableEncodedFrame&)>>
callback;
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc + 1),
/*is_default_stream=*/false));
EXPECT_TRUE(channel_->SetSink(kSsrc + 1, &renderer_));
channel_->SetRecordableEncodedFrameCallback(kSsrc, callback.AsStdFunction());
DeliverKeyFrame(kSsrc); // Expected to not cause function to fire.
DeliverKeyFrameAndWait(kSsrc + 1);
channel_->RemoveRecvStream(kSsrc + 1);
}
TEST_F(WebRtcVideoChannelEncodedFrameCallbackTest,
SetEncodedFrameBufferFunction_MismatchSsrcWithDefaultStream) {
testing::StrictMock<
testing::MockFunction<void(const webrtc::RecordableEncodedFrame&)>>
callback;
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc + 1),
/*is_default_stream=*/true));
EXPECT_TRUE(channel_->SetSink(kSsrc + 1, &renderer_));
channel_->SetRecordableEncodedFrameCallback(kSsrc, callback.AsStdFunction());
DeliverKeyFrame(kSsrc); // Expected to not cause function to fire.
DeliverKeyFrameAndWait(kSsrc + 1);
channel_->RemoveRecvStream(kSsrc + 1);
}
class WebRtcVideoChannelBaseTest : public ::testing::Test {
protected:
WebRtcVideoChannelBaseTest()
: task_queue_factory_(webrtc::CreateDefaultTaskQueueFactory()),
video_bitrate_allocator_factory_(
webrtc::CreateBuiltinVideoBitrateAllocatorFactory()),
engine_(webrtc::CreateBuiltinVideoEncoderFactory(),
webrtc::CreateBuiltinVideoDecoderFactory(),
field_trials_) {}
void SetUp() override {
// One testcase calls SetUp in a loop, only create call_ once.
if (!call_) {
webrtc::Call::Config call_config(&event_log_);
call_config.task_queue_factory = task_queue_factory_.get();
call_config.trials = &field_trials_;
call_.reset(webrtc::Call::Create(call_config));
}
cricket::MediaConfig media_config;
// Disabling cpu overuse detection actually disables quality scaling too; it
// implies DegradationPreference kMaintainResolution. Automatic scaling
// needs to be disabled, otherwise, tests which check the size of received
// frames become flaky.
media_config.video.enable_cpu_adaptation = false;
channel_.reset(
static_cast<cricket::WebRtcVideoChannel*>(engine_.CreateMediaChannel(
call_.get(), media_config, cricket::VideoOptions(),
webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get())));
channel_->OnReadyToSend(true);
EXPECT_TRUE(channel_.get() != NULL);
network_interface_.SetDestination(channel_.get());
channel_->SetInterface(&network_interface_);
cricket::VideoRecvParameters parameters;
parameters.codecs = engine_.send_codecs();
channel_->SetRecvParameters(parameters);
EXPECT_TRUE(channel_->AddSendStream(DefaultSendStreamParams()));
frame_forwarder_ = std::make_unique<webrtc::test::FrameForwarder>();
frame_source_ = std::make_unique<cricket::FakeFrameSource>(
640, 480, rtc::kNumMicrosecsPerSec / kFramerate);
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, frame_forwarder_.get()));
}
// Utility method to setup an additional stream to send and receive video.
// Used to test send and recv between two streams.
void SetUpSecondStream() {
SetUpSecondStreamWithNoRecv();
// Setup recv for second stream.
EXPECT_TRUE(channel_->AddRecvStream(
cricket::StreamParams::CreateLegacy(kSsrc + 2)));
// Make the second renderer available for use by a new stream.
EXPECT_TRUE(channel_->SetSink(kSsrc + 2, &renderer2_));
}
// Setup an additional stream just to send video. Defer add recv stream.
// This is required if you want to test unsignalled recv of video rtp packets.
void SetUpSecondStreamWithNoRecv() {
// SetUp() already added kSsrc make sure duplicate SSRCs cant be added.
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
EXPECT_FALSE(
channel_->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_TRUE(channel_->AddSendStream(
cricket::StreamParams::CreateLegacy(kSsrc + 2)));
// We dont add recv for the second stream.
// Setup the receive and renderer for second stream after send.
frame_forwarder_2_ = std::make_unique<webrtc::test::FrameForwarder>();
EXPECT_TRUE(
channel_->SetVideoSend(kSsrc + 2, nullptr, frame_forwarder_2_.get()));
}
void TearDown() override {
channel_->SetInterface(nullptr);
channel_.reset();
}
void ResetTest() {
TearDown();
SetUp();
}
bool SetDefaultCodec() { return SetOneCodec(DefaultCodec()); }
bool SetOneCodec(const cricket::VideoCodec& codec) {
frame_source_ = std::make_unique<cricket::FakeFrameSource>(
kVideoWidth, kVideoHeight, rtc::kNumMicrosecsPerSec / kFramerate);
bool sending = channel_->sending();
bool success = SetSend(false);
if (success) {
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(codec);
success = channel_->SetSendParameters(parameters);
}
if (success) {
success = SetSend(sending);
}
return success;
}
bool SetSend(bool send) { return channel_->SetSend(send); }
void SendFrame() {
if (frame_forwarder_2_) {
frame_forwarder_2_->IncomingCapturedFrame(frame_source_->GetFrame());
}
frame_forwarder_->IncomingCapturedFrame(frame_source_->GetFrame());
}
bool WaitAndSendFrame(int wait_ms) {
bool ret = rtc::Thread::Current()->ProcessMessages(wait_ms);
SendFrame();
return ret;
}
int NumRtpBytes() { return network_interface_.NumRtpBytes(); }
int NumRtpBytes(uint32_t ssrc) {
return network_interface_.NumRtpBytes(ssrc);
}
int NumRtpPackets() { return network_interface_.NumRtpPackets(); }
int NumRtpPackets(uint32_t ssrc) {
return network_interface_.NumRtpPackets(ssrc);
}
int NumSentSsrcs() { return network_interface_.NumSentSsrcs(); }
rtc::CopyOnWriteBuffer GetRtpPacket(int index) {
return network_interface_.GetRtpPacket(index);
}
static int GetPayloadType(rtc::CopyOnWriteBuffer p) {
RtpPacket header;
EXPECT_TRUE(header.Parse(std::move(p)));
return header.PayloadType();
}
// Tests that we can send and receive frames.
void SendAndReceive(const cricket::VideoCodec& codec) {
EXPECT_TRUE(SetOneCodec(codec));
EXPECT_TRUE(SetSend(true));
channel_->SetDefaultSink(&renderer_);
EXPECT_EQ(0, renderer_.num_rendered_frames());
SendFrame();
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
EXPECT_EQ(codec.id, GetPayloadType(GetRtpPacket(0)));
}
void SendReceiveManyAndGetStats(const cricket::VideoCodec& codec,
int duration_sec,
int fps) {
EXPECT_TRUE(SetOneCodec(codec));
EXPECT_TRUE(SetSend(true));
channel_->SetDefaultSink(&renderer_);
EXPECT_EQ(0, renderer_.num_rendered_frames());
for (int i = 0; i < duration_sec; ++i) {
for (int frame = 1; frame <= fps; ++frame) {
EXPECT_TRUE(WaitAndSendFrame(1000 / fps));
EXPECT_FRAME_WAIT(frame + i * fps, kVideoWidth, kVideoHeight, kTimeout);
}
}
EXPECT_EQ(codec.id, GetPayloadType(GetRtpPacket(0)));
}
cricket::VideoSenderInfo GetSenderStats(size_t i) {
cricket::VideoMediaInfo info;
EXPECT_TRUE(channel_->GetStats(&info));
return info.senders[i];
}
cricket::VideoReceiverInfo GetReceiverStats(size_t i) {
cricket::VideoMediaInfo info;
EXPECT_TRUE(channel_->GetStats(&info));
return info.receivers[i];
}
// Two streams one channel tests.
// Tests that we can send and receive frames.
void TwoStreamsSendAndReceive(const cricket::VideoCodec& codec) {
SetUpSecondStream();
// Test sending and receiving on first stream.
SendAndReceive(codec);
// Test sending and receiving on second stream.
EXPECT_EQ_WAIT(1, renderer2_.num_rendered_frames(), kTimeout);
EXPECT_GT(NumRtpPackets(), 0);
EXPECT_EQ(1, renderer2_.num_rendered_frames());
}
cricket::VideoCodec GetEngineCodec(const std::string& name) {
for (const cricket::VideoCodec& engine_codec : engine_.send_codecs()) {
if (absl::EqualsIgnoreCase(name, engine_codec.name))
return engine_codec;
}
// This point should never be reached.
ADD_FAILURE() << "Unrecognized codec name: " << name;
return cricket::VideoCodec();
}
cricket::VideoCodec DefaultCodec() { return GetEngineCodec("VP8"); }
cricket::StreamParams DefaultSendStreamParams() {
return cricket::StreamParams::CreateLegacy(kSsrc);
}
webrtc::RtcEventLogNull event_log_;
webrtc::FieldTrialBasedConfig field_trials_;
std::unique_ptr<webrtc::test::ScopedFieldTrials> override_field_trials_;
std::unique_ptr<webrtc::TaskQueueFactory> task_queue_factory_;
std::unique_ptr<webrtc::Call> call_;
std::unique_ptr<webrtc::VideoBitrateAllocatorFactory>
video_bitrate_allocator_factory_;
WebRtcVideoEngine engine_;
std::unique_ptr<cricket::FakeFrameSource> frame_source_;
std::unique_ptr<webrtc::test::FrameForwarder> frame_forwarder_;
std::unique_ptr<webrtc::test::FrameForwarder> frame_forwarder_2_;
std::unique_ptr<WebRtcVideoChannel> channel_;
cricket::FakeNetworkInterface network_interface_;
cricket::FakeVideoRenderer renderer_;
// Used by test cases where 2 streams are run on the same channel.
cricket::FakeVideoRenderer renderer2_;
};
// Test that SetSend works.
TEST_F(WebRtcVideoChannelBaseTest, SetSend) {
EXPECT_FALSE(channel_->sending());
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, frame_forwarder_.get()));
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_FALSE(channel_->sending());
EXPECT_TRUE(SetSend(true));
EXPECT_TRUE(channel_->sending());
SendFrame();
EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
EXPECT_TRUE(SetSend(false));
EXPECT_FALSE(channel_->sending());
}
// Test that SetSend fails without codecs being set.
TEST_F(WebRtcVideoChannelBaseTest, SetSendWithoutCodecs) {
EXPECT_FALSE(channel_->sending());
EXPECT_FALSE(SetSend(true));
EXPECT_FALSE(channel_->sending());
}
// Test that we properly set the send and recv buffer sizes by the time
// SetSend is called.
TEST_F(WebRtcVideoChannelBaseTest, SetSendSetsTransportBufferSizes) {
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
EXPECT_EQ(64 * 1024, network_interface_.sendbuf_size());
EXPECT_EQ(256 * 1024, network_interface_.recvbuf_size());
}
// Test that we properly set the send and recv buffer sizes when overriding
// via field trials.
TEST_F(WebRtcVideoChannelBaseTest, OverridesRecvBufferSize) {
// Set field trial to override the default recv buffer size, and then re-run
// setup where the interface is created and configured.
const int kCustomRecvBufferSize = 123456;
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-IncreasedReceivebuffers/123456/");
ResetTest();
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
EXPECT_EQ(64 * 1024, network_interface_.sendbuf_size());
EXPECT_EQ(kCustomRecvBufferSize, network_interface_.recvbuf_size());
}
// Test that we properly set the send and recv buffer sizes when overriding
// via field trials with suffix.
TEST_F(WebRtcVideoChannelBaseTest, OverridesRecvBufferSizeWithSuffix) {
// Set field trial to override the default recv buffer size, and then re-run
// setup where the interface is created and configured.
const int kCustomRecvBufferSize = 123456;
RTC_DCHECK(!override_field_trials_);
override_field_trials_ = std::make_unique<webrtc::test::ScopedFieldTrials>(
"WebRTC-IncreasedReceivebuffers/123456_Dogfood/");
ResetTest();
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
EXPECT_EQ(64 * 1024, network_interface_.sendbuf_size());
EXPECT_EQ(kCustomRecvBufferSize, network_interface_.recvbuf_size());
}
// Test that we properly set the send and recv buffer sizes when overriding
// via field trials that don't make any sense.
TEST_F(WebRtcVideoChannelBaseTest, InvalidRecvBufferSize) {
// Set bogus field trial values to override the default recv buffer size, and
// then re-run setup where the interface is created and configured. The
// default value should still be used.
const char* prev_field_trials = webrtc::field_trial::GetFieldTrialString();
std::string field_trial_string;
for (std::string group : {" ", "NotANumber", "-1", "0"}) {
std::string trial_string = "WebRTC-IncreasedReceivebuffers/";
trial_string += group;
trial_string += "/";
// Dear reader. Sorry for this... it's a bit of a mess.
// TODO(bugs.webrtc.org/12854): This test needs to be rewritten to not use
// ResetTest and changing global field trials in a loop.
TearDown();
// This is a hack to appease tsan. Because of the way the test is written
// active state within Call, including running task queues may race with
// the test changing the global field trial variable.
// This particular hack, pauses the transport controller TQ while we
// change the field trial.
rtc::TaskQueue* tq = call_->GetTransportControllerSend()->GetWorkerQueue();
rtc::Event waiting, resume;
tq->PostTask([&waiting, &resume]() {
waiting.Set();
resume.Wait(rtc::Event::kForever);
});
waiting.Wait(rtc::Event::kForever);
field_trial_string = std::move(trial_string);
webrtc::field_trial::InitFieldTrialsFromString(field_trial_string.c_str());
SetUp();
resume.Set();
// OK, now the test can carry on.
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
EXPECT_EQ(64 * 1024, network_interface_.sendbuf_size());
EXPECT_EQ(256 * 1024, network_interface_.recvbuf_size());
}
webrtc::field_trial::InitFieldTrialsFromString(prev_field_trials);
}
// Test that stats work properly for a 1-1 call.
TEST_F(WebRtcVideoChannelBaseTest, GetStats) {
const int kDurationSec = 3;
const int kFps = 10;
SendReceiveManyAndGetStats(DefaultCodec(), kDurationSec, kFps);
cricket::VideoMediaInfo info;
EXPECT_TRUE(channel_->GetStats(&info));
ASSERT_EQ(1U, info.senders.size());
// TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload?
// For webrtc, bytes_sent does not include the RTP header length.
EXPECT_EQ(info.senders[0].payload_bytes_sent,
NumRtpBytes() - kRtpHeaderSize * NumRtpPackets());
EXPECT_EQ(NumRtpPackets(), info.senders[0].packets_sent);
EXPECT_EQ(0.0, info.senders[0].fraction_lost);
ASSERT_TRUE(info.senders[0].codec_payload_type);
EXPECT_EQ(DefaultCodec().id, *info.senders[0].codec_payload_type);
EXPECT_EQ(0, info.senders[0].firs_rcvd);
EXPECT_EQ(0, info.senders[0].plis_rcvd);
EXPECT_EQ(0u, info.senders[0].nacks_rcvd);
EXPECT_EQ(kVideoWidth, info.senders[0].send_frame_width);
EXPECT_EQ(kVideoHeight, info.senders[0].send_frame_height);
EXPECT_GT(info.senders[0].framerate_input, 0);
EXPECT_GT(info.senders[0].framerate_sent, 0);
EXPECT_EQ(1U, info.send_codecs.count(DefaultCodec().id));
EXPECT_EQ(DefaultCodec().ToCodecParameters(),
info.send_codecs[DefaultCodec().id]);
ASSERT_EQ(1U, info.receivers.size());
EXPECT_EQ(1U, info.senders[0].ssrcs().size());
EXPECT_EQ(1U, info.receivers[0].ssrcs().size());
EXPECT_EQ(info.senders[0].ssrcs()[0], info.receivers[0].ssrcs()[0]);
ASSERT_TRUE(info.receivers[0].codec_payload_type);
EXPECT_EQ(DefaultCodec().id, *info.receivers[0].codec_payload_type);
EXPECT_EQ(NumRtpBytes() - kRtpHeaderSize * NumRtpPackets(),
info.receivers[0].payload_bytes_rcvd);
EXPECT_EQ(NumRtpPackets(), info.receivers[0].packets_rcvd);
EXPECT_EQ(0, info.receivers[0].packets_lost);
// TODO(asapersson): Not set for webrtc. Handle missing stats.
// EXPECT_EQ(0, info.receivers[0].packets_concealed);
EXPECT_EQ(0, info.receivers[0].firs_sent);
EXPECT_EQ(0, info.receivers[0].plis_sent);
EXPECT_EQ(0U, info.receivers[0].nacks_sent);
EXPECT_EQ(kVideoWidth, info.receivers[0].frame_width);
EXPECT_EQ(kVideoHeight, info.receivers[0].frame_height);
EXPECT_GT(info.receivers[0].framerate_rcvd, 0);
EXPECT_GT(info.receivers[0].framerate_decoded, 0);
EXPECT_GT(info.receivers[0].framerate_output, 0);
EXPECT_EQ(1U, info.receive_codecs.count(DefaultCodec().id));
EXPECT_EQ(DefaultCodec().ToCodecParameters(),
info.receive_codecs[DefaultCodec().id]);
}
// Test that stats work properly for a conf call with multiple recv streams.
TEST_F(WebRtcVideoChannelBaseTest, GetStatsMultipleRecvStreams) {
cricket::FakeVideoRenderer renderer1, renderer2;
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(DefaultCodec());
parameters.conference_mode = true;
EXPECT_TRUE(channel_->SetSendParameters(parameters));
EXPECT_TRUE(SetSend(true));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
EXPECT_TRUE(channel_->SetSink(1, &renderer1));
EXPECT_TRUE(channel_->SetSink(2, &renderer2));
EXPECT_EQ(0, renderer1.num_rendered_frames());
EXPECT_EQ(0, renderer2.num_rendered_frames());
std::vector<uint32_t> ssrcs;
ssrcs.push_back(1);
ssrcs.push_back(2);
network_interface_.SetConferenceMode(true, ssrcs);
SendFrame();
EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, kVideoWidth, kVideoHeight,
kTimeout);
EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, kVideoWidth, kVideoHeight,
kTimeout);
EXPECT_TRUE(channel_->SetSend(false));
cricket::VideoMediaInfo info;
EXPECT_TRUE(channel_->GetStats(&info));
ASSERT_EQ(1U, info.senders.size());
// TODO(whyuan): bytes_sent and bytes_rcvd are different. Are both payload?
// For webrtc, bytes_sent does not include the RTP header length.
EXPECT_EQ_WAIT(NumRtpBytes() - kRtpHeaderSize * NumRtpPackets(),
GetSenderStats(0).payload_bytes_sent, kTimeout);
EXPECT_EQ_WAIT(NumRtpPackets(), GetSenderStats(0).packets_sent, kTimeout);
EXPECT_EQ(kVideoWidth, GetSenderStats(0).send_frame_width);
EXPECT_EQ(kVideoHeight, GetSenderStats(0).send_frame_height);
ASSERT_EQ(2U, info.receivers.size());
for (size_t i = 0; i < info.receivers.size(); ++i) {
EXPECT_EQ(1U, GetReceiverStats(i).ssrcs().size());
EXPECT_EQ(i + 1, GetReceiverStats(i).ssrcs()[0]);
EXPECT_EQ_WAIT(NumRtpBytes() - kRtpHeaderSize * NumRtpPackets(),
GetReceiverStats(i).payload_bytes_rcvd, kTimeout);
EXPECT_EQ_WAIT(NumRtpPackets(), GetReceiverStats(i).packets_rcvd, kTimeout);
EXPECT_EQ_WAIT(kVideoWidth, GetReceiverStats(i).frame_width, kTimeout);
EXPECT_EQ_WAIT(kVideoHeight, GetReceiverStats(i).frame_height, kTimeout);
}
}
// Test that stats work properly for a conf call with multiple send streams.
TEST_F(WebRtcVideoChannelBaseTest, GetStatsMultipleSendStreams) {
// Normal setup; note that we set the SSRC explicitly to ensure that
// it will come first in the senders map.
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(DefaultCodec());
parameters.conference_mode = true;
EXPECT_TRUE(channel_->SetSendParameters(parameters));
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
EXPECT_TRUE(SetSend(true));
SendFrame();
EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
// Add an additional capturer, and hook up a renderer to receive it.
cricket::FakeVideoRenderer renderer2;
webrtc::test::FrameForwarder frame_forwarder;
const int kTestWidth = 160;
const int kTestHeight = 120;
cricket::FakeFrameSource frame_source(kTestWidth, kTestHeight,
rtc::kNumMicrosecsPerSec / 5);
EXPECT_TRUE(
channel_->AddSendStream(cricket::StreamParams::CreateLegacy(5678)));
EXPECT_TRUE(channel_->SetVideoSend(5678, nullptr, &frame_forwarder));
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(5678)));
EXPECT_TRUE(channel_->SetSink(5678, &renderer2));
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, kTestWidth, kTestHeight,
kTimeout);
// Get stats, and make sure they are correct for two senders. We wait until
// the number of expected packets have been sent to avoid races where we
// check stats before it has been updated.
cricket::VideoMediaInfo info;
for (uint32_t i = 0; i < kTimeout; ++i) {
rtc::Thread::Current()->ProcessMessages(1);
EXPECT_TRUE(channel_->GetStats(&info));
ASSERT_EQ(2U, info.senders.size());
if (info.senders[0].packets_sent + info.senders[1].packets_sent ==
NumRtpPackets()) {
// Stats have been updated for both sent frames, expectations can be
// checked now.
break;
}
}
EXPECT_EQ(NumRtpPackets(),
info.senders[0].packets_sent + info.senders[1].packets_sent)
<< "Timed out while waiting for packet counts for all sent packets.";
EXPECT_EQ(1U, info.senders[0].ssrcs().size());
EXPECT_EQ(1234U, info.senders[0].ssrcs()[0]);
EXPECT_EQ(kVideoWidth, info.senders[0].send_frame_width);
EXPECT_EQ(kVideoHeight, info.senders[0].send_frame_height);
EXPECT_EQ(1U, info.senders[1].ssrcs().size());
EXPECT_EQ(5678U, info.senders[1].ssrcs()[0]);
EXPECT_EQ(kTestWidth, info.senders[1].send_frame_width);
EXPECT_EQ(kTestHeight, info.senders[1].send_frame_height);
// The capturer must be unregistered here as it runs out of it's scope next.
channel_->SetVideoSend(5678, nullptr, nullptr);
}
// Test that we can set the bandwidth.
TEST_F(WebRtcVideoChannelBaseTest, SetSendBandwidth) {
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(DefaultCodec());
parameters.max_bandwidth_bps = -1; // <= 0 means unlimited.
EXPECT_TRUE(channel_->SetSendParameters(parameters));
parameters.max_bandwidth_bps = 128 * 1024;
EXPECT_TRUE(channel_->SetSendParameters(parameters));
}
// Test that we can set the SSRC for the default send source.
TEST_F(WebRtcVideoChannelBaseTest, SetSendSsrc) {
EXPECT_TRUE(SetDefaultCodec());
EXPECT_TRUE(SetSend(true));
SendFrame();
EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
RtpPacket header;
EXPECT_TRUE(header.Parse(GetRtpPacket(0)));
EXPECT_EQ(kSsrc, header.Ssrc());
// Packets are being paced out, so these can mismatch between the first and
// second call to NumRtpPackets until pending packets are paced out.
EXPECT_EQ_WAIT(NumRtpPackets(), NumRtpPackets(header.Ssrc()), kTimeout);
EXPECT_EQ_WAIT(NumRtpBytes(), NumRtpBytes(header.Ssrc()), kTimeout);
EXPECT_EQ(1, NumSentSsrcs());
EXPECT_EQ(0, NumRtpPackets(kSsrc - 1));
EXPECT_EQ(0, NumRtpBytes(kSsrc - 1));
}
// Test that we can set the SSRC even after codecs are set.
TEST_F(WebRtcVideoChannelBaseTest, SetSendSsrcAfterSetCodecs) {
// Remove stream added in Setup.
EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
EXPECT_TRUE(SetDefaultCodec());
EXPECT_TRUE(
channel_->AddSendStream(cricket::StreamParams::CreateLegacy(999)));
EXPECT_TRUE(channel_->SetVideoSend(999u, nullptr, frame_forwarder_.get()));
EXPECT_TRUE(SetSend(true));
EXPECT_TRUE(WaitAndSendFrame(0));
EXPECT_TRUE_WAIT(NumRtpPackets() > 0, kTimeout);
RtpPacket header;
EXPECT_TRUE(header.Parse(GetRtpPacket(0)));
EXPECT_EQ(999u, header.Ssrc());
// Packets are being paced out, so these can mismatch between the first and
// second call to NumRtpPackets until pending packets are paced out.
EXPECT_EQ_WAIT(NumRtpPackets(), NumRtpPackets(header.Ssrc()), kTimeout);
EXPECT_EQ_WAIT(NumRtpBytes(), NumRtpBytes(header.Ssrc()), kTimeout);
EXPECT_EQ(1, NumSentSsrcs());
EXPECT_EQ(0, NumRtpPackets(kSsrc));
EXPECT_EQ(0, NumRtpBytes(kSsrc));
}
// Test that we can set the default video renderer before and after
// media is received.
TEST_F(WebRtcVideoChannelBaseTest, SetSink) {
RtpPacket packet;
packet.SetSsrc(kSsrc);
channel_->SetDefaultSink(NULL);
EXPECT_TRUE(SetDefaultCodec());
EXPECT_TRUE(SetSend(true));
EXPECT_EQ(0, renderer_.num_rendered_frames());
channel_->OnPacketReceived(packet.Buffer(), /* packet_time_us */ -1);
channel_->SetDefaultSink(&renderer_);
SendFrame();
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
}
// Tests setting up and configuring a send stream.
TEST_F(WebRtcVideoChannelBaseTest, AddRemoveSendStreams) {
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
channel_->SetDefaultSink(&renderer_);
SendFrame();
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
EXPECT_GT(NumRtpPackets(), 0);
RtpPacket header;
size_t last_packet = NumRtpPackets() - 1;
EXPECT_TRUE(header.Parse(GetRtpPacket(static_cast<int>(last_packet))));
EXPECT_EQ(kSsrc, header.Ssrc());
// Remove the send stream that was added during Setup.
EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
int rtp_packets = NumRtpPackets();
EXPECT_TRUE(
channel_->AddSendStream(cricket::StreamParams::CreateLegacy(789u)));
EXPECT_TRUE(channel_->SetVideoSend(789u, nullptr, frame_forwarder_.get()));
EXPECT_EQ(rtp_packets, NumRtpPackets());
// Wait 30ms to guarantee the engine does not drop the frame.
EXPECT_TRUE(WaitAndSendFrame(30));
EXPECT_TRUE_WAIT(NumRtpPackets() > rtp_packets, kTimeout);
last_packet = NumRtpPackets() - 1;
EXPECT_TRUE(header.Parse(GetRtpPacket(static_cast<int>(last_packet))));
EXPECT_EQ(789u, header.Ssrc());
}
// Tests the behavior of incoming streams in a conference scenario.
TEST_F(WebRtcVideoChannelBaseTest, SimulateConference) {
cricket::FakeVideoRenderer renderer1, renderer2;
EXPECT_TRUE(SetDefaultCodec());
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(DefaultCodec());
parameters.conference_mode = true;
EXPECT_TRUE(channel_->SetSendParameters(parameters));
EXPECT_TRUE(SetSend(true));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
EXPECT_TRUE(channel_->SetSink(1, &renderer1));
EXPECT_TRUE(channel_->SetSink(2, &renderer2));
EXPECT_EQ(0, renderer1.num_rendered_frames());
EXPECT_EQ(0, renderer2.num_rendered_frames());
std::vector<uint32_t> ssrcs;
ssrcs.push_back(1);
ssrcs.push_back(2);
network_interface_.SetConferenceMode(true, ssrcs);
SendFrame();
EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, kVideoWidth, kVideoHeight,
kTimeout);
EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, kVideoWidth, kVideoHeight,
kTimeout);
EXPECT_EQ(DefaultCodec().id, GetPayloadType(GetRtpPacket(0)));
EXPECT_EQ(kVideoWidth, renderer1.width());
EXPECT_EQ(kVideoHeight, renderer1.height());
EXPECT_EQ(kVideoWidth, renderer2.width());
EXPECT_EQ(kVideoHeight, renderer2.height());
EXPECT_TRUE(channel_->RemoveRecvStream(2));
EXPECT_TRUE(channel_->RemoveRecvStream(1));
}
// Tests that we can add and remove capturers and frames are sent out properly
TEST_F(WebRtcVideoChannelBaseTest, DISABLED_AddRemoveCapturer) {
using cricket::FOURCC_I420;
using cricket::VideoCodec;
using cricket::VideoFormat;
using cricket::VideoOptions;
VideoCodec codec = DefaultCodec();
const int time_between_send_ms = VideoFormat::FpsToInterval(kFramerate);
EXPECT_TRUE(SetOneCodec(codec));
EXPECT_TRUE(SetSend(true));
channel_->SetDefaultSink(&renderer_);
EXPECT_EQ(0, renderer_.num_rendered_frames());
SendFrame();
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
webrtc::test::FrameForwarder frame_forwarder;
cricket::FakeFrameSource frame_source(480, 360, rtc::kNumMicrosecsPerSec / 30,
rtc::kNumMicrosecsPerSec / 30);
// TODO(nisse): This testcase fails if we don't configure
// screencast. It's unclear why, I see nothing obvious in this
// test which is related to screencast logic.
VideoOptions video_options;
video_options.is_screencast = true;
channel_->SetVideoSend(kSsrc, &video_options, nullptr);
int captured_frames = 1;
for (int iterations = 0; iterations < 2; ++iterations) {
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, &frame_forwarder));
rtc::Thread::Current()->ProcessMessages(time_between_send_ms);
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
++captured_frames;
// Wait until frame of right size is captured.
EXPECT_TRUE_WAIT(renderer_.num_rendered_frames() >= captured_frames &&
480 == renderer_.width() &&
360 == renderer_.height() && !renderer_.black_frame(),
kTimeout);
EXPECT_GE(renderer_.num_rendered_frames(), captured_frames);
EXPECT_EQ(480, renderer_.width());
EXPECT_EQ(360, renderer_.height());
captured_frames = renderer_.num_rendered_frames() + 1;
EXPECT_FALSE(renderer_.black_frame());
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, nullptr));
// Make sure a black frame is generated within the specified timeout.
// The black frame should be the resolution of the previous frame to
// prevent expensive encoder reconfigurations.
EXPECT_TRUE_WAIT(renderer_.num_rendered_frames() >= captured_frames &&
480 == renderer_.width() &&
360 == renderer_.height() && renderer_.black_frame(),
kTimeout);
EXPECT_GE(renderer_.num_rendered_frames(), captured_frames);
EXPECT_EQ(480, renderer_.width());
EXPECT_EQ(360, renderer_.height());
EXPECT_TRUE(renderer_.black_frame());
// The black frame has the same timestamp as the next frame since it's
// timestamp is set to the last frame's timestamp + interval. WebRTC will
// not render a frame with the same timestamp so capture another frame
// with the frame capturer to increment the next frame's timestamp.
frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame());
}
}
// Tests that if SetVideoSend is called with a NULL capturer after the
// capturer was already removed, the application doesn't crash (and no black
// frame is sent).
TEST_F(WebRtcVideoChannelBaseTest, RemoveCapturerWithoutAdd) {
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
EXPECT_TRUE(SetSend(true));
channel_->SetDefaultSink(&renderer_);
EXPECT_EQ(0, renderer_.num_rendered_frames());
SendFrame();
EXPECT_FRAME_WAIT(1, kVideoWidth, kVideoHeight, kTimeout);
// Wait for one frame so they don't get dropped because we send frames too
// tightly.
rtc::Thread::Current()->ProcessMessages(30);
// Remove the capturer.
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, nullptr));
// No capturer was added, so this SetVideoSend shouldn't do anything.
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, nullptr));
rtc::Thread::Current()->ProcessMessages(300);
// Verify no more frames were sent.
EXPECT_EQ(1, renderer_.num_rendered_frames());
}
// Tests that we can add and remove capturer as unique sources.
TEST_F(WebRtcVideoChannelBaseTest, AddRemoveCapturerMultipleSources) {
// WebRTC implementation will drop frames if pushed to quickly. Wait the
// interval time to avoid that.
// WebRTC implementation will drop frames if pushed to quickly. Wait the
// interval time to avoid that.
// Set up the stream associated with the engine.
EXPECT_TRUE(
channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrc)));
EXPECT_TRUE(channel_->SetSink(kSsrc, &renderer_));
cricket::VideoFormat capture_format(
kVideoWidth, kVideoHeight,
cricket::VideoFormat::FpsToInterval(kFramerate), cricket::FOURCC_I420);
// Set up additional stream 1.
cricket::FakeVideoRenderer renderer1;
EXPECT_FALSE(channel_->SetSink(1, &renderer1));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
EXPECT_TRUE(channel_->SetSink(1, &renderer1));
EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(1)));
webrtc::test::FrameForwarder frame_forwarder1;
cricket::FakeFrameSource frame_source(kVideoWidth, kVideoHeight,
rtc::kNumMicrosecsPerSec / kFramerate);
// Set up additional stream 2.
cricket::FakeVideoRenderer renderer2;
EXPECT_FALSE(channel_->SetSink(2, &renderer2));
EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
EXPECT_TRUE(channel_->SetSink(2, &renderer2));
EXPECT_TRUE(channel_->AddSendStream(cricket::StreamParams::CreateLegacy(2)));
webrtc::test::FrameForwarder frame_forwarder2;
// State for all the streams.
EXPECT_TRUE(SetOneCodec(DefaultCodec()));
// A limitation in the lmi implementation requires that SetVideoSend() is
// called after SetOneCodec().
// TODO(hellner): this seems like an unnecessary constraint, fix it.
EXPECT_TRUE(channel_->SetVideoSend(1, nullptr, &frame_forwarder1));
EXPECT_TRUE(channel_->SetVideoSend(2, nullptr, &frame_forwarder2));
EXPECT_TRUE(SetSend(true));
// Test capturer associated with engine.
const int kTestWidth = 160;
const int kTestHeight = 120;
frame_forwarder1.IncomingCapturedFrame(frame_source.GetFrame(
kTestWidth, kTestHeight, webrtc::VideoRotation::kVideoRotation_0,
rtc::kNumMicrosecsPerSec / kFramerate));
EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, kTestWidth, kTestHeight,
kTimeout);
// Capture a frame with additional capturer2, frames should be received
frame_forwarder2.IncomingCapturedFrame(frame_source.GetFrame(
kTestWidth, kTestHeight, webrtc::VideoRotation::kVideoRotation_0,
rtc::kNumMicrosecsPerSec / kFramerate));
EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, kTestWidth, kTestHeight,
kTimeout);
// Successfully remove the capturer.
EXPECT_TRUE(channel_->SetVideoSend(kSsrc, nullptr, nullptr));
// The capturers must be unregistered here as it runs out of it's scope
// next.
EXPECT_TRUE(channel_->SetVideoSend(1, nullptr, nullptr));
EXPECT_TRUE(channel_->SetVideoSend(2, nullptr, nullptr));
}
// Tests empty StreamParams is rejected.
TEST_F(WebRtcVideoChannelBaseTest, RejectEmptyStreamParams) {
// Remove the send stream that was added during Setup.
EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
cricket::StreamParams empty;
EXPECT_FALSE(channel_->AddSendStream(empty));
EXPECT_TRUE(
channel_->AddSendStream(cricket::StreamParams::CreateLegacy(789u)));
}
// Test that multiple send streams can be created and deleted properly.
TEST_F(WebRtcVideoChannelBaseTest, MultipleSendStreams) {
// Remove stream added in Setup. I.e. remove stream corresponding to default
// channel.
EXPECT_TRUE(channel_->RemoveSendStream(kSsrc));
const unsigned int kSsrcsSize = sizeof(kSsrcs4) / sizeof(kSsrcs4[0]);
for (unsigned int i = 0; i < kSsrcsSize; ++i) {
EXPECT_TRUE(channel_->AddSendStream(
cricket::StreamParams::CreateLegacy(kSsrcs4[i])));
}
// Delete one of the non default channel streams, let the destructor delete
// the remaining ones.
EXPECT_TRUE(channel_->RemoveSendStream(kSsrcs4[kSsrcsSize - 1]));
// Stream should already be deleted.
EXPECT_FALSE(channel_->RemoveSendStream(kSsrcs4[kSsrcsSize - 1]));
}
TEST_F(WebRtcVideoChannelBaseTest, SendAndReceiveVp8Vga) {
SendAndReceive(GetEngineCodec("VP8"));
}
TEST_F(WebRtcVideoChannelBaseTest, SendAndReceiveVp8Qvga) {
SendAndReceive(GetEngineCodec("VP8"));
}
TEST_F(WebRtcVideoChannelBaseTest, SendAndReceiveVp8SvcQqvga) {
SendAndReceive(GetEngineCodec("VP8"));
}
TEST_F(WebRtcVideoChannelBaseTest, TwoStreamsSendAndReceive) {
// Set a high bitrate to not be downscaled by VP8 due to low initial start
// bitrates. This currently happens at <250k, and two streams sharing 300k
// initially will use QVGA instead of VGA.
// TODO(pbos): Set up the quality scaler so that both senders reliably start
// at QVGA, then verify that instead.
cricket::VideoCodec codec = GetEngineCodec("VP8");
codec.params[kCodecParamStartBitrate] = "1000000";
TwoStreamsSendAndReceive(codec);
}
#if defined(RTC_ENABLE_VP9)
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderFallback) {
cricket::VideoSendParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP9"));
parameters.codecs.push_back(GetEngineCodec("VP8"));
EXPECT_TRUE(channel_->SetSendParameters(parameters));
VideoCodec codec;
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_EQ("VP9", codec.name);
// RequestEncoderFallback will post a task to the worker thread (which is also
// the current thread), hence the ProcessMessages call.
channel_->RequestEncoderFallback();
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_EQ("VP8", codec.name);
// No other codec to fall back to, keep using VP8.
channel_->RequestEncoderFallback();
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_EQ("VP8", codec.name);
}
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchWithConfig) {
const std::string kParam = "the-param";
const std::string kPing = "ping";
const std::string kPong = "pong";
cricket::VideoSendParameters parameters;
VideoCodec vp9 = GetEngineCodec("VP9");
vp9.params[kParam] = kPong;
parameters.codecs.push_back(vp9);
VideoCodec vp8 = GetEngineCodec("VP8");
vp8.params[kParam] = kPing;
parameters.codecs.push_back(vp8);
EXPECT_TRUE(channel_->SetSendParameters(parameters));
channel_->SetVideoCodecSwitchingEnabled(true);
VideoCodec codec;
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP9"));
// RequestEncoderSwitch will post a task to the worker thread (which is also
// the current thread), hence the ProcessMessages call.
webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing};
channel_->RequestEncoderSwitch(conf1);
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP8"));
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPong};
channel_->RequestEncoderSwitch(conf2);
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP9"));
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPong)));
}
TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchIncorrectParam) {
const std::string kParam = "the-param";
const std::string kPing = "ping";
const std::string kPong = "pong";
cricket::VideoSendParameters parameters;
VideoCodec vp9 = GetEngineCodec("VP9");
vp9.params[kParam] = kPong;
parameters.codecs.push_back(vp9);
VideoCodec vp8 = GetEngineCodec("VP8");
vp8.params[kParam] = kPing;
parameters.codecs.push_back(vp8);
EXPECT_TRUE(channel_->SetSendParameters(parameters));
channel_->SetVideoCodecSwitchingEnabled(true);
VideoCodec codec;
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP9"));
// RequestEncoderSwitch will post a task to the worker thread (which is also
// the current thread), hence the ProcessMessages call.
webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing};
channel_->RequestEncoderSwitch(conf1);
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP8"));
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
// Incorrect conf2.value, expect no codec switch.
webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPing};
channel_->RequestEncoderSwitch(conf2);
rtc::Thread::Current()->ProcessMessages(30);
ASSERT_TRUE(channel_->GetSendCodec(&codec));
EXPECT_THAT(codec.name, Eq("VP8"));
EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing)));
}
TEST_F(WebRtcVideoChannelBaseTest,
RequestEncoderSwitchWithConfigBeforeEnabling) {
const std::string kParam = "the-param";
const std::string kPing = "ping";
const std::string kPong = "pong";
cricket::VideoSendParameters parameters;