diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /media/webrtc/signaling/gtest/videoconduit_unittests.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | media/webrtc/signaling/gtest/videoconduit_unittests.cpp | 2336 |
1 files changed, 2336 insertions, 0 deletions
diff --git a/media/webrtc/signaling/gtest/videoconduit_unittests.cpp b/media/webrtc/signaling/gtest/videoconduit_unittests.cpp new file mode 100644 index 0000000000..0997eb807f --- /dev/null +++ b/media/webrtc/signaling/gtest/videoconduit_unittests.cpp @@ -0,0 +1,2336 @@ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define GTEST_HAS_RTTI 0 +#include "gtest/gtest.h" + +#include "nspr.h" +#include "nss.h" +#include "ssl.h" + +#include "ConcreteConduitControl.h" +#include "VideoConduit.h" +#include "RtpRtcpConfig.h" +#include "WebrtcCallWrapper.h" +#include "WebrtcGmpVideoCodec.h" + +#include "api/video/video_sink_interface.h" +#include "media/base/media_constants.h" +#include "media/base/video_adapter.h" + +#include "MockCall.h" +#include "MockConduit.h" + +using namespace mozilla; +using namespace testing; +using namespace webrtc; + +namespace test { + +class MockVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> { + public: + MockVideoSink() : mVideoFrame(nullptr, kVideoRotation_0, 0) {} + + ~MockVideoSink() override = default; + + void OnFrame(const webrtc::VideoFrame& frame) override { + mVideoFrame = frame; + ++mOnFrameCount; + } + + size_t mOnFrameCount = 0; + webrtc::VideoFrame mVideoFrame; +}; + +class VideoConduitTest : public Test { + public: + VideoConduitTest( + VideoSessionConduit::Options aOptions = VideoSessionConduit::Options()) + : mCallWrapper(MockCallWrapper::Create()), + mVideoConduit(MakeRefPtr<WebrtcVideoConduit>( + mCallWrapper, GetCurrentSerialEventTarget(), std::move(aOptions), + "", TrackingId(TrackingId::Source::Unimplemented, 0))), + mControl(GetCurrentSerialEventTarget()) { + NSS_NoDB_Init(nullptr); + + EXPECT_EQ(mVideoConduit->Init(), kMediaConduitNoError); + mVideoConduit->InitControl(&mControl); + mControl.Update([](auto& aControl) { + aControl.mLocalSsrcs = {42}; + aControl.mLocalVideoRtxSsrcs = {43}; + }); + } + + ~VideoConduitTest() override { + mozilla::Unused << WaitFor(mVideoConduit->Shutdown()); + mCallWrapper->Destroy(); + } + + MockCall* Call() { return mCallWrapper->GetMockCall(); } + + MediaConduitErrorCode SendVideoFrame(unsigned short width, + unsigned short height, + uint64_t capture_time_ms) { + rtc::scoped_refptr<webrtc::I420Buffer> buffer = + webrtc::I420Buffer::Create(width, height); + memset(buffer->MutableDataY(), 0x10, buffer->StrideY() * buffer->height()); + memset(buffer->MutableDataU(), 0x80, + buffer->StrideU() * ((buffer->height() + 1) / 2)); + memset(buffer->MutableDataV(), 0x80, + buffer->StrideV() * ((buffer->height() + 1) / 2)); + + webrtc::VideoFrame frame(buffer, capture_time_ms, capture_time_ms, + webrtc::kVideoRotation_0); + return mVideoConduit->SendVideoFrame(frame); + } + + const RefPtr<MockCallWrapper> mCallWrapper; + const RefPtr<mozilla::WebrtcVideoConduit> mVideoConduit; + ConcreteConduitControl mControl; +}; + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecs) { + // No codecs + mControl.Update([&](auto& aControl) { + aControl.mReceiving = true; + aControl.mVideoRecvCodecs = {}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 0U); + + // empty codec name + mControl.Update([&](auto& aControl) { + VideoCodecConfig codec(120, "", EncodingConstraints()); + aControl.mVideoRecvCodecs = {codec}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 0U); + + // Defaults + mControl.Update([&](auto& aControl) { + VideoCodecConfig codec(120, "VP8", EncodingConstraints()); + aControl.mVideoRecvCodecs = {codec}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsFEC) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mFECFbSet = true; + aControl.mVideoRecvCodecs = { + codecConfig, VideoCodecConfig(1, "ulpfec", EncodingConstraints()), + VideoCodecConfig(2, "red", EncodingConstraints())}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, 1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, 2); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsH264) { + mControl.Update([&](auto& aControl) { + // Insert twice to test that only one H264 codec is used at a time + aControl.mReceiving = true; + aControl.mVideoRecvCodecs = { + VideoCodecConfig(120, "H264", EncodingConstraints()), + VideoCodecConfig(120, "H264", EncodingConstraints())}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "H264"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsKeyframeRequestType) { + // PLI should be preferred to FIR, same codec. + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mNackFbTypes.push_back("pli"); + codecConfig.mCcmFbTypes.push_back("fir"); + aControl.mReceiving = true; + aControl.mVideoRecvCodecs = {codecConfig}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kPliRtcp); + + // Just FIR + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mCcmFbTypes.push_back("fir"); + aControl.mVideoRecvCodecs = {codecConfig}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kFirRtcp); + + // PLI should be preferred to FIR, multiple codecs. + mControl.Update([&](auto& aControl) { + VideoCodecConfig pliCodec(120, "VP8", EncodingConstraints()); + pliCodec.mNackFbTypes.push_back("pli"); + VideoCodecConfig firCodec(120, "VP8", EncodingConstraints()); + firCodec.mCcmFbTypes.push_back("fir"); + aControl.mVideoRecvCodecs = {pliCodec, firCodec}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 2U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kPliRtcp); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsNack) { + mControl.Update([&](auto& aControl) { + aControl.mReceiving = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mNackFbTypes.push_back(""); + aControl.mVideoRecvCodecs = {codecConfig}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 1000); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsRemb) { + mControl.Update([&](auto& aControl) { + aControl.mReceiving = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mRembFbSet = true; + aControl.mVideoRecvCodecs = {codecConfig}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureReceiveMediaCodecsTmmbr) { + mControl.Update([&](auto& aControl) { + aControl.mReceiving = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mCcmFbTypes.push_back("tmmbr"); + aControl.mVideoRecvCodecs = {codecConfig}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodec) { + // defaults + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8"); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type, + VideoEncoderConfig::ContentType::kRealtimeVideo); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000)); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U); + + // empty codec name + mControl.Update([&](auto& aControl) { + aControl.mVideoSendCodec = + Some(VideoCodecConfig(120, "", EncodingConstraints())); + }); + // Bad codec gets ignored + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8"); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxFps) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + EncodingConstraints constraints; + VideoCodecConfig codecConfig(120, "VP8", constraints); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + std::vector<webrtc::VideoStream> videoStreams; + videoStreams = Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].max_framerate, 30); // DEFAULT_VIDEO_MAX_FRAMERATE + + mControl.Update([&](auto& aControl) { + EncodingConstraints constraints; + constraints.maxFps = Some(42); + VideoCodecConfig codecConfig(120, "VP8", constraints); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + videoStreams = Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].max_framerate, 42); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxMbps) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + EncodingConstraints constraints; + constraints.maxMbps = 0; + VideoCodecConfig codecConfig(120, "VP8", constraints); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(640, 480, 1); + std::vector<webrtc::VideoStream> videoStreams; + videoStreams = Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].max_framerate, 30); // DEFAULT_VIDEO_MAX_FRAMERATE + + mControl.Update([&](auto& aControl) { + EncodingConstraints constraints; + constraints.maxMbps = 10000; + VideoCodecConfig codecConfig(120, "VP8", constraints); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(640, 480, 1); + videoStreams = Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].max_framerate, 8); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecDefaults) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + + { + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(640, 480); + EXPECT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].min_bitrate_bps, 150000); + EXPECT_EQ(videoStreams[0].target_bitrate_bps, 500000); + EXPECT_EQ(videoStreams[0].max_bitrate_bps, 2000000); + } + + { + // SelectBitrates not called until we send a frame + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + EXPECT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].min_bitrate_bps, 200000); + EXPECT_EQ(videoStreams[0].target_bitrate_bps, 800000); + EXPECT_EQ(videoStreams[0].max_bitrate_bps, 2500000); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecTias) { + // TIAS + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints()); + codecConfigTias.mEncodings.emplace_back(); + codecConfigTias.mTias = 1000000; + aControl.mVideoSendCodec = Some(codecConfigTias); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + { + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000); + ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000); + } + + // TIAS (too low) + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigTiasLow(120, "VP8", EncodingConstraints()); + codecConfigTiasLow.mEncodings.emplace_back(); + codecConfigTiasLow.mTias = 1000; + aControl.mVideoSendCodec = Some(codecConfigTiasLow); + }); + { + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].min_bitrate_bps, 30000); + ASSERT_EQ(videoStreams[0].target_bitrate_bps, 30000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 30000); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxBr) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.maxBr = 50000; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000); + ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecScaleResolutionBy) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 4; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = {42, 1729}; + aControl.mLocalVideoRtxSsrcs = {43, 1730}; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + SendVideoFrame(640, 360, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 2U); + ASSERT_EQ(videoStreams[0].width, 160U); + ASSERT_EQ(videoStreams[0].height, 90U); + ASSERT_EQ(videoStreams[1].width, 320U); + ASSERT_EQ(videoStreams[1].height, 180U); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecCodecMode) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type, + VideoEncoderConfig::ContentType::kScreen); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecFEC) { + { + // H264 + FEC + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "H264", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mFECFbSet = true; + codecConfig.mULPFECPayloadType = 1; + codecConfig.mREDPayloadType = 2; + codecConfig.mREDRTXPayloadType = 3; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3); + } + + { + // H264 + FEC + Nack + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "H264", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mFECFbSet = true; + codecConfig.mNackFbTypes.push_back(""); + codecConfig.mULPFECPayloadType = 1; + codecConfig.mREDPayloadType = 2; + codecConfig.mREDRTXPayloadType = 3; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, -1); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, -1); + } + + { + // VP8 + FEC + Nack + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mFECFbSet = true; + codecConfig.mNackFbTypes.push_back(""); + codecConfig.mULPFECPayloadType = 1; + codecConfig.mREDPayloadType = 2; + codecConfig.mREDRTXPayloadType = 3; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecNack) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.nack.rtp_history_ms, 0); + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mNackFbTypes.push_back(""); + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.nack.rtp_history_ms, 1000); +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecRids) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids.size(), 0U); + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.rid = "1"; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.rid = "2"; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mLocalSsrcs = {42, 1729}; + aControl.mLocalVideoRtxSsrcs = {43, 1730}; + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids.size(), 2U); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids[0], "2"); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rids[1], "1"); +} + +TEST_F(VideoConduitTest, TestOnSinkWantsChanged) { + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mEncodingConstraints.maxFs = 0; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + wants.max_pixel_count = 256000; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + SendVideoFrame(1920, 1080, 1); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1920, 1080); + EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 256000U); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 673U); + EXPECT_EQ(videoStreams[0].height, 379U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 500; + aControl.mVideoSendCodec = Some(codecConfig); + }); + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + SendVideoFrame(1920, 1080, 2); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 500U * 16U * 16U); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 476U); + EXPECT_EQ(videoStreams[0].height, 268U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 1000; + aControl.mVideoSendCodec = Some(codecConfig); + }); + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + SendVideoFrame(1920, 1080, 3); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + EXPECT_LE(videoStreams[0].width * videoStreams[0].height, + 1000U * 16U * 16U); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 673U); + EXPECT_EQ(videoStreams[0].height, 379U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 500; + aControl.mVideoSendCodec = Some(codecConfig); + }); + wants.max_pixel_count = 64000; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + SendVideoFrame(1920, 1080, 4); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 64000U); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 336U); + EXPECT_EQ(videoStreams[0].height, 189U); + } +} + +class VideoConduitTestScalingLocked : public VideoConduitTest { + public: + static VideoSessionConduit::Options CreateOptions() { + VideoSessionConduit::Options options; + options.mLockScaling = true; + return options; + } + VideoConduitTestScalingLocked() : VideoConduitTest(CreateOptions()) {} +}; + +TEST_F(VideoConduitTestScalingLocked, TestOnSinkWantsChanged) { + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodingConstraints.maxFs = 0; + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + wants.max_pixel_count = 256000; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + SendVideoFrame(1920, 1080, 1); + EXPECT_EQ(sink->mVideoFrame.width(), 1920); + EXPECT_EQ(sink->mVideoFrame.height(), 1080); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 1920U); + EXPECT_EQ(videoStreams[0].height, 1080U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 500; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1920, 1080, 2); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + EXPECT_LE(videoStreams[0].width * videoStreams[0].height, 500U * 16U * 16U); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 476U); + EXPECT_EQ(videoStreams[0].height, 268U); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastOddScreen) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 4; + } + aControl.mVideoSendCodec = Some(codecConfig); + } + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = {42, 43, 44}; + aControl.mLocalVideoRtxSsrcs = {45, 46, 47}; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + SendVideoFrame(26, 24, 1); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 3U); + EXPECT_EQ(videoStreams[2].width, 26U); + EXPECT_EQ(videoStreams[2].height, 24U); + EXPECT_EQ(videoStreams[1].width, 13U); + EXPECT_EQ(videoStreams[1].height, 12U); + EXPECT_EQ(videoStreams[0].width, 6U); + EXPECT_EQ(videoStreams[0].height, 6U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodings.clear(); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mLocalSsrcs = {42}; + aControl.mLocalVideoRtxSsrcs = {43}; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(26, 24, 2); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 1U); + EXPECT_EQ(videoStreams[0].width, 26U); + EXPECT_EQ(videoStreams[0].height, 24U); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastAllScaling) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 4; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 6; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = {42, 43, 44}; + aControl.mLocalVideoRtxSsrcs = {45, 46, 47}; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + SendVideoFrame(1281, 721, 1); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 3U); + EXPECT_EQ(videoStreams[2].width, 640U); + EXPECT_EQ(videoStreams[2].height, 360U); + EXPECT_EQ(videoStreams[1].width, 320U); + EXPECT_EQ(videoStreams[1].height, 180U); + EXPECT_EQ(videoStreams[0].width, 213U); + EXPECT_EQ(videoStreams[0].height, 120U); + } + + SendVideoFrame(1281, 721, 2); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 3U); + EXPECT_EQ(videoStreams[2].width, 640U); + EXPECT_EQ(videoStreams[2].height, 360U); + EXPECT_EQ(videoStreams[1].width, 320U); + EXPECT_EQ(videoStreams[1].height, 180U); + EXPECT_EQ(videoStreams[0].width, 213U); + EXPECT_EQ(videoStreams[0].height, 120U); + } + + SendVideoFrame(1280, 720, 3); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 3U); + EXPECT_EQ(videoStreams[2].width, 640U); + EXPECT_EQ(videoStreams[2].height, 360U); + EXPECT_EQ(videoStreams[1].width, 320U); + EXPECT_EQ(videoStreams[1].height, 180U); + EXPECT_EQ(videoStreams[0].width, 213U); + EXPECT_EQ(videoStreams[0].height, 120U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodings[0].constraints.scaleDownBy = 1; + codecConfig.mEncodings[1].constraints.scaleDownBy = 2; + codecConfig.mEncodings[2].constraints.scaleDownBy = 4; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 4); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams.size(), 3U); + EXPECT_EQ(videoStreams[2].width, 1280U); + EXPECT_EQ(videoStreams[2].height, 720U); + EXPECT_EQ(videoStreams[1].width, 640U); + EXPECT_EQ(videoStreams[1].height, 360U); + EXPECT_EQ(videoStreams[0].width, 320U); + EXPECT_EQ(videoStreams[0].height, 180U); + } +} + +TEST_F(VideoConduitTest, TestConfigureSendMediaCodecSimulcastScreenshare) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 4; + } + + aControl.mTransmitting = true; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = {42, 43, 44}; + aControl.mLocalVideoRtxSsrcs = {45, 46, 47}; + aControl.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), 1U); +} + +TEST_F(VideoConduitTest, TestReconfigureReceiveMediaCodecs) { + // Defaults + mControl.Update([&](auto& aControl) { + aControl.mReceiving = true; + aControl.mVideoRecvCodecs = { + VideoCodecConfig(120, "VP8", EncodingConstraints())}; + aControl.mVideoRecvRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); + + // FEC + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigFecFb(120, "VP8", EncodingConstraints()); + codecConfigFecFb.mFECFbSet = true; + VideoCodecConfig codecConfigFEC(1, "ulpfec", EncodingConstraints()); + VideoCodecConfig codecConfigRED(2, "red", EncodingConstraints()); + aControl.mVideoRecvCodecs = {codecConfigFecFb, codecConfigFEC, + codecConfigRED}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, 1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, 2); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); + + // H264 + mControl.Update([&](auto& aControl) { + aControl.mVideoRecvCodecs = { + VideoCodecConfig(120, "H264", EncodingConstraints())}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "H264"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); + + // Nack + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigNack(120, "VP8", EncodingConstraints()); + codecConfigNack.mNackFbTypes.push_back(""); + aControl.mVideoRecvCodecs = {codecConfigNack}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 1000); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); + + // Remb + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigRemb(120, "VP8", EncodingConstraints()); + codecConfigRemb.mRembFbSet = true; + aControl.mVideoRecvCodecs = {codecConfigRemb}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); + + // Tmmbr + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigTmmbr(120, "VP8", EncodingConstraints()); + codecConfigTmmbr.mCcmFbTypes.push_back("tmmbr"); + aControl.mVideoRecvCodecs = {codecConfigTmmbr}; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders.size(), 1U); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].payload_type, 120); + ASSERT_EQ(Call()->mVideoReceiveConfig->decoders[0].video_format.name, "VP8"); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.local_ssrc, 0U); + ASSERT_NE(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 0U); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.nack.rtp_history_ms, 0); + ASSERT_FALSE(Call()->mVideoReceiveConfig->rtp.remb); + ASSERT_TRUE(Call()->mVideoReceiveConfig->rtp.tmmbr); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.keyframe_method, + webrtc::KeyFrameReqMethod::kNone); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.ulpfec_payload_type, -1); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.red_payload_type, -1); + ASSERT_EQ( + Call()->mVideoReceiveConfig->rtp.rtx_associated_payload_types.size(), 0U); +} + +TEST_F(VideoConduitTest, TestReconfigureSendMediaCodec) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_FALSE(Call()->mVideoSendConfig); + + // Defaults + mControl.Update([&](auto& aControl) { aControl.mTransmitting = true; }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8"); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type, + VideoEncoderConfig::ContentType::kRealtimeVideo); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000)); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U); + mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; }); + + // FEC + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfigFEC(120, "VP8", EncodingConstraints()); + codecConfigFEC.mEncodings.emplace_back(); + codecConfigFEC.mFECFbSet = true; + codecConfigFEC.mNackFbTypes.push_back(""); + codecConfigFEC.mULPFECPayloadType = 1; + codecConfigFEC.mREDPayloadType = 2; + codecConfigFEC.mREDRTXPayloadType = 3; + aControl.mVideoSendCodec = Some(codecConfigFEC); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.ulpfec_payload_type, 1); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_payload_type, 2); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.ulpfec.red_rtx_payload_type, 3); + mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; }); + + // H264 + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfigH264(120, "H264", EncodingConstraints()); + codecConfigH264.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfigH264); + }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "H264"); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120); + mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; }); + + // TIAS + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints()); + codecConfigTias.mEncodings.emplace_back(); + codecConfigTias.mTias = 1000000; + aControl.mVideoSendCodec = Some(codecConfigTias); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000); + ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000); + } + mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; }); + + // MaxBr + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + VideoCodecConfig::Encoding encoding; + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.maxBr = 50000; + } + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000); + ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000); + } + mControl.Update([&](auto& aControl) { aControl.mTransmitting = false; }); + + // MaxFs + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfigMaxFs(120, "VP8", EncodingConstraints()); + codecConfigMaxFs.mEncodingConstraints.maxFs = 3600; + VideoCodecConfig::Encoding encoding; + encoding.constraints.maxBr = 0; + codecConfigMaxFs.mEncodings.push_back(encoding); + aControl.mVideoSendCodec = Some(codecConfigMaxFs); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1280U); + ASSERT_EQ(videoStreams[0].height, 720U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + } + + { + SendVideoFrame(640, 360, 2); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 640U); + ASSERT_EQ(videoStreams[0].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + } + + { + SendVideoFrame(1920, 1280, 3); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1174U); + ASSERT_EQ(videoStreams[0].height, 783U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U); + ASSERT_EQ(sink->mOnFrameCount, 3U); + } +} + +TEST_F(VideoConduitTest, TestReconfigureSendMediaCodecWhileTransmitting) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_FALSE(Call()->mVideoSendConfig); + + // Defaults + mControl.Update([&](auto& aControl) { aControl.mTransmitting = true; }); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_name, "VP8"); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.payload_type, 120); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kCompound); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.max_packet_size, kVideoMtu); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->content_type, + VideoEncoderConfig::ContentType::kRealtimeVideo); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->min_transmit_bitrate_bps, 0); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->max_bitrate_bps, KBPS(10000)); + ASSERT_EQ(Call()->mVideoSendEncoderConfig->number_of_streams, 1U); + + // Changing these parameters should not require flipping mTransmitting for the + // changes to take effect. + + // TIAS + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigTias(120, "VP8", EncodingConstraints()); + codecConfigTias.mEncodings.emplace_back(); + codecConfigTias.mTias = 1000000; + aControl.mVideoSendCodec = Some(codecConfigTias); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_EQ(videoStreams[0].min_bitrate_bps, 200000); + ASSERT_EQ(videoStreams[0].target_bitrate_bps, 800000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 1000000); + } + + // MaxBr + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.maxBr = 50000; + } + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + SendVideoFrame(1280, 720, 1); + { + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(1280, 720); + ASSERT_EQ(videoStreams.size(), 1U); + ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000); + ASSERT_LE(videoStreams[0].target_bitrate_bps, 50000); + ASSERT_EQ(videoStreams[0].max_bitrate_bps, 50000); + } + + // MaxFs + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodingConstraints.maxFs = 3600; + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.maxBr = 0; + } + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1280U); + ASSERT_EQ(videoStreams[0].height, 720U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + } + + { + SendVideoFrame(640, 360, 2); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 640U); + ASSERT_EQ(videoStreams[0].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + } + + { + SendVideoFrame(1920, 1280, 3); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1174U); + ASSERT_EQ(videoStreams[0].height, 783U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U); + ASSERT_EQ(sink->mOnFrameCount, 3U); + } + + // ScaleResolutionDownBy + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.maxFs = 0; + encoding.constraints.scaleDownBy = 3.7; + } + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + SendVideoFrame(1280, 720, 4); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 345U); + ASSERT_EQ(videoStreams[0].height, 194U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U); + ASSERT_EQ(sink->mOnFrameCount, 4U); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfigScaleDownBy = *aControl.mVideoSendCodec.Ref(); + codecConfigScaleDownBy.mEncodings[0].constraints.scaleDownBy = 1.3; + aControl.mVideoSendCodec = Some(codecConfigScaleDownBy); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + SendVideoFrame(641, 359, 5); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 493U); + ASSERT_EQ(videoStreams[0].height, 276U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U); + ASSERT_EQ(sink->mOnFrameCount, 5U); + } +} + +TEST_F(VideoConduitTest, TestVideoEncode) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + SendVideoFrame(1280, 720, 1); + ASSERT_EQ(sink->mVideoFrame.width(), 1280); + ASSERT_EQ(sink->mVideoFrame.height(), 720); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + + SendVideoFrame(640, 360, 2); + ASSERT_EQ(sink->mVideoFrame.width(), 640); + ASSERT_EQ(sink->mVideoFrame.height(), 360); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + + SendVideoFrame(1920, 1280, 3); + ASSERT_EQ(sink->mVideoFrame.width(), 1920); + ASSERT_EQ(sink->mVideoFrame.height(), 1280); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U); + ASSERT_EQ(sink->mOnFrameCount, 3U); + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxFs) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodingConstraints.maxFs = 3600; + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1280U); + ASSERT_EQ(videoStreams[0].height, 720U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + } + + { + SendVideoFrame(640, 360, 2); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 640U); + ASSERT_EQ(videoStreams[0].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + } + + { + SendVideoFrame(1920, 1280, 3); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1174U); + ASSERT_EQ(videoStreams[0].height, 783U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U); + ASSERT_EQ(sink->mOnFrameCount, 3U); + } + + // maxFs should not force pixel count above what a sink has requested. + // We set 3600 macroblocks (16x16 pixels), so we request 3500 here. + wants.max_pixel_count = 3500 * 16 * 16; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, 4); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U); + ASSERT_EQ(sink->mOnFrameCount, 4U); + } + + { + SendVideoFrame(640, 360, 5); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 640U); + ASSERT_EQ(videoStreams[0].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U); + ASSERT_EQ(sink->mOnFrameCount, 5U); + } + + { + SendVideoFrame(1920, 1280, 6); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1158U); + ASSERT_EQ(videoStreams[0].height, 772U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 6000U); + ASSERT_EQ(sink->mOnFrameCount, 6U); + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxFsNegotiatedThenSinkWants) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mEncodingConstraints.maxFs = 3500; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + unsigned int frame = 0; + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + wants.max_pixel_count = 3600 * 16 * 16; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxFsCodecChange) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mEncodingConstraints.maxFs = 3500; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + unsigned int frame = 0; + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(121, "VP9", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mEncodingConstraints.maxFs = 3500; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxFsSinkWantsThenCodecChange) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + wants.max_pixel_count = 3500 * 16 * 16; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + unsigned int frame = 0; + + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(121, "VP9", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxFsNegotiated) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + unsigned int frame = 0; + SendVideoFrame(1280, 720, frame++); + ASSERT_EQ(sink->mVideoFrame.width(), 1280); + ASSERT_EQ(sink->mVideoFrame.height(), 720); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + + // Ensure that negotiating a new max-fs works + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 3500; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + SendVideoFrame(1280, 720, frame++); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1260U); + ASSERT_EQ(videoStreams[0].height, 709U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + } + + // Ensure that negotiating max-fs away works + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig = *aControl.mVideoSendCodec.Ref(); + codecConfig.mEncodingConstraints.maxFs = 0; + aControl.mVideoSendCodec = Some(codecConfig); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + SendVideoFrame(1280, 720, frame++); + ASSERT_EQ(sink->mVideoFrame.width(), 1280); + ASSERT_EQ(sink->mVideoFrame.height(), 720); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), (frame - 1) * 1000); + ASSERT_EQ(sink->mOnFrameCount, frame); + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeMaxWidthAndHeight) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodingConstraints.maxWidth = 1280; + codecConfig.mEncodingConstraints.maxHeight = 720; + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + SendVideoFrame(1280, 720, 1); + ASSERT_EQ(sink->mVideoFrame.width(), 1280); + ASSERT_EQ(sink->mVideoFrame.height(), 720); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + + SendVideoFrame(640, 360, 2); + ASSERT_EQ(sink->mVideoFrame.width(), 640); + ASSERT_EQ(sink->mVideoFrame.height(), 360); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + + { + SendVideoFrame(1920, 1280, 3); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 1080U); + ASSERT_EQ(videoStreams[0].height, 720U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U); + ASSERT_EQ(sink->mOnFrameCount, 3U); + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestVideoEncodeScaleResolutionBy) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodingConstraints.maxFs = 3600; + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(1280, 720, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 640U); + ASSERT_EQ(videoStreams[0].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + } + + { + SendVideoFrame(640, 360, 2); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[0].width, 320U); + ASSERT_EQ(videoStreams[0].height, 180U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + mVideoConduit->RemoveSink(sink.get()); + } +} + +TEST_F(VideoConduitTest, TestVideoEncodeSimulcastScaleResolutionBy) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 2; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 3; + } + { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = 4; + } + + aControl.mTransmitting = true; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = {42, 43, 44}; + aControl.mLocalVideoRtxSsrcs = {45, 46, 47}; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + SendVideoFrame(640, 480, 1); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[2].width, 320U); + ASSERT_EQ(videoStreams[2].height, 240U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U); + ASSERT_EQ(sink->mOnFrameCount, 1U); + } + + { + SendVideoFrame(1280, 720, 2); + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(sink->mVideoFrame.width(), + sink->mVideoFrame.height()); + ASSERT_EQ(videoStreams[2].width, 640U); + ASSERT_EQ(videoStreams[2].height, 360U); + ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U); + ASSERT_EQ(sink->mOnFrameCount, 2U); + mVideoConduit->RemoveSink(sink.get()); + } +} + +TEST_F(VideoConduitTest, TestVideoEncodeLargeScaleResolutionByFrameDropping) { + for (const auto& scales : + {std::vector{200U}, std::vector{200U, 300U}, std::vector{300U, 200U}}) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + for (const auto& scale : scales) { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = scale; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = scales; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + rtc::VideoSinkWants wants; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + { + // If all layers' scaleDownBy is larger than any input dimension, that + // dimension becomes zero and we drop it. + // NB: libwebrtc doesn't CreateEncoderStreams() unless there's a real + // frame, so no reason to call it here. + SendVideoFrame(199, 199, 1); + EXPECT_EQ(sink->mOnFrameCount, 0U); + } + + { + // If only width becomes zero, we drop. + SendVideoFrame(199, 200, 2); + EXPECT_EQ(sink->mOnFrameCount, 0U); + } + + { + // If only height becomes zero, we drop. + SendVideoFrame(200, 199, 3); + EXPECT_EQ(sink->mOnFrameCount, 0U); + } + + { + // If dimensions are non-zero, we pass through. + SendVideoFrame(200, 200, 4); + EXPECT_EQ(sink->mOnFrameCount, 1U); + } + + mVideoConduit->RemoveSink(sink.get()); + } +} + +TEST_F(VideoConduitTest, TestVideoEncodeLargeScaleResolutionByStreamCreation) { + for (const auto& scales : + {std::vector{200U}, std::vector{200U, 300U}, std::vector{300U, 200U}}) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + for (const auto& scale : scales) { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = scale; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = scales; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + { + // If dimensions scale to <1, we create a 1x1 stream. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(199, 199); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (const auto& stream : videoStreams) { + EXPECT_EQ(stream.width, 1U); + EXPECT_EQ(stream.height, 1U); + } + } + + { + // If width scales to <1, we create a 1x1 stream. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(199, 200); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (const auto& stream : videoStreams) { + EXPECT_EQ(stream.width, 1U); + EXPECT_EQ(stream.height, 1U); + } + } + + { + // If height scales to <1, we create a 1x1 stream. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(200, 199); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (const auto& stream : videoStreams) { + EXPECT_EQ(stream.width, 1U); + EXPECT_EQ(stream.height, 1U); + } + } + + { + // If dimensions scale to 1, we create a 1x1 stream. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(200, 200); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (const auto& stream : videoStreams) { + EXPECT_EQ(stream.width, 1U); + EXPECT_EQ(stream.height, 1U); + } + } + + { + // If one dimension scales to 0 and the other >1, we create a 1x1 stream. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(400, 199); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (const auto& stream : videoStreams) { + EXPECT_EQ(stream.width, 1U); + EXPECT_EQ(stream.height, 1U); + } + } + + { + // Legit case scaling down to more than 1x1. + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(600, 400); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (size_t i = 0; i < scales.size(); ++i) { + // Streams are backwards for some reason + const auto& stream = videoStreams[scales.size() - i - 1]; + const auto& scale = scales[i]; + if (scale == 200U) { + EXPECT_EQ(stream.width, 3U); + EXPECT_EQ(stream.height, 2U); + } else { + EXPECT_EQ(stream.width, 2U); + EXPECT_EQ(stream.height, 1U); + } + } + } + } +} + +TEST_F(VideoConduitTest, TestVideoEncodeResolutionAlignment) { + UniquePtr<MockVideoSink> sink(new MockVideoSink()); + + for (const auto& scales : {std::vector{1U}, std::vector{1U, 9U}}) { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + for (const auto& scale : scales) { + auto& encoding = codecConfig.mEncodings.emplace_back(); + encoding.constraints.scaleDownBy = scale; + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mLocalSsrcs = scales; + }); + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + + for (const auto& alignment : {2, 16, 39, 400, 1000}) { + // Test that requesting specific alignment always results in the expected + // number of layers and valid alignment. + rtc::VideoSinkWants wants; + wants.resolution_alignment = alignment; + mVideoConduit->AddOrUpdateSink(sink.get(), wants); + + const std::vector<webrtc::VideoStream> videoStreams = + Call()->CreateEncoderStreams(640, 480); + ASSERT_EQ(videoStreams.size(), scales.size()); + for (size_t i = 0; i < videoStreams.size(); ++i) { + // videoStreams is backwards + const auto& stream = videoStreams[videoStreams.size() - 1 - i]; + const auto& scale = scales[i]; + uint32_t expectation = + 480 / scale < static_cast<uint32_t>(alignment) ? 1 : 0; + EXPECT_EQ(stream.width % alignment, expectation) + << " for scale " << scale << " and alignment " << alignment; + EXPECT_EQ(stream.height % alignment, expectation); + } + } + } + + mVideoConduit->RemoveSink(sink.get()); +} + +TEST_F(VideoConduitTest, TestSettingRtpRtcpRsize) { + mControl.Update([&](auto& aControl) { + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + RtpRtcpConfig rtcpConf(webrtc::RtcpMode::kReducedSize); + + aControl.mReceiving = true; + aControl.mVideoRecvCodecs = {codecConfig}; + aControl.mVideoRecvRtpRtcpConfig = Some(rtcpConf); + aControl.mTransmitting = true; + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = Some(rtcpConf); + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kReducedSize); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_EQ(Call()->mVideoSendConfig->rtp.rtcp_mode, + webrtc::RtcpMode::kReducedSize); +} + +TEST_F(VideoConduitTest, TestRemoteSsrcDefault) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 0; + aControl.mLocalSsrcs = {1}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc, + Not(testing::AnyOf(0U, 1U))); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, ElementsAre(1U)); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestRemoteSsrcCollision) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mLocalSsrcs = {1}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + EXPECT_TRUE(Call()->mVideoReceiveConfig); + EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + EXPECT_TRUE(Call()->mVideoSendConfig); + EXPECT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, + ElementsAre(Not(testing::AnyOf(0U, 1U)))); + EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestLocalSsrcDefault) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mLocalSsrcs = {}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, + ElementsAre(Not(testing::AnyOf(0U, 1U)))); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestLocalSsrcCollision) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mLocalSsrcs = {2, 2}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + codecConfig.mEncodings.emplace_back(); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, + ElementsAre(2U, Not(testing::AnyOf(0U, 2U)))); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestLocalSsrcUnorderedCollision) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mLocalSsrcs = {2, 3, 2}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + for (int i = 0; i < 3; ++i) { + codecConfig.mEncodings.emplace_back(); + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, + ElementsAre(2U, 3U, Not(testing::AnyOf(0U, 2U)))); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestLocalAndRemoteSsrcCollision) { + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mLocalSsrcs = {1, 2, 2}; + VideoCodecConfig codecConfig(120, "VP8", EncodingConstraints()); + for (int i = 0; i < 3; ++i) { + codecConfig.mEncodings.emplace_back(); + } + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + aControl.mReceiving = true; + aControl.mTransmitting = true; + }); + ASSERT_TRUE(Call()->mVideoReceiveConfig); + ASSERT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + ASSERT_TRUE(Call()->mVideoSendConfig); + ASSERT_THAT(Call()->mVideoSendConfig->rtp.ssrcs, + ElementsAre(Not(testing::AnyOf(0U, 1U, 2U)), 2U, + Not(testing::AnyOf( + 0U, 1U, 2U, + Call()->mVideoReceiveConfig->rtp.remote_ssrc)))); + ASSERT_EQ(Call()->mVideoReceiveConfig->rtp.local_ssrc, + Call()->mVideoSendConfig->rtp.ssrcs[0]); +} + +TEST_F(VideoConduitTest, TestExternalRemoteSsrcCollision) { + auto other = MakeRefPtr<MockConduit>(); + mCallWrapper->RegisterConduit(other); + + // First the mControl update should trigger an UnsetRemoteSSRC(1) from us. + // Then we simulate another conduit using that same ssrc, which should trigger + // us to generate a fresh ssrc that is not 0 and not 1. + { + InSequence s; + EXPECT_CALL(*other, UnsetRemoteSSRC(1U)).Times(2); + EXPECT_CALL(*other, UnsetRemoteSSRC(Not(testing::AnyOf(0U, 1U)))); + } + + mControl.Update([&](auto& aControl) { + aControl.mRemoteSsrc = 1; + aControl.mReceiving = true; + }); + EXPECT_TRUE(Call()->mVideoReceiveConfig); + EXPECT_EQ(Call()->mVideoReceiveConfig->rtp.remote_ssrc, 1U); + + mozilla::Unused << WaitFor(InvokeAsync( + GetCurrentSerialEventTarget(), __func__, [wrapper = mCallWrapper] { + wrapper->UnsetRemoteSSRC(1); + return GenericPromise::CreateAndResolve(true, __func__); + })); + + EXPECT_TRUE(Call()->mVideoReceiveConfig); + EXPECT_THAT(Call()->mVideoReceiveConfig->rtp.remote_ssrc, + Not(testing::AnyOf(0U, 1U))); +} + +TEST_F(VideoConduitTest, TestVideoConfigurationH264) { + const int profileLevelId1 = 0x42E00D; + const int profileLevelId2 = 0x64000C; + const char* sprop1 = "foo bar"; + const char* sprop2 = "baz"; + + // Test that VideoConduit propagates H264 configuration data properly. + // We do two tests: + // - Test valid data in packetization mode 0 (SingleNALU) + // - Test different valid data in packetization mode 1 (NonInterleaved) + + { + mControl.Update([&](auto& aControl) { + aControl.mTransmitting = true; + VideoCodecConfigH264 h264{}; + h264.packetization_mode = 0; + h264.profile_level_id = profileLevelId1; + strncpy(h264.sprop_parameter_sets, sprop1, + sizeof(h264.sprop_parameter_sets) - 1); + VideoCodecConfig codecConfig(97, "H264", EncodingConstraints(), &h264); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + aControl.mVideoSendRtpRtcpConfig = + Some(RtpRtcpConfig(webrtc::RtcpMode::kCompound)); + }); + + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + auto& params = Call()->mVideoSendEncoderConfig->video_format.parameters; + EXPECT_EQ(params[cricket::kH264FmtpPacketizationMode], "0"); + EXPECT_EQ(params[cricket::kH264FmtpProfileLevelId], "42e00d"); + EXPECT_EQ(params[cricket::kH264FmtpSpropParameterSets], sprop1); + } + + { + mControl.Update([&](auto& aControl) { + VideoCodecConfigH264 h264{}; + h264.packetization_mode = 1; + h264.profile_level_id = profileLevelId2; + strncpy(h264.sprop_parameter_sets, sprop2, + sizeof(h264.sprop_parameter_sets) - 1); + VideoCodecConfig codecConfig(126, "H264", EncodingConstraints(), &h264); + codecConfig.mEncodings.emplace_back(); + aControl.mVideoSendCodec = Some(codecConfig); + }); + + ASSERT_TRUE(Call()->mVideoSendEncoderConfig); + auto& params = Call()->mVideoSendEncoderConfig->video_format.parameters; + EXPECT_EQ(params[cricket::kH264FmtpPacketizationMode], "1"); + EXPECT_EQ(params[cricket::kH264FmtpProfileLevelId], "64000c"); + EXPECT_EQ(params[cricket::kH264FmtpSpropParameterSets], sprop2); + } +} + +} // End namespace test. |