Zero-hertz encoding mode: avoid encoder bitrate overshooting.
The encoders wrapped in VideoStreamEncoder grossly over-estimates available bitrate when capture FPS falls close to zero, and frames re-commence highly frequent delivery. Avoid this by moving the input RateStatistics inside VSE into the frame cadence adapter, and changing the reported framerate under zero-hertz encoding mode to always return the configured max FPS. Bug: chromium:1255737 Change-Id: Iaa71ef51c0755b12e24e435d86d9562122ed494e Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/239126 Commit-Queue: Markus Handell <handellm@webrtc.org> Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> Reviewed-by: Stefan Holmer <stefan@webrtc.org> Cr-Commit-Position: refs/heads/main@{#35431}
This commit is contained in:
parent
b55c299f4d
commit
ee22543829
@ -273,6 +273,7 @@ rtc_library("frame_cadence_adapter") {
|
||||
"../rtc_base:macromagic",
|
||||
"../rtc_base:rtc_base_approved",
|
||||
"../rtc_base/synchronization:mutex",
|
||||
"../rtc_base/system:no_unique_address",
|
||||
"../rtc_base/task_utils:pending_task_safety_flag",
|
||||
"../rtc_base/task_utils:to_queued_task",
|
||||
"../system_wrappers",
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
#include "api/task_queue/task_queue_base.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
#include "rtc_base/system/no_unique_address.h"
|
||||
#include "rtc_base/task_utils/pending_task_safety_flag.h"
|
||||
#include "rtc_base/task_utils/to_queued_task.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
@ -28,6 +30,80 @@
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Abstracts concrete modes of the cadence adapter.
|
||||
class AdapterMode {
|
||||
public:
|
||||
virtual ~AdapterMode() = default;
|
||||
|
||||
// Called on the worker thread for every frame that enters.
|
||||
virtual void OnFrame(Timestamp post_time,
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) = 0;
|
||||
|
||||
// Returns the currently estimated input framerate.
|
||||
virtual absl::optional<uint32_t> GetInputFrameRateFps() = 0;
|
||||
|
||||
// Updates the frame rate.
|
||||
virtual void UpdateFrameRate() = 0;
|
||||
};
|
||||
|
||||
// Implements a pass-through adapter. Single-threaded.
|
||||
class PassthroughAdapterMode : public AdapterMode {
|
||||
public:
|
||||
PassthroughAdapterMode(Clock* clock,
|
||||
FrameCadenceAdapterInterface::Callback* callback)
|
||||
: clock_(clock), callback_(callback) {
|
||||
sequence_checker_.Detach();
|
||||
}
|
||||
|
||||
// Adapter overrides.
|
||||
void OnFrame(Timestamp post_time,
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) override {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
|
||||
}
|
||||
|
||||
absl::optional<uint32_t> GetInputFrameRateFps() override {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
return input_framerate_.Rate(clock_->TimeInMilliseconds());
|
||||
}
|
||||
|
||||
void UpdateFrameRate() override {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
input_framerate_.Update(1, clock_->TimeInMilliseconds());
|
||||
}
|
||||
|
||||
private:
|
||||
Clock* const clock_;
|
||||
FrameCadenceAdapterInterface::Callback* const callback_;
|
||||
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
|
||||
// Input frame rate statistics for use when not in zero-hertz mode.
|
||||
RateStatistics input_framerate_ RTC_GUARDED_BY(sequence_checker_){
|
||||
FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000};
|
||||
};
|
||||
|
||||
// Implements a frame cadence adapter supporting zero-hertz input.
|
||||
class ZeroHertzAdapterMode : public AdapterMode {
|
||||
public:
|
||||
ZeroHertzAdapterMode(FrameCadenceAdapterInterface::Callback* callback,
|
||||
double max_fps);
|
||||
|
||||
// Adapter overrides.
|
||||
void OnFrame(Timestamp post_time,
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) override;
|
||||
absl::optional<uint32_t> GetInputFrameRateFps() override;
|
||||
void UpdateFrameRate() override {}
|
||||
|
||||
private:
|
||||
FrameCadenceAdapterInterface::Callback* const callback_;
|
||||
// The configured max_fps.
|
||||
// TODO(crbug.com/1255737): support max_fps updates.
|
||||
const double max_fps_;
|
||||
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
|
||||
};
|
||||
|
||||
class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
|
||||
public:
|
||||
FrameCadenceAdapterImpl(Clock* clock, TaskQueueBase* queue);
|
||||
@ -35,6 +111,8 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
|
||||
// FrameCadenceAdapterInterface overrides.
|
||||
void Initialize(Callback* callback) override;
|
||||
void SetZeroHertzModeEnabled(bool enabled) override;
|
||||
absl::optional<uint32_t> GetInputFrameRateFps() override;
|
||||
void UpdateFrameRate() override;
|
||||
|
||||
// VideoFrameSink overrides.
|
||||
void OnFrame(const VideoFrame& frame) override;
|
||||
@ -48,8 +126,18 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) RTC_RUN_ON(queue_);
|
||||
|
||||
// Returns true under all of the following conditions:
|
||||
// - constraints min fps set to 0
|
||||
// - constraints max fps set and greater than 0,
|
||||
// - field trial enabled
|
||||
// - zero-hertz mode enabled
|
||||
bool IsZeroHertzScreenshareEnabled() const RTC_RUN_ON(queue_);
|
||||
|
||||
// Handles adapter creation on configuration changes.
|
||||
void MaybeReconfigureAdapters(bool was_zero_hertz_enabled) RTC_RUN_ON(queue_);
|
||||
|
||||
// Called to report on constraint UMAs.
|
||||
void MaybeReportFrameRateConstraintUmas() RTC_RUN_ON(&queue_);
|
||||
void MaybeReportFrameRateConstraintUmas() RTC_RUN_ON(queue_);
|
||||
|
||||
Clock* const clock_;
|
||||
TaskQueueBase* const queue_;
|
||||
@ -58,6 +146,12 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
|
||||
// 0 Hz.
|
||||
const bool zero_hertz_screenshare_enabled_;
|
||||
|
||||
// The two possible modes we're under.
|
||||
absl::optional<PassthroughAdapterMode> passthrough_adapter_;
|
||||
absl::optional<ZeroHertzAdapterMode> zero_hertz_adapter_;
|
||||
// Cache for the current adapter mode.
|
||||
AdapterMode* current_adapter_mode_ = nullptr;
|
||||
|
||||
// Set up during Initialize.
|
||||
Callback* callback_ = nullptr;
|
||||
|
||||
@ -80,6 +174,26 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
|
||||
ScopedTaskSafetyDetached safety_;
|
||||
};
|
||||
|
||||
ZeroHertzAdapterMode::ZeroHertzAdapterMode(
|
||||
FrameCadenceAdapterInterface::Callback* callback,
|
||||
double max_fps)
|
||||
: callback_(callback), max_fps_(max_fps) {
|
||||
sequence_checker_.Detach();
|
||||
}
|
||||
|
||||
void ZeroHertzAdapterMode::OnFrame(Timestamp post_time,
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
// TODO(crbug.com/1255737): fill with meaningful implementation.
|
||||
callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
|
||||
}
|
||||
|
||||
absl::optional<uint32_t> ZeroHertzAdapterMode::GetInputFrameRateFps() {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
return max_fps_;
|
||||
}
|
||||
|
||||
FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(Clock* clock,
|
||||
TaskQueueBase* queue)
|
||||
: clock_(clock),
|
||||
@ -89,13 +203,30 @@ FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(Clock* clock,
|
||||
|
||||
void FrameCadenceAdapterImpl::Initialize(Callback* callback) {
|
||||
callback_ = callback;
|
||||
passthrough_adapter_.emplace(clock_, callback);
|
||||
current_adapter_mode_ = &passthrough_adapter_.value();
|
||||
}
|
||||
|
||||
void FrameCadenceAdapterImpl::SetZeroHertzModeEnabled(bool enabled) {
|
||||
RTC_DCHECK_RUN_ON(queue_);
|
||||
bool was_zero_hertz_enabled = zero_hertz_and_uma_reporting_enabled_;
|
||||
if (enabled && !zero_hertz_and_uma_reporting_enabled_)
|
||||
has_reported_screenshare_frame_rate_umas_ = false;
|
||||
zero_hertz_and_uma_reporting_enabled_ = enabled;
|
||||
MaybeReconfigureAdapters(was_zero_hertz_enabled);
|
||||
}
|
||||
|
||||
absl::optional<uint32_t> FrameCadenceAdapterImpl::GetInputFrameRateFps() {
|
||||
RTC_DCHECK_RUN_ON(queue_);
|
||||
return current_adapter_mode_->GetInputFrameRateFps();
|
||||
}
|
||||
|
||||
void FrameCadenceAdapterImpl::UpdateFrameRate() {
|
||||
RTC_DCHECK_RUN_ON(queue_);
|
||||
// The frame rate need not be updated for the zero-hertz adapter. The
|
||||
// passthrough adapter however uses it. Always pass frames into the
|
||||
// passthrough to keep the estimation alive should there be an adapter switch.
|
||||
passthrough_adapter_->UpdateFrameRate();
|
||||
}
|
||||
|
||||
void FrameCadenceAdapterImpl::OnFrame(const VideoFrame& frame) {
|
||||
@ -124,7 +255,9 @@ void FrameCadenceAdapterImpl::OnConstraintsChanged(
|
||||
<< constraints.max_fps.value_or(-1);
|
||||
queue_->PostTask(ToQueuedTask(safety_.flag(), [this, constraints] {
|
||||
RTC_DCHECK_RUN_ON(queue_);
|
||||
bool was_zero_hertz_enabled = IsZeroHertzScreenshareEnabled();
|
||||
source_constraints_ = constraints;
|
||||
MaybeReconfigureAdapters(was_zero_hertz_enabled);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -133,7 +266,33 @@ void FrameCadenceAdapterImpl::OnFrameOnMainQueue(
|
||||
Timestamp post_time,
|
||||
int frames_scheduled_for_processing,
|
||||
const VideoFrame& frame) {
|
||||
callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
|
||||
current_adapter_mode_->OnFrame(post_time, frames_scheduled_for_processing,
|
||||
frame);
|
||||
}
|
||||
|
||||
// RTC_RUN_ON(queue_)
|
||||
bool FrameCadenceAdapterImpl::IsZeroHertzScreenshareEnabled() const {
|
||||
return zero_hertz_screenshare_enabled_ && source_constraints_.has_value() &&
|
||||
source_constraints_->max_fps.value_or(-1) > 0 &&
|
||||
source_constraints_->min_fps.value_or(-1) == 0 &&
|
||||
zero_hertz_and_uma_reporting_enabled_;
|
||||
}
|
||||
|
||||
// RTC_RUN_ON(queue_)
|
||||
void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
|
||||
bool was_zero_hertz_enabled) {
|
||||
bool is_zero_hertz_enabled = IsZeroHertzScreenshareEnabled();
|
||||
if (is_zero_hertz_enabled) {
|
||||
if (!was_zero_hertz_enabled) {
|
||||
zero_hertz_adapter_.emplace(callback_,
|
||||
source_constraints_->max_fps.value());
|
||||
}
|
||||
current_adapter_mode_ = &zero_hertz_adapter_.value();
|
||||
} else {
|
||||
if (was_zero_hertz_enabled)
|
||||
zero_hertz_adapter_ = absl::nullopt;
|
||||
current_adapter_mode_ = &passthrough_adapter_.value();
|
||||
}
|
||||
}
|
||||
|
||||
// RTC_RUN_ON(queue_)
|
||||
|
||||
@ -29,6 +29,10 @@ namespace webrtc {
|
||||
class FrameCadenceAdapterInterface
|
||||
: public rtc::VideoSinkInterface<VideoFrame> {
|
||||
public:
|
||||
// Averaging window spanning 90 frames at default 30fps, matching old media
|
||||
// optimization module defaults.
|
||||
static constexpr int64_t kFrameRateAveragingWindowSizeMs = (1000 / 30) * 90;
|
||||
|
||||
// Callback interface used to inform instance owners.
|
||||
class Callback {
|
||||
public:
|
||||
@ -66,6 +70,14 @@ class FrameCadenceAdapterInterface
|
||||
|
||||
// Pass true in |enabled| as a prerequisite to enable zero-hertz operation.
|
||||
virtual void SetZeroHertzModeEnabled(bool enabled) = 0;
|
||||
|
||||
// Returns the input framerate. This is measured by RateStatistics when
|
||||
// zero-hertz mode is off, and returns the max framerate in zero-hertz mode.
|
||||
virtual absl::optional<uint32_t> GetInputFrameRateFps() = 0;
|
||||
|
||||
// Updates frame rate. This is done unconditionally irrespective of adapter
|
||||
// mode.
|
||||
virtual void UpdateFrameRate() = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "api/task_queue/task_queue_base.h"
|
||||
#include "api/video/nv12_buffer.h"
|
||||
#include "api/video/video_frame.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
#include "rtc_base/ref_counted_object.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
#include "test/field_trial.h"
|
||||
@ -56,6 +57,12 @@ class ZeroHertzFieldTrialDisabler : public test::ScopedFieldTrials {
|
||||
: test::ScopedFieldTrials("WebRTC-ZeroHertzScreenshare/Disabled/") {}
|
||||
};
|
||||
|
||||
class ZeroHertzFieldTrialEnabler : public test::ScopedFieldTrials {
|
||||
public:
|
||||
ZeroHertzFieldTrialEnabler()
|
||||
: test::ScopedFieldTrials("WebRTC-ZeroHertzScreenshare/Enabled/") {}
|
||||
};
|
||||
|
||||
TEST(FrameCadenceAdapterTest,
|
||||
ForwardsFramesOnConstructionAndUnderDisabledFieldTrial) {
|
||||
GlobalSimulatedTimeController time_controller(Timestamp::Millis(1));
|
||||
@ -93,6 +100,92 @@ TEST(FrameCadenceAdapterTest, CountsOutstandingFramesToProcess) {
|
||||
time_controller.AdvanceTime(TimeDelta::Zero());
|
||||
}
|
||||
|
||||
TEST(FrameCadenceAdapterTest, FrameRateFollowsRateStatisticsByDefault) {
|
||||
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
|
||||
auto adapter = CreateAdapter(time_controller.GetClock());
|
||||
adapter->Initialize(nullptr);
|
||||
|
||||
// Create an "oracle" rate statistics which should be followed on a sequence
|
||||
// of frames.
|
||||
RateStatistics rate(
|
||||
FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
|
||||
|
||||
for (int frame = 0; frame != 10; ++frame) {
|
||||
time_controller.AdvanceTime(TimeDelta::Millis(10));
|
||||
rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
|
||||
adapter->UpdateFrameRate();
|
||||
EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
|
||||
adapter->GetInputFrameRateFps())
|
||||
<< " failed for frame " << frame;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameCadenceAdapterTest,
|
||||
FrameRateFollowsRateStatisticsWhenFeatureDisabled) {
|
||||
ZeroHertzFieldTrialDisabler feature_disabler;
|
||||
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
|
||||
auto adapter = CreateAdapter(time_controller.GetClock());
|
||||
adapter->Initialize(nullptr);
|
||||
|
||||
// Create an "oracle" rate statistics which should be followed on a sequence
|
||||
// of frames.
|
||||
RateStatistics rate(
|
||||
FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
|
||||
|
||||
for (int frame = 0; frame != 10; ++frame) {
|
||||
time_controller.AdvanceTime(TimeDelta::Millis(10));
|
||||
rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
|
||||
adapter->UpdateFrameRate();
|
||||
EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
|
||||
adapter->GetInputFrameRateFps())
|
||||
<< " failed for frame " << frame;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameCadenceAdapterTest, FrameRateFollowsMaxFpsWhenZeroHertzActivated) {
|
||||
ZeroHertzFieldTrialEnabler enabler;
|
||||
MockCallback callback;
|
||||
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
|
||||
auto adapter = CreateAdapter(time_controller.GetClock());
|
||||
adapter->Initialize(nullptr);
|
||||
adapter->SetZeroHertzModeEnabled(true);
|
||||
adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1});
|
||||
for (int frame = 0; frame != 10; ++frame) {
|
||||
time_controller.AdvanceTime(TimeDelta::Millis(10));
|
||||
adapter->UpdateFrameRate();
|
||||
EXPECT_EQ(adapter->GetInputFrameRateFps(), 1u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameCadenceAdapterTest,
|
||||
FrameRateFollowsRateStatisticsAfterZeroHertzDeactivated) {
|
||||
ZeroHertzFieldTrialEnabler enabler;
|
||||
MockCallback callback;
|
||||
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
|
||||
auto adapter = CreateAdapter(time_controller.GetClock());
|
||||
adapter->Initialize(nullptr);
|
||||
adapter->SetZeroHertzModeEnabled(true);
|
||||
adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1});
|
||||
RateStatistics rate(
|
||||
FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
|
||||
constexpr int MAX = 10;
|
||||
for (int frame = 0; frame != MAX; ++frame) {
|
||||
time_controller.AdvanceTime(TimeDelta::Millis(10));
|
||||
rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
|
||||
adapter->UpdateFrameRate();
|
||||
}
|
||||
// Turn off zero hertz on the next-last frame; after the last frame we
|
||||
// should see a value that tracks the rate oracle.
|
||||
adapter->SetZeroHertzModeEnabled(false);
|
||||
// Last frame.
|
||||
time_controller.AdvanceTime(TimeDelta::Millis(10));
|
||||
rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
|
||||
adapter->UpdateFrameRate();
|
||||
|
||||
EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
|
||||
adapter->GetInputFrameRateFps());
|
||||
}
|
||||
|
||||
class FrameCadenceAdapterMetricsTest : public ::testing::Test {
|
||||
public:
|
||||
FrameCadenceAdapterMetricsTest() : time_controller_(Timestamp::Millis(1)) {
|
||||
|
||||
@ -64,10 +64,6 @@ const int64_t kPendingFrameTimeoutMs = 1000;
|
||||
|
||||
constexpr char kFrameDropperFieldTrial[] = "WebRTC-FrameDropper";
|
||||
|
||||
// Averaging window spanning 90 frames at default 30fps, matching old media
|
||||
// optimization module defaults.
|
||||
const int64_t kFrameRateAvergingWindowSizeMs = (1000 / 30) * 90;
|
||||
|
||||
const size_t kDefaultPayloadSize = 1440;
|
||||
|
||||
const int64_t kParameterUpdateIntervalMs = 1000;
|
||||
@ -633,7 +629,6 @@ VideoStreamEncoder::VideoStreamEncoder(
|
||||
expect_resize_state_(ExpectResizeState::kNoResize),
|
||||
fec_controller_override_(nullptr),
|
||||
force_disable_frame_dropper_(false),
|
||||
input_framerate_(kFrameRateAvergingWindowSizeMs, 1000),
|
||||
pending_frame_drops_(0),
|
||||
cwnd_frame_counter_(0),
|
||||
next_frame_types_(1, VideoFrameType::kVideoFrameDelta),
|
||||
@ -1422,8 +1417,13 @@ VideoStreamEncoder::UpdateBitrateAllocation(
|
||||
|
||||
uint32_t VideoStreamEncoder::GetInputFramerateFps() {
|
||||
const uint32_t default_fps = max_framerate_ != -1 ? max_framerate_ : 30;
|
||||
|
||||
// This method may be called after we cleared out the frame_cadence_adapter_
|
||||
// reference in Stop(). In such a situation it's probably not important with a
|
||||
// decent estimate.
|
||||
absl::optional<uint32_t> input_fps =
|
||||
input_framerate_.Rate(clock_->TimeInMilliseconds());
|
||||
frame_cadence_adapter_ ? frame_cadence_adapter_->GetInputFrameRateFps()
|
||||
: absl::nullopt;
|
||||
if (!input_fps || *input_fps == 0) {
|
||||
return default_fps;
|
||||
}
|
||||
@ -1525,7 +1525,7 @@ void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame,
|
||||
// Poll the rate before updating, otherwise we risk the rate being estimated
|
||||
// a little too high at the start of the call when then window is small.
|
||||
uint32_t framerate_fps = GetInputFramerateFps();
|
||||
input_framerate_.Update(1u, clock_->TimeInMilliseconds());
|
||||
frame_cadence_adapter_->UpdateFrameRate();
|
||||
|
||||
int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
if (pending_encoder_reconfiguration_) {
|
||||
|
||||
@ -339,7 +339,6 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface,
|
||||
// trusted rate controller. This is determined on a per-frame basis, as the
|
||||
// encoder behavior might dynamically change.
|
||||
bool force_disable_frame_dropper_ RTC_GUARDED_BY(&encoder_queue_);
|
||||
RateStatistics input_framerate_ RTC_GUARDED_BY(&encoder_queue_);
|
||||
// Incremented on worker thread whenever `frame_dropper_` determines that a
|
||||
// frame should be dropped. Decremented on whichever thread runs
|
||||
// OnEncodedImage(), which is only called by one thread but not necessarily
|
||||
|
||||
@ -76,6 +76,7 @@ using ::testing::Eq;
|
||||
using ::testing::Field;
|
||||
using ::testing::Ge;
|
||||
using ::testing::Gt;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Le;
|
||||
using ::testing::Lt;
|
||||
using ::testing::Matcher;
|
||||
@ -640,30 +641,26 @@ class SimpleVideoStreamEncoderFactory {
|
||||
~AdaptedVideoStreamEncoder() { Stop(); }
|
||||
};
|
||||
|
||||
SimpleVideoStreamEncoderFactory()
|
||||
: time_controller_(Timestamp::Millis(0)),
|
||||
task_queue_factory_(time_controller_.CreateTaskQueueFactory()),
|
||||
stats_proxy_(std::make_unique<MockableSendStatisticsProxy>(
|
||||
time_controller_.GetClock(),
|
||||
VideoSendStream::Config(nullptr),
|
||||
webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo)),
|
||||
encoder_settings_(
|
||||
VideoEncoder::Capabilities(/*loss_notification=*/false)),
|
||||
fake_encoder_(time_controller_.GetClock()),
|
||||
encoder_factory_(&fake_encoder_) {
|
||||
SimpleVideoStreamEncoderFactory() {
|
||||
encoder_settings_.encoder_factory = &encoder_factory_;
|
||||
encoder_settings_.bitrate_allocator_factory =
|
||||
bitrate_allocator_factory_.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<AdaptedVideoStreamEncoder> Create(
|
||||
std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter) {
|
||||
std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter,
|
||||
TaskQueueBase** encoder_queue_ptr = nullptr) {
|
||||
auto encoder_queue =
|
||||
time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
|
||||
"EncoderQueue", TaskQueueFactory::Priority::NORMAL);
|
||||
if (encoder_queue_ptr)
|
||||
*encoder_queue_ptr = encoder_queue.get();
|
||||
auto result = std::make_unique<AdaptedVideoStreamEncoder>(
|
||||
time_controller_.GetClock(),
|
||||
/*number_of_cores=*/1,
|
||||
/*stats_proxy=*/stats_proxy_.get(), encoder_settings_,
|
||||
std::make_unique<CpuOveruseDetectorProxy>(/*stats_proxy=*/nullptr),
|
||||
std::move(zero_hertz_adapter),
|
||||
time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
|
||||
"EncoderQueue", TaskQueueFactory::Priority::NORMAL),
|
||||
std::move(zero_hertz_adapter), std::move(encoder_queue),
|
||||
VideoStreamEncoder::BitrateAllocationCallbackType::
|
||||
kVideoBitrateAllocation);
|
||||
result->SetSink(&sink_, /*rotation_applied=*/false);
|
||||
@ -692,12 +689,20 @@ class SimpleVideoStreamEncoderFactory {
|
||||
}
|
||||
};
|
||||
|
||||
GlobalSimulatedTimeController time_controller_;
|
||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||
std::unique_ptr<MockableSendStatisticsProxy> stats_proxy_;
|
||||
VideoStreamEncoderSettings encoder_settings_;
|
||||
test::FakeEncoder fake_encoder_;
|
||||
test::VideoEncoderProxyFactory encoder_factory_;
|
||||
GlobalSimulatedTimeController time_controller_{Timestamp::Millis(0)};
|
||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_{
|
||||
time_controller_.CreateTaskQueueFactory()};
|
||||
std::unique_ptr<MockableSendStatisticsProxy> stats_proxy_ =
|
||||
std::make_unique<MockableSendStatisticsProxy>(
|
||||
time_controller_.GetClock(),
|
||||
VideoSendStream::Config(nullptr),
|
||||
webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo);
|
||||
std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_ =
|
||||
CreateBuiltinVideoBitrateAllocatorFactory();
|
||||
VideoStreamEncoderSettings encoder_settings_{
|
||||
VideoEncoder::Capabilities(/*loss_notification=*/false)};
|
||||
test::FakeEncoder fake_encoder_{time_controller_.GetClock()};
|
||||
test::VideoEncoderProxyFactory encoder_factory_{&fake_encoder_};
|
||||
NullEncoderSink sink_;
|
||||
};
|
||||
|
||||
@ -706,6 +711,8 @@ class MockFrameCadenceAdapter : public FrameCadenceAdapterInterface {
|
||||
MOCK_METHOD(void, Initialize, (Callback * callback), (override));
|
||||
MOCK_METHOD(void, SetZeroHertzModeEnabled, (bool), (override));
|
||||
MOCK_METHOD(void, OnFrame, (const VideoFrame&), (override));
|
||||
MOCK_METHOD(absl::optional<uint32_t>, GetInputFrameRateFps, (), (override));
|
||||
MOCK_METHOD(void, UpdateFrameRate, (), (override));
|
||||
};
|
||||
|
||||
class MockEncoderSelector
|
||||
@ -8746,4 +8753,46 @@ TEST(VideoStreamEncoderFrameCadenceTest,
|
||||
.build());
|
||||
}
|
||||
|
||||
TEST(VideoStreamEncoderFrameCadenceTest, UsesFrameCadenceAdapterForFrameRate) {
|
||||
auto adapter = std::make_unique<MockFrameCadenceAdapter>();
|
||||
auto* adapter_ptr = adapter.get();
|
||||
test::FrameForwarder video_source;
|
||||
SimpleVideoStreamEncoderFactory factory;
|
||||
FrameCadenceAdapterInterface::Callback* video_stream_encoder_callback =
|
||||
nullptr;
|
||||
EXPECT_CALL(*adapter_ptr, Initialize)
|
||||
.WillOnce(Invoke([&video_stream_encoder_callback](
|
||||
FrameCadenceAdapterInterface::Callback* callback) {
|
||||
video_stream_encoder_callback = callback;
|
||||
}));
|
||||
TaskQueueBase* encoder_queue = nullptr;
|
||||
auto video_stream_encoder =
|
||||
factory.Create(std::move(adapter), &encoder_queue);
|
||||
|
||||
// This is just to make the VSE operational. We'll feed a frame directly by
|
||||
// the callback interface.
|
||||
video_stream_encoder->SetSource(
|
||||
&video_source, webrtc::DegradationPreference::MAINTAIN_FRAMERATE);
|
||||
|
||||
VideoEncoderConfig video_encoder_config;
|
||||
test::FillEncoderConfiguration(kVideoCodecGeneric, 1, &video_encoder_config);
|
||||
video_stream_encoder->ConfigureEncoder(std::move(video_encoder_config),
|
||||
/*max_data_payload_length=*/1000);
|
||||
|
||||
EXPECT_CALL(*adapter_ptr, GetInputFrameRateFps);
|
||||
EXPECT_CALL(*adapter_ptr, UpdateFrameRate);
|
||||
encoder_queue->PostTask(ToQueuedTask([video_stream_encoder_callback] {
|
||||
video_stream_encoder_callback->OnFrame(
|
||||
Timestamp::Millis(1), 1,
|
||||
VideoFrame::Builder()
|
||||
.set_video_frame_buffer(
|
||||
rtc::make_ref_counted<NV12Buffer>(/*width=*/16, /*height=*/16))
|
||||
.set_ntp_time_ms(0)
|
||||
.set_timestamp_ms(0)
|
||||
.set_rotation(kVideoRotation_0)
|
||||
.build());
|
||||
}));
|
||||
factory.DepleteTaskQueues();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user