mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00

VideoStreamEncoder creates VideoEncoders. To pass an Environment to VideoEncoder, it should be available in the VideoStreamEncoder. Bug: webrtc:15860 Change-Id: Id89ac024ce61fdd9673bb66f03f94f243fc0c7f7 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/341840 Reviewed-by: Philip Eliasson <philipel@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#41861}
1161 lines
44 KiB
C++
1161 lines
44 KiB
C++
/*
|
|
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "video/video_send_stream_impl.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "api/array_view.h"
|
|
#include "api/call/bitrate_allocation.h"
|
|
#include "api/environment/environment.h"
|
|
#include "api/environment/environment_factory.h"
|
|
#include "api/rtc_event_log/rtc_event_log.h"
|
|
#include "api/rtp_parameters.h"
|
|
#include "api/task_queue/task_queue_base.h"
|
|
#include "api/task_queue/task_queue_factory.h"
|
|
#include "api/units/data_rate.h"
|
|
#include "api/units/time_delta.h"
|
|
#include "api/units/timestamp.h"
|
|
#include "api/video/encoded_image.h"
|
|
#include "api/video/video_bitrate_allocation.h"
|
|
#include "api/video/video_layers_allocation.h"
|
|
#include "api/video_codecs/video_encoder.h"
|
|
#include "call/bitrate_allocator.h"
|
|
#include "call/rtp_config.h"
|
|
#include "call/rtp_video_sender_interface.h"
|
|
#include "call/test/mock_bitrate_allocator.h"
|
|
#include "call/test/mock_rtp_transport_controller_send.h"
|
|
#include "call/video_send_stream.h"
|
|
#include "modules/pacing/packet_router.h"
|
|
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
|
#include "modules/rtp_rtcp/source/rtp_sequence_number_map.h"
|
|
#include "modules/video_coding/include/video_codec_interface.h"
|
|
#include "rtc_base/experiments/alr_experiment.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
#include "test/mock_transport.h"
|
|
#include "test/scoped_key_value_config.h"
|
|
#include "test/time_controller/simulated_time_controller.h"
|
|
#include "video/config/video_encoder_config.h"
|
|
#include "video/send_delay_stats.h"
|
|
#include "video/send_statistics_proxy.h"
|
|
#include "video/test/mock_video_stream_encoder.h"
|
|
#include "video/video_stream_encoder.h"
|
|
#include "video/video_stream_encoder_interface.h"
|
|
|
|
namespace webrtc {
|
|
|
|
bool operator==(const BitrateAllocationUpdate& a,
|
|
const BitrateAllocationUpdate& b) {
|
|
return a.target_bitrate == b.target_bitrate &&
|
|
a.round_trip_time == b.round_trip_time &&
|
|
a.packet_loss_ratio == b.packet_loss_ratio;
|
|
}
|
|
|
|
namespace internal {
|
|
namespace {
|
|
using ::testing::_;
|
|
using ::testing::AllOf;
|
|
using ::testing::AnyNumber;
|
|
using ::testing::Field;
|
|
using ::testing::Invoke;
|
|
using ::testing::Mock;
|
|
using ::testing::NiceMock;
|
|
using ::testing::Return;
|
|
using ::testing::SaveArg;
|
|
|
|
constexpr int64_t kDefaultInitialBitrateBps = 333000;
|
|
const double kDefaultBitratePriority = 0.5;
|
|
|
|
const float kAlrProbingExperimentPaceMultiplier = 1.0f;
|
|
std::string GetAlrProbingExperimentString() {
|
|
return std::string(
|
|
AlrExperimentSettings::kScreenshareProbingBweExperimentName) +
|
|
"/1.0,2875,80,40,-60,3/";
|
|
}
|
|
class MockRtpVideoSender : public RtpVideoSenderInterface {
|
|
public:
|
|
MOCK_METHOD(void, SetSending, (bool sending), (override));
|
|
MOCK_METHOD(bool, IsActive, (), (override));
|
|
MOCK_METHOD(void, OnNetworkAvailability, (bool), (override));
|
|
MOCK_METHOD((std::map<uint32_t, RtpState>),
|
|
GetRtpStates,
|
|
(),
|
|
(const, override));
|
|
MOCK_METHOD((std::map<uint32_t, RtpPayloadState>),
|
|
GetRtpPayloadStates,
|
|
(),
|
|
(const, override));
|
|
MOCK_METHOD(void, DeliverRtcp, (const uint8_t*, size_t), (override));
|
|
MOCK_METHOD(void,
|
|
OnBitrateAllocationUpdated,
|
|
(const VideoBitrateAllocation&),
|
|
(override));
|
|
MOCK_METHOD(void,
|
|
OnVideoLayersAllocationUpdated,
|
|
(const VideoLayersAllocation&),
|
|
(override));
|
|
MOCK_METHOD(EncodedImageCallback::Result,
|
|
OnEncodedImage,
|
|
(const EncodedImage&, const CodecSpecificInfo*),
|
|
(override));
|
|
MOCK_METHOD(void, OnTransportOverheadChanged, (size_t), (override));
|
|
MOCK_METHOD(void,
|
|
OnBitrateUpdated,
|
|
(BitrateAllocationUpdate, int),
|
|
(override));
|
|
MOCK_METHOD(uint32_t, GetPayloadBitrateBps, (), (const, override));
|
|
MOCK_METHOD(uint32_t, GetProtectionBitrateBps, (), (const, override));
|
|
MOCK_METHOD(void, SetEncodingData, (size_t, size_t, size_t), (override));
|
|
MOCK_METHOD(std::vector<RtpSequenceNumberMap::Info>,
|
|
GetSentRtpPacketInfos,
|
|
(uint32_t ssrc, rtc::ArrayView<const uint16_t> sequence_numbers),
|
|
(const, override));
|
|
|
|
MOCK_METHOD(void, SetFecAllowed, (bool fec_allowed), (override));
|
|
};
|
|
|
|
BitrateAllocationUpdate CreateAllocation(int bitrate_bps) {
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = DataRate::BitsPerSec(bitrate_bps);
|
|
update.packet_loss_ratio = 0;
|
|
update.round_trip_time = TimeDelta::Zero();
|
|
return update;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class VideoSendStreamImplTest : public ::testing::Test {
|
|
protected:
|
|
VideoSendStreamImplTest()
|
|
: time_controller_(Timestamp::Seconds(1000)),
|
|
config_(&transport_),
|
|
send_delay_stats_(time_controller_.GetClock()),
|
|
encoder_queue_(time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
|
|
"encoder_queue",
|
|
TaskQueueFactory::Priority::NORMAL)),
|
|
stats_proxy_(time_controller_.GetClock(),
|
|
config_,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
field_trials_) {
|
|
config_.rtp.ssrcs.push_back(8080);
|
|
config_.rtp.payload_type = 1;
|
|
|
|
EXPECT_CALL(transport_controller_, packet_router())
|
|
.WillRepeatedly(Return(&packet_router_));
|
|
EXPECT_CALL(transport_controller_, CreateRtpVideoSender)
|
|
.WillRepeatedly(Return(&rtp_video_sender_));
|
|
ON_CALL(rtp_video_sender_, IsActive()).WillByDefault(Invoke([&]() {
|
|
return rtp_sending_;
|
|
}));
|
|
ON_CALL(rtp_video_sender_, SetSending)
|
|
.WillByDefault(SaveArg<0>(&rtp_sending_));
|
|
}
|
|
~VideoSendStreamImplTest() {}
|
|
|
|
VideoEncoderConfig TestVideoEncoderConfig(
|
|
VideoEncoderConfig::ContentType content_type =
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
int initial_encoder_max_bitrate = kDefaultInitialBitrateBps,
|
|
double initial_encoder_bitrate_priority = kDefaultBitratePriority) {
|
|
VideoEncoderConfig encoder_config;
|
|
encoder_config.max_bitrate_bps = initial_encoder_max_bitrate;
|
|
encoder_config.bitrate_priority = initial_encoder_bitrate_priority;
|
|
encoder_config.content_type = content_type;
|
|
encoder_config.simulcast_layers.push_back(VideoStream());
|
|
encoder_config.simulcast_layers.back().active = true;
|
|
return encoder_config;
|
|
}
|
|
|
|
std::unique_ptr<VideoSendStreamImpl> CreateVideoSendStreamImpl(
|
|
VideoEncoderConfig encoder_config) {
|
|
EXPECT_CALL(bitrate_allocator_, GetStartBitrate).WillOnce(Return(123000));
|
|
|
|
std::map<uint32_t, RtpState> suspended_ssrcs;
|
|
std::map<uint32_t, RtpPayloadState> suspended_payload_states;
|
|
|
|
std::unique_ptr<NiceMock<MockVideoStreamEncoder>> video_stream_encoder =
|
|
std::make_unique<NiceMock<MockVideoStreamEncoder>>();
|
|
video_stream_encoder_ = video_stream_encoder.get();
|
|
|
|
auto ret = std::make_unique<VideoSendStreamImpl>(
|
|
CreateEnvironment(&field_trials_, time_controller_.GetClock(),
|
|
time_controller_.GetTaskQueueFactory()),
|
|
/*num_cpu_cores=*/1,
|
|
/*call_stats=*/nullptr, &transport_controller_,
|
|
/*metronome=*/nullptr, &bitrate_allocator_, &send_delay_stats_,
|
|
config_.Copy(), std::move(encoder_config), suspended_ssrcs,
|
|
suspended_payload_states,
|
|
/*fec_controller=*/nullptr, std::move(video_stream_encoder));
|
|
|
|
// The call to GetStartBitrate() executes asynchronously on the tq.
|
|
// Ensure all tasks get to run.
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
|
|
return ret;
|
|
}
|
|
|
|
protected:
|
|
GlobalSimulatedTimeController time_controller_;
|
|
webrtc::test::ScopedKeyValueConfig field_trials_;
|
|
NiceMock<MockTransport> transport_;
|
|
NiceMock<MockRtpTransportControllerSend> transport_controller_;
|
|
NiceMock<MockBitrateAllocator> bitrate_allocator_;
|
|
NiceMock<MockVideoStreamEncoder>* video_stream_encoder_ = nullptr;
|
|
NiceMock<MockRtpVideoSender> rtp_video_sender_;
|
|
bool rtp_sending_ = false;
|
|
|
|
RtcEventLogNull event_log_;
|
|
VideoSendStream::Config config_;
|
|
SendDelayStats send_delay_stats_;
|
|
std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue_;
|
|
SendStatisticsProxy stats_proxy_;
|
|
PacketRouter packet_router_;
|
|
};
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
NotRegistersAsBitrateObserverOnStartIfNoActiveEncodings) {
|
|
VideoEncoderConfig encoder_config = TestVideoEncoderConfig();
|
|
encoder_config.simulcast_layers[0].active = false;
|
|
auto vss_impl = CreateVideoSendStreamImpl(std::move(encoder_config));
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _)).Times(0);
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(0);
|
|
|
|
vss_impl->Start();
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
RegistersAsBitrateObserverOnStartIfHasActiveEncodings) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _));
|
|
vss_impl->Start();
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
DeRegistersAsBitrateObserverIfNoActiveEncodings) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _));
|
|
vss_impl->Start();
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
VideoEncoderConfig no_active_encodings = TestVideoEncoderConfig();
|
|
no_active_encodings.simulcast_layers[0].active = false;
|
|
|
|
vss_impl->ReconfigureVideoEncoder(std::move(no_active_encodings));
|
|
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
::testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
DoNotRegistersAsBitrateObserverOnStrayEncodedImage) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
EncodedImage encoded_image;
|
|
CodecSpecificInfo codec_specific;
|
|
ON_CALL(rtp_video_sender_, OnEncodedImage)
|
|
.WillByDefault(Return(
|
|
EncodedImageCallback::Result(EncodedImageCallback::Result::OK)));
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
|
|
.Times(AnyNumber());
|
|
vss_impl->Start();
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
// VideoSendStreamImpl gets an allocated bitrate.
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
// A frame is encoded.
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
|
|
// Expect allocation to be removed if encoder stop producing frames.
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
time_controller_.AdvanceTime(TimeDelta::Seconds(5));
|
|
Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _)).Times(0);
|
|
|
|
VideoEncoderConfig no_active_encodings = TestVideoEncoderConfig();
|
|
no_active_encodings.simulcast_layers[0].active = false;
|
|
vss_impl->ReconfigureVideoEncoder(std::move(no_active_encodings));
|
|
|
|
// Expect that allocation in not resumed if a stray encoded image is received.
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChange) {
|
|
const bool kSuspend = false;
|
|
config_.suspend_below_min_bitrate = kSuspend;
|
|
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
|
|
1);
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
config_.rtp.ssrcs.emplace_back(2);
|
|
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
vss_impl->Start();
|
|
|
|
// QVGA + VGA configuration matching defaults in
|
|
// media/engine/simulcast.cc.
|
|
VideoStream qvga_stream;
|
|
qvga_stream.width = 320;
|
|
qvga_stream.height = 180;
|
|
qvga_stream.max_framerate = 30;
|
|
qvga_stream.min_bitrate_bps = 30000;
|
|
qvga_stream.target_bitrate_bps = 150000;
|
|
qvga_stream.max_bitrate_bps = 200000;
|
|
qvga_stream.max_qp = 56;
|
|
qvga_stream.bitrate_priority = 1;
|
|
|
|
VideoStream vga_stream;
|
|
vga_stream.width = 640;
|
|
vga_stream.height = 360;
|
|
vga_stream.max_framerate = 30;
|
|
vga_stream.min_bitrate_bps = 150000;
|
|
vga_stream.target_bitrate_bps = 500000;
|
|
vga_stream.max_bitrate_bps = 700000;
|
|
vga_stream.max_qp = 56;
|
|
vga_stream.bitrate_priority = 1;
|
|
|
|
int min_transmit_bitrate_bps = 30000;
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](BitrateAllocatorObserver*, MediaStreamAllocationConfig config) {
|
|
EXPECT_EQ(config.min_bitrate_bps,
|
|
static_cast<uint32_t>(min_transmit_bitrate_bps));
|
|
EXPECT_EQ(config.max_bitrate_bps,
|
|
static_cast<uint32_t>(qvga_stream.max_bitrate_bps +
|
|
vga_stream.max_bitrate_bps));
|
|
if (config.pad_up_bitrate_bps != 0) {
|
|
EXPECT_EQ(config.pad_up_bitrate_bps,
|
|
static_cast<uint32_t>(qvga_stream.target_bitrate_bps +
|
|
vga_stream.min_bitrate_bps));
|
|
}
|
|
EXPECT_EQ(config.enforce_min_bitrate, !kSuspend);
|
|
}));
|
|
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{qvga_stream, vga_stream}, false,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
min_transmit_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, UpdatesObserverOnConfigurationChangeWithAlr) {
|
|
const bool kSuspend = false;
|
|
config_.suspend_below_min_bitrate = kSuspend;
|
|
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
|
|
1);
|
|
config_.periodic_alr_bandwidth_probing = true;
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
config_.rtp.ssrcs.emplace_back(2);
|
|
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
vss_impl->Start();
|
|
|
|
// Simulcast screenshare.
|
|
VideoStream low_stream;
|
|
low_stream.width = 1920;
|
|
low_stream.height = 1080;
|
|
low_stream.max_framerate = 5;
|
|
low_stream.min_bitrate_bps = 30000;
|
|
low_stream.target_bitrate_bps = 200000;
|
|
low_stream.max_bitrate_bps = 1000000;
|
|
low_stream.num_temporal_layers = 2;
|
|
low_stream.max_qp = 56;
|
|
low_stream.bitrate_priority = 1;
|
|
|
|
VideoStream high_stream;
|
|
high_stream.width = 1920;
|
|
high_stream.height = 1080;
|
|
high_stream.max_framerate = 30;
|
|
high_stream.min_bitrate_bps = 60000;
|
|
high_stream.target_bitrate_bps = 1250000;
|
|
high_stream.max_bitrate_bps = 1250000;
|
|
high_stream.num_temporal_layers = 2;
|
|
high_stream.max_qp = 56;
|
|
high_stream.bitrate_priority = 1;
|
|
|
|
// With ALR probing, this will be the padding target instead of
|
|
// low_stream.target_bitrate_bps + high_stream.min_bitrate_bps.
|
|
int min_transmit_bitrate_bps = 400000;
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](BitrateAllocatorObserver*, MediaStreamAllocationConfig config) {
|
|
EXPECT_EQ(config.min_bitrate_bps,
|
|
static_cast<uint32_t>(low_stream.min_bitrate_bps));
|
|
EXPECT_EQ(config.max_bitrate_bps,
|
|
static_cast<uint32_t>(low_stream.max_bitrate_bps +
|
|
high_stream.max_bitrate_bps));
|
|
if (config.pad_up_bitrate_bps != 0) {
|
|
EXPECT_EQ(config.pad_up_bitrate_bps,
|
|
static_cast<uint32_t>(min_transmit_bitrate_bps));
|
|
}
|
|
EXPECT_EQ(config.enforce_min_bitrate, !kSuspend);
|
|
}));
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{low_stream, high_stream}, false,
|
|
VideoEncoderConfig::ContentType::kScreen, min_transmit_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
UpdatesObserverOnConfigurationChangeWithSimulcastVideoHysteresis) {
|
|
test::ScopedKeyValueConfig hysteresis_experiment(
|
|
field_trials_, "WebRTC-VideoRateControl/video_hysteresis:1.25/");
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
config_.rtp.ssrcs.emplace_back(2);
|
|
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
vss_impl->Start();
|
|
// 2-layer video simulcast.
|
|
VideoStream low_stream;
|
|
low_stream.width = 320;
|
|
low_stream.height = 240;
|
|
low_stream.max_framerate = 30;
|
|
low_stream.min_bitrate_bps = 30000;
|
|
low_stream.target_bitrate_bps = 100000;
|
|
low_stream.max_bitrate_bps = 200000;
|
|
low_stream.max_qp = 56;
|
|
low_stream.bitrate_priority = 1;
|
|
|
|
VideoStream high_stream;
|
|
high_stream.width = 640;
|
|
high_stream.height = 480;
|
|
high_stream.max_framerate = 30;
|
|
high_stream.min_bitrate_bps = 150000;
|
|
high_stream.target_bitrate_bps = 500000;
|
|
high_stream.max_bitrate_bps = 750000;
|
|
high_stream.max_qp = 56;
|
|
high_stream.bitrate_priority = 1;
|
|
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
|
|
.WillRepeatedly(Invoke([&](BitrateAllocatorObserver*,
|
|
MediaStreamAllocationConfig config) {
|
|
EXPECT_EQ(config.min_bitrate_bps,
|
|
static_cast<uint32_t>(low_stream.min_bitrate_bps));
|
|
EXPECT_EQ(config.max_bitrate_bps,
|
|
static_cast<uint32_t>(low_stream.max_bitrate_bps +
|
|
high_stream.max_bitrate_bps));
|
|
if (config.pad_up_bitrate_bps != 0) {
|
|
EXPECT_EQ(config.pad_up_bitrate_bps,
|
|
static_cast<uint32_t>(low_stream.target_bitrate_bps +
|
|
1.25 * high_stream.min_bitrate_bps));
|
|
}
|
|
}));
|
|
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{low_stream, high_stream}, false,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
/*min_transmit_bitrate_bps=*/
|
|
0);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, SetsScreensharePacingFactorWithFeedback) {
|
|
test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString());
|
|
|
|
constexpr int kId = 1;
|
|
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
|
|
kId);
|
|
EXPECT_CALL(transport_controller_,
|
|
SetPacingFactor(kAlrProbingExperimentPaceMultiplier))
|
|
.Times(1);
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
vss_impl->Start();
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, DoesNotSetPacingFactorWithoutFeedback) {
|
|
test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString());
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
EXPECT_CALL(transport_controller_, SetPacingFactor(_)).Times(0);
|
|
vss_impl->Start();
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, ForwardsVideoBitrateAllocationWhenEnabled) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
|
|
EXPECT_CALL(transport_controller_, SetPacingFactor(_)).Times(0);
|
|
VideoStreamEncoderInterface::EncoderSink* const sink =
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get());
|
|
vss_impl->Start();
|
|
// Populate a test instance of video bitrate allocation.
|
|
VideoBitrateAllocation alloc;
|
|
alloc.SetBitrate(0, 0, 10000);
|
|
alloc.SetBitrate(0, 1, 20000);
|
|
alloc.SetBitrate(1, 0, 30000);
|
|
alloc.SetBitrate(1, 1, 40000);
|
|
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] {
|
|
// Encoder starts out paused, don't forward allocation.
|
|
|
|
sink->OnBitrateAllocationUpdated(alloc);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
// Unpause encoder, allocation should be passed through.
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
encoder_queue_->PostTask([&] { sink->OnBitrateAllocationUpdated(alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
// Pause encoder again, and block allocations.
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(0));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(0));
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] { sink->OnBitrateAllocationUpdated(alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, ThrottlesVideoBitrateAllocationWhenTooSimilar) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
vss_impl->Start();
|
|
// Unpause encoder, to allows allocations to be passed through.
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
VideoStreamEncoderInterface::EncoderSink* const sink =
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get());
|
|
|
|
// Populate a test instance of video bitrate allocation.
|
|
VideoBitrateAllocation alloc;
|
|
alloc.SetBitrate(0, 0, 10000);
|
|
alloc.SetBitrate(0, 1, 20000);
|
|
alloc.SetBitrate(1, 0, 30000);
|
|
alloc.SetBitrate(1, 1, 40000);
|
|
|
|
// Initial value.
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
encoder_queue_->PostTask([&] { sink->OnBitrateAllocationUpdated(alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
VideoBitrateAllocation updated_alloc = alloc;
|
|
// Needs 10% increase in bitrate to trigger immediate forward.
|
|
const uint32_t base_layer_min_update_bitrate_bps =
|
|
alloc.GetBitrate(0, 0) + alloc.get_sum_bps() / 10;
|
|
|
|
// Too small increase, don't forward.
|
|
updated_alloc.SetBitrate(0, 0, base_layer_min_update_bitrate_bps - 1);
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(_)).Times(0);
|
|
encoder_queue_->PostTask(
|
|
[&] { sink->OnBitrateAllocationUpdated(updated_alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
// Large enough increase, do forward.
|
|
updated_alloc.SetBitrate(0, 0, base_layer_min_update_bitrate_bps);
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(updated_alloc))
|
|
.Times(1);
|
|
encoder_queue_->PostTask(
|
|
[&] { sink->OnBitrateAllocationUpdated(updated_alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
// This is now a decrease compared to last forward allocation,
|
|
// forward immediately.
|
|
updated_alloc.SetBitrate(0, 0, base_layer_min_update_bitrate_bps - 1);
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(updated_alloc))
|
|
.Times(1);
|
|
encoder_queue_->PostTask(
|
|
[&] { sink->OnBitrateAllocationUpdated(updated_alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, ForwardsVideoBitrateAllocationOnLayerChange) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
|
|
vss_impl->Start();
|
|
// Unpause encoder, to allows allocations to be passed through.
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
VideoStreamEncoderInterface::EncoderSink* const sink =
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get());
|
|
|
|
// Populate a test instance of video bitrate allocation.
|
|
VideoBitrateAllocation alloc;
|
|
alloc.SetBitrate(0, 0, 10000);
|
|
alloc.SetBitrate(0, 1, 20000);
|
|
alloc.SetBitrate(1, 0, 30000);
|
|
alloc.SetBitrate(1, 1, 40000);
|
|
|
|
// Initial value.
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
sink->OnBitrateAllocationUpdated(alloc);
|
|
|
|
// Move some bitrate from one layer to a new one, but keep sum the
|
|
// same. Since layout has changed, immediately trigger forward.
|
|
VideoBitrateAllocation updated_alloc = alloc;
|
|
updated_alloc.SetBitrate(2, 0, 10000);
|
|
updated_alloc.SetBitrate(1, 1, alloc.GetBitrate(1, 1) - 10000);
|
|
EXPECT_EQ(alloc.get_sum_bps(), updated_alloc.get_sum_bps());
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(updated_alloc))
|
|
.Times(1);
|
|
encoder_queue_->PostTask(
|
|
[&] { sink->OnBitrateAllocationUpdated(updated_alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, ForwardsVideoBitrateAllocationAfterTimeout) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(
|
|
TestVideoEncoderConfig(VideoEncoderConfig::ContentType::kScreen));
|
|
vss_impl->Start();
|
|
const uint32_t kBitrateBps = 100000;
|
|
// Unpause encoder, to allows allocations to be passed through.
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillRepeatedly(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
VideoStreamEncoderInterface::EncoderSink* const sink =
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get());
|
|
|
|
// Populate a test instance of video bitrate allocation.
|
|
VideoBitrateAllocation alloc;
|
|
|
|
alloc.SetBitrate(0, 0, 10000);
|
|
alloc.SetBitrate(0, 1, 20000);
|
|
alloc.SetBitrate(1, 0, 30000);
|
|
alloc.SetBitrate(1, 1, 40000);
|
|
|
|
EncodedImage encoded_image;
|
|
CodecSpecificInfo codec_specific;
|
|
EXPECT_CALL(rtp_video_sender_, OnEncodedImage)
|
|
.WillRepeatedly(Return(
|
|
EncodedImageCallback::Result(EncodedImageCallback::Result::OK)));
|
|
// Max time we will throttle similar video bitrate allocations.
|
|
static constexpr int64_t kMaxVbaThrottleTimeMs = 500;
|
|
|
|
{
|
|
// Initial value.
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
encoder_queue_->PostTask([&] { sink->OnBitrateAllocationUpdated(alloc); });
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
{
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] {
|
|
// Sending same allocation again, this one should be throttled.
|
|
sink->OnBitrateAllocationUpdated(alloc);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
time_controller_.AdvanceTime(TimeDelta::Millis(kMaxVbaThrottleTimeMs));
|
|
{
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
encoder_queue_->PostTask([&] {
|
|
// Sending similar allocation again after timeout, should
|
|
// forward.
|
|
sink->OnBitrateAllocationUpdated(alloc);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
{
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] {
|
|
// Sending similar allocation again without timeout, throttle.
|
|
sink->OnBitrateAllocationUpdated(alloc);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
{
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] {
|
|
// Send encoded image, should be a noop.
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
{
|
|
// Advance time and send encoded image, this should wake up and
|
|
// send cached bitrate allocation.
|
|
time_controller_.AdvanceTime(TimeDelta::Millis(kMaxVbaThrottleTimeMs));
|
|
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(1);
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
{
|
|
// Advance time and send encoded image, there should be no
|
|
// cached allocation to send.
|
|
time_controller_.AdvanceTime(TimeDelta::Millis(kMaxVbaThrottleTimeMs));
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateAllocationUpdated(alloc)).Times(0);
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
}
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, PriorityBitrateConfigInactiveByDefault) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
EXPECT_CALL(
|
|
bitrate_allocator_,
|
|
AddObserver(
|
|
vss_impl.get(),
|
|
Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 0)));
|
|
vss_impl->Start();
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, PriorityBitrateConfigAffectsAV1) {
|
|
test::ScopedFieldTrials override_priority_bitrate(
|
|
"WebRTC-AV1-OverridePriorityBitrate/bitrate:20000/");
|
|
config_.rtp.payload_name = "AV1";
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
EXPECT_CALL(
|
|
bitrate_allocator_,
|
|
AddObserver(
|
|
vss_impl.get(),
|
|
Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 20000)));
|
|
vss_impl->Start();
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest,
|
|
PriorityBitrateConfigSurvivesConfigurationChange) {
|
|
VideoStream qvga_stream;
|
|
qvga_stream.width = 320;
|
|
qvga_stream.height = 180;
|
|
qvga_stream.max_framerate = 30;
|
|
qvga_stream.min_bitrate_bps = 30000;
|
|
qvga_stream.target_bitrate_bps = 150000;
|
|
qvga_stream.max_bitrate_bps = 200000;
|
|
qvga_stream.max_qp = 56;
|
|
qvga_stream.bitrate_priority = 1;
|
|
|
|
int min_transmit_bitrate_bps = 30000;
|
|
|
|
test::ScopedFieldTrials override_priority_bitrate(
|
|
"WebRTC-AV1-OverridePriorityBitrate/bitrate:20000/");
|
|
config_.rtp.payload_name = "AV1";
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
EXPECT_CALL(
|
|
bitrate_allocator_,
|
|
AddObserver(
|
|
vss_impl.get(),
|
|
Field(&MediaStreamAllocationConfig::priority_bitrate_bps, 20000)))
|
|
.Times(2);
|
|
vss_impl->Start();
|
|
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{qvga_stream}, false,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
min_transmit_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(1);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, CallsVideoStreamEncoderOnBitrateUpdate) {
|
|
const bool kSuspend = false;
|
|
config_.suspend_below_min_bitrate = kSuspend;
|
|
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
|
|
1);
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
vss_impl->Start();
|
|
VideoStream qvga_stream;
|
|
qvga_stream.width = 320;
|
|
qvga_stream.height = 180;
|
|
qvga_stream.max_framerate = 30;
|
|
qvga_stream.min_bitrate_bps = 30000;
|
|
qvga_stream.target_bitrate_bps = 150000;
|
|
qvga_stream.max_bitrate_bps = 200000;
|
|
qvga_stream.max_qp = 56;
|
|
qvga_stream.bitrate_priority = 1;
|
|
|
|
int min_transmit_bitrate_bps = 30000;
|
|
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{qvga_stream}, false,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
min_transmit_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
|
|
const DataRate network_constrained_rate =
|
|
DataRate::BitsPerSec(qvga_stream.target_bitrate_bps);
|
|
BitrateAllocationUpdate update;
|
|
update.target_bitrate = network_constrained_rate;
|
|
update.stable_target_bitrate = network_constrained_rate;
|
|
update.round_trip_time = TimeDelta::Millis(1);
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.WillOnce(Return(network_constrained_rate.bps()));
|
|
EXPECT_CALL(
|
|
*video_stream_encoder_,
|
|
OnBitrateUpdated(network_constrained_rate, network_constrained_rate,
|
|
network_constrained_rate, 0, _, 0));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(update);
|
|
|
|
// Test allocation where the link allocation is larger than the
|
|
// target, meaning we have some headroom on the link.
|
|
const DataRate qvga_max_bitrate =
|
|
DataRate::BitsPerSec(qvga_stream.max_bitrate_bps);
|
|
const DataRate headroom = DataRate::BitsPerSec(50000);
|
|
const DataRate rate_with_headroom = qvga_max_bitrate + headroom;
|
|
update.target_bitrate = rate_with_headroom;
|
|
update.stable_target_bitrate = rate_with_headroom;
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.WillOnce(Return(rate_with_headroom.bps()));
|
|
EXPECT_CALL(*video_stream_encoder_,
|
|
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
|
|
rate_with_headroom, 0, _, 0));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(update);
|
|
|
|
// Add protection bitrate to the mix, this should be subtracted
|
|
// from the headroom.
|
|
const uint32_t protection_bitrate_bps = 10000;
|
|
EXPECT_CALL(rtp_video_sender_, GetProtectionBitrateBps())
|
|
.WillOnce(Return(protection_bitrate_bps));
|
|
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.WillOnce(Return(rate_with_headroom.bps()));
|
|
const DataRate headroom_minus_protection =
|
|
rate_with_headroom - DataRate::BitsPerSec(protection_bitrate_bps);
|
|
EXPECT_CALL(*video_stream_encoder_,
|
|
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
|
|
headroom_minus_protection, 0, _, 0));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(update);
|
|
|
|
// Protection bitrate exceeds head room, link allocation should be
|
|
// capped to target bitrate.
|
|
EXPECT_CALL(rtp_video_sender_, GetProtectionBitrateBps())
|
|
.WillOnce(Return(headroom.bps() + 1000));
|
|
EXPECT_CALL(rtp_video_sender_, OnBitrateUpdated(update, _));
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.WillOnce(Return(rate_with_headroom.bps()));
|
|
EXPECT_CALL(*video_stream_encoder_,
|
|
OnBitrateUpdated(qvga_max_bitrate, qvga_max_bitrate,
|
|
qvga_max_bitrate, 0, _, 0));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(update);
|
|
|
|
// Set rates to zero on stop.
|
|
EXPECT_CALL(*video_stream_encoder_,
|
|
OnBitrateUpdated(DataRate::Zero(), DataRate::Zero(),
|
|
DataRate::Zero(), 0, 0, 0));
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, DisablesPaddingOnPausedEncoder) {
|
|
int padding_bitrate = 0;
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
|
|
// Capture padding bitrate for testing.
|
|
EXPECT_CALL(bitrate_allocator_, AddObserver(vss_impl.get(), _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](BitrateAllocatorObserver*, MediaStreamAllocationConfig config) {
|
|
padding_bitrate = config.pad_up_bitrate_bps;
|
|
}));
|
|
// If observer is removed, no padding will be sent.
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get()))
|
|
.WillRepeatedly(
|
|
Invoke([&](BitrateAllocatorObserver*) { padding_bitrate = 0; }));
|
|
|
|
EXPECT_CALL(rtp_video_sender_, OnEncodedImage)
|
|
.WillRepeatedly(Return(
|
|
EncodedImageCallback::Result(EncodedImageCallback::Result::OK)));
|
|
const bool kSuspend = false;
|
|
config_.suspend_below_min_bitrate = kSuspend;
|
|
config_.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri,
|
|
1);
|
|
VideoStream qvga_stream;
|
|
qvga_stream.width = 320;
|
|
qvga_stream.height = 180;
|
|
qvga_stream.max_framerate = 30;
|
|
qvga_stream.min_bitrate_bps = 30000;
|
|
qvga_stream.target_bitrate_bps = 150000;
|
|
qvga_stream.max_bitrate_bps = 200000;
|
|
qvga_stream.max_qp = 56;
|
|
qvga_stream.bitrate_priority = 1;
|
|
|
|
int min_transmit_bitrate_bps = 30000;
|
|
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
vss_impl->Start();
|
|
// Starts without padding.
|
|
EXPECT_EQ(0, padding_bitrate);
|
|
encoder_queue_->PostTask([&] {
|
|
// Reconfigure e.g. due to a fake frame.
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{qvga_stream}, false,
|
|
VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
min_transmit_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
// Still no padding because no actual frames were passed, only
|
|
// reconfiguration happened.
|
|
EXPECT_EQ(0, padding_bitrate);
|
|
|
|
// Unpause encoder.
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
|
|
encoder_queue_->PostTask([&] {
|
|
// A frame is encoded.
|
|
EncodedImage encoded_image;
|
|
CodecSpecificInfo codec_specific;
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
// Only after actual frame is encoded are we enabling the padding.
|
|
EXPECT_GT(padding_bitrate, 0);
|
|
|
|
time_controller_.AdvanceTime(TimeDelta::Seconds(5));
|
|
// Since no more frames are sent the last 5s, no padding is supposed to be
|
|
// sent.
|
|
EXPECT_EQ(0, padding_bitrate);
|
|
testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, KeepAliveOnDroppedFrame) {
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig());
|
|
EXPECT_CALL(bitrate_allocator_, RemoveObserver(vss_impl.get())).Times(0);
|
|
vss_impl->Start();
|
|
const uint32_t kBitrateBps = 100000;
|
|
EXPECT_CALL(rtp_video_sender_, GetPayloadBitrateBps())
|
|
.Times(1)
|
|
.WillOnce(Return(kBitrateBps));
|
|
static_cast<BitrateAllocatorObserver*>(vss_impl.get())
|
|
->OnBitrateUpdated(CreateAllocation(kBitrateBps));
|
|
encoder_queue_->PostTask([&] {
|
|
// Keep the stream from deallocating by dropping a frame.
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnDroppedFrame(EncodedImageCallback::DropReason::kDroppedByEncoder);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Seconds(2));
|
|
testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
vss_impl->Stop();
|
|
}
|
|
|
|
TEST_F(VideoSendStreamImplTest, ConfiguresBitratesForSvc) {
|
|
struct TestConfig {
|
|
bool screenshare = false;
|
|
bool alr = false;
|
|
int min_padding_bitrate_bps = 0;
|
|
};
|
|
|
|
std::vector<TestConfig> test_variants;
|
|
for (bool screenshare : {false, true}) {
|
|
for (bool alr : {false, true}) {
|
|
for (int min_padding : {0, 400000}) {
|
|
test_variants.push_back({screenshare, alr, min_padding});
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const TestConfig& test_config : test_variants) {
|
|
const bool kSuspend = false;
|
|
config_.suspend_below_min_bitrate = kSuspend;
|
|
config_.rtp.extensions.emplace_back(
|
|
RtpExtension::kTransportSequenceNumberUri, 1);
|
|
config_.periodic_alr_bandwidth_probing = test_config.alr;
|
|
auto vss_impl = CreateVideoSendStreamImpl(TestVideoEncoderConfig(
|
|
test_config.screenshare
|
|
? VideoEncoderConfig::ContentType::kScreen
|
|
: VideoEncoderConfig::ContentType::kRealtimeVideo));
|
|
|
|
vss_impl->Start();
|
|
|
|
// Svc
|
|
VideoStream stream;
|
|
stream.width = 1920;
|
|
stream.height = 1080;
|
|
stream.max_framerate = 30;
|
|
stream.min_bitrate_bps = 60000;
|
|
stream.target_bitrate_bps = 6000000;
|
|
stream.max_bitrate_bps = 1250000;
|
|
stream.num_temporal_layers = 2;
|
|
stream.max_qp = 56;
|
|
stream.bitrate_priority = 1;
|
|
|
|
config_.rtp.ssrcs.emplace_back(1);
|
|
config_.rtp.ssrcs.emplace_back(2);
|
|
|
|
EXPECT_CALL(
|
|
bitrate_allocator_,
|
|
AddObserver(
|
|
vss_impl.get(),
|
|
AllOf(Field(&MediaStreamAllocationConfig::min_bitrate_bps,
|
|
static_cast<uint32_t>(stream.min_bitrate_bps)),
|
|
Field(&MediaStreamAllocationConfig::max_bitrate_bps,
|
|
static_cast<uint32_t>(stream.max_bitrate_bps)),
|
|
// Stream not yet active - no padding.
|
|
Field(&MediaStreamAllocationConfig::pad_up_bitrate_bps, 0u),
|
|
Field(&MediaStreamAllocationConfig::enforce_min_bitrate,
|
|
!kSuspend))));
|
|
encoder_queue_->PostTask([&] {
|
|
static_cast<VideoStreamEncoderInterface::EncoderSink*>(vss_impl.get())
|
|
->OnEncoderConfigurationChanged(
|
|
std::vector<VideoStream>{stream}, true,
|
|
test_config.screenshare
|
|
? VideoEncoderConfig::ContentType::kScreen
|
|
: VideoEncoderConfig::ContentType::kRealtimeVideo,
|
|
test_config.min_padding_bitrate_bps);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
::testing::Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
|
|
// Simulate an encoded image, this will turn the stream active and
|
|
// enable padding.
|
|
EXPECT_CALL(rtp_video_sender_, OnEncodedImage)
|
|
.WillRepeatedly(Return(
|
|
EncodedImageCallback::Result(EncodedImageCallback::Result::OK)));
|
|
// Screensharing implicitly forces ALR.
|
|
const bool using_alr = test_config.alr || test_config.screenshare;
|
|
// If ALR is used, pads only to min bitrate as rampup is handled by
|
|
// probing. Otherwise target_bitrate contains the padding target.
|
|
int expected_padding =
|
|
using_alr ? stream.min_bitrate_bps
|
|
: static_cast<int>(stream.target_bitrate_bps *
|
|
(test_config.screenshare ? 1.35 : 1.2));
|
|
// Min padding bitrate may override padding target.
|
|
expected_padding =
|
|
std::max(expected_padding, test_config.min_padding_bitrate_bps);
|
|
EXPECT_CALL(
|
|
bitrate_allocator_,
|
|
AddObserver(
|
|
vss_impl.get(),
|
|
AllOf(Field(&MediaStreamAllocationConfig::min_bitrate_bps,
|
|
static_cast<uint32_t>(stream.min_bitrate_bps)),
|
|
Field(&MediaStreamAllocationConfig::max_bitrate_bps,
|
|
static_cast<uint32_t>(stream.max_bitrate_bps)),
|
|
// Stream now active - min bitrate use as padding target
|
|
// when ALR is active.
|
|
Field(&MediaStreamAllocationConfig::pad_up_bitrate_bps,
|
|
expected_padding),
|
|
Field(&MediaStreamAllocationConfig::enforce_min_bitrate,
|
|
!kSuspend))));
|
|
encoder_queue_->PostTask([&] {
|
|
EncodedImage encoded_image;
|
|
CodecSpecificInfo codec_specific;
|
|
|
|
static_cast<EncodedImageCallback*>(vss_impl.get())
|
|
->OnEncodedImage(encoded_image, &codec_specific);
|
|
});
|
|
time_controller_.AdvanceTime(TimeDelta::Zero());
|
|
Mock::VerifyAndClearExpectations(&bitrate_allocator_);
|
|
|
|
vss_impl->Stop();
|
|
}
|
|
}
|
|
} // namespace internal
|
|
} // namespace webrtc
|