diff options
Diffstat (limited to 'third_party/libwebrtc/video/frame_cadence_adapter.cc')
-rw-r--r-- | third_party/libwebrtc/video/frame_cadence_adapter.cc | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/frame_cadence_adapter.cc b/third_party/libwebrtc/video/frame_cadence_adapter.cc new file mode 100644 index 0000000000..efffa9672a --- /dev/null +++ b/third_party/libwebrtc/video/frame_cadence_adapter.cc @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2021 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/frame_cadence_adapter.h" + +#include <atomic> +#include <deque> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/base/attributes.h" +#include "api/sequence_checker.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "rtc_base/checks.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/repeating_task.h" +#include "rtc_base/thread_annotations.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/metrics.h" +#include "system_wrappers/include/ntp_time.h" + +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(TaskQueueBase* queue, + Clock* clock, + FrameCadenceAdapterInterface::Callback* callback, + double max_fps); + + // Reconfigures according to parameters. + // All spatial layer trackers are initialized as unconverged by this method. + void ReconfigureParameters( + const FrameCadenceAdapterInterface::ZeroHertzModeParams& params); + + // Updates spatial layer quality convergence status. + void UpdateLayerQualityConvergence(size_t spatial_index, + bool quality_converged); + + // Updates spatial layer enabled status. + void UpdateLayerStatus(size_t spatial_index, bool enabled); + + // 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 {} + + // Notified on dropped frames. + void OnDiscardedFrame(); + + // Conditionally requests a refresh frame via + // Callback::RequestRefreshFrame. + void ProcessKeyFrameRequest(); + + private: + // The tracking state of each spatial layer. Used for determining when to + // stop repeating frames. + struct SpatialLayerTracker { + // If unset, the layer is disabled. Otherwise carries the quality + // convergence status of the layer. + absl::optional<bool> quality_converged; + }; + // The state of a scheduled repeat. + struct ScheduledRepeat { + ScheduledRepeat(Timestamp origin, + int64_t origin_timestamp_us, + int64_t origin_ntp_time_ms) + : scheduled(origin), + idle(false), + origin(origin), + origin_timestamp_us(origin_timestamp_us), + origin_ntp_time_ms(origin_ntp_time_ms) {} + // The instant when the repeat was scheduled. + Timestamp scheduled; + // True if the repeat was scheduled as an idle repeat (long), false + // otherwise. + bool idle; + // The moment we decided to start repeating. + Timestamp origin; + // The timestamp_us of the frame when we started repeating. + int64_t origin_timestamp_us; + // The ntp_times_ms of the frame when we started repeating. + int64_t origin_ntp_time_ms; + }; + + // Returns true if all spatial layers can be considered to be converged in + // terms of quality. + // Convergence means QP has dropped to a low-enough level to warrant ceasing + // to send identical frames at high frequency. + bool HasQualityConverged() const RTC_RUN_ON(sequence_checker_); + // Resets quality convergence information. HasQualityConverged() returns false + // after this call. + void ResetQualityConvergenceInfo() RTC_RUN_ON(sequence_checker_); + // Processes incoming frames on a delayed cadence. + void ProcessOnDelayedCadence() RTC_RUN_ON(sequence_checker_); + // Schedules a later repeat with delay depending on state of layer trackers. + // If true is passed in `idle_repeat`, the repeat is going to be + // kZeroHertzIdleRepeatRatePeriod. Otherwise it'll be the value of + // `frame_delay`. + void ScheduleRepeat(int frame_id, bool idle_repeat) + RTC_RUN_ON(sequence_checker_); + // Repeats a frame in the abscence of incoming frames. Slows down when quality + // convergence is attained, and stops the cadence terminally when new frames + // have arrived. + void ProcessRepeatedFrameOnDelayedCadence(int frame_id) + RTC_RUN_ON(sequence_checker_); + // Sends a frame, updating the timestamp to the current time. + void SendFrameNow(const VideoFrame& frame) const + RTC_RUN_ON(sequence_checker_); + // Returns the repeat duration depending on if it's an idle repeat or not. + TimeDelta RepeatDuration(bool idle_repeat) const + RTC_RUN_ON(sequence_checker_); + // Unless timer already running, starts repeatedly requesting refresh frames + // after a grace_period. If a frame appears before the grace_period has + // passed, the request is cancelled. + void MaybeStartRefreshFrameRequester() RTC_RUN_ON(sequence_checker_); + + TaskQueueBase* const queue_; + Clock* const clock_; + FrameCadenceAdapterInterface::Callback* const callback_; + + // The configured max_fps. + // TODO(crbug.com/1255737): support max_fps updates. + const double max_fps_; + // How much the incoming frame sequence is delayed by. + const TimeDelta frame_delay_ = TimeDelta::Seconds(1) / max_fps_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + // A queue of incoming frames and repeated frames. + std::deque<VideoFrame> queued_frames_ RTC_GUARDED_BY(sequence_checker_); + // The current frame ID to use when starting to repeat frames. This is used + // for cancelling deferred repeated frame processing happening. + int current_frame_id_ RTC_GUARDED_BY(sequence_checker_) = 0; + // Has content when we are repeating frames. + absl::optional<ScheduledRepeat> scheduled_repeat_ + RTC_GUARDED_BY(sequence_checker_); + // Convergent state of each of the configured simulcast layers. + std::vector<SpatialLayerTracker> layer_trackers_ + RTC_GUARDED_BY(sequence_checker_); + // Repeating task handle used for requesting refresh frames until arrival, as + // they can be dropped in various places in the capture pipeline. + RepeatingTaskHandle refresh_frame_requester_ + RTC_GUARDED_BY(sequence_checker_); + + ScopedTaskSafety safety_; +}; + +class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface { + public: + FrameCadenceAdapterImpl(Clock* clock, + TaskQueueBase* queue, + const FieldTrialsView& field_trials); + ~FrameCadenceAdapterImpl(); + + // FrameCadenceAdapterInterface overrides. + void Initialize(Callback* callback) override; + void SetZeroHertzModeEnabled( + absl::optional<ZeroHertzModeParams> params) override; + absl::optional<uint32_t> GetInputFrameRateFps() override; + void UpdateFrameRate() override; + void UpdateLayerQualityConvergence(size_t spatial_index, + bool quality_converged) override; + void UpdateLayerStatus(size_t spatial_index, bool enabled) override; + void ProcessKeyFrameRequest() override; + + // VideoFrameSink overrides. + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + void OnConstraintsChanged( + const VideoTrackSourceConstraints& constraints) override; + + private: + // Called from OnFrame in zero-hertz mode. + void OnFrameOnMainQueue(Timestamp post_time, + 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_); + + Clock* const clock_; + TaskQueueBase* const queue_; + + // True if we support frame entry for screenshare with a minimum frequency of + // 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_; + // If set, zero-hertz mode has been enabled. + absl::optional<ZeroHertzModeParams> zero_hertz_params_; + // Cache for the current adapter mode. + AdapterMode* current_adapter_mode_ = nullptr; + + // Timestamp for statistics reporting. + absl::optional<Timestamp> zero_hertz_adapter_created_timestamp_ + RTC_GUARDED_BY(queue_); + + // Set up during Initialize. + Callback* callback_ = nullptr; + + // The source's constraints. + absl::optional<VideoTrackSourceConstraints> source_constraints_ + RTC_GUARDED_BY(queue_); + + // Race checker for incoming frames. This is the network thread in chromium, + // but may vary from test contexts. + rtc::RaceChecker incoming_frame_race_checker_; + bool has_reported_screenshare_frame_rate_umas_ RTC_GUARDED_BY(queue_) = false; + + // Number of frames that are currently scheduled for processing on the + // `queue_`. + std::atomic<int> frames_scheduled_for_processing_{0}; + + ScopedTaskSafetyDetached safety_; +}; + +ZeroHertzAdapterMode::ZeroHertzAdapterMode( + TaskQueueBase* queue, + Clock* clock, + FrameCadenceAdapterInterface::Callback* callback, + double max_fps) + : queue_(queue), clock_(clock), callback_(callback), max_fps_(max_fps) { + sequence_checker_.Detach(); + MaybeStartRefreshFrameRequester(); +} + +void ZeroHertzAdapterMode::ReconfigureParameters( + const FrameCadenceAdapterInterface::ZeroHertzModeParams& params) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_INFO) << __func__ << " this " << this << " num_simulcast_layers " + << params.num_simulcast_layers; + + // Start as unconverged. + layer_trackers_.clear(); + layer_trackers_.resize(params.num_simulcast_layers, + SpatialLayerTracker{false}); +} + +void ZeroHertzAdapterMode::UpdateLayerQualityConvergence( + size_t spatial_index, + bool quality_converged) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_LOG(LS_INFO) << __func__ << " this " << this << " layer " << spatial_index + << " quality has converged: " << quality_converged; + if (spatial_index >= layer_trackers_.size()) + return; + if (layer_trackers_[spatial_index].quality_converged.has_value()) + layer_trackers_[spatial_index].quality_converged = quality_converged; +} + +void ZeroHertzAdapterMode::UpdateLayerStatus(size_t spatial_index, + bool enabled) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (spatial_index >= layer_trackers_.size()) + return; + if (enabled) { + if (!layer_trackers_[spatial_index].quality_converged.has_value()) { + // Assume quality has not converged until hearing otherwise. + layer_trackers_[spatial_index].quality_converged = false; + } + } else { + layer_trackers_[spatial_index].quality_converged = absl::nullopt; + } + RTC_LOG(LS_INFO) + << __func__ << " this " << this << " layer " << spatial_index + << (enabled + ? (layer_trackers_[spatial_index].quality_converged.has_value() + ? " enabled." + : " enabled and it's assumed quality has not converged.") + : " disabled."); +} + +void ZeroHertzAdapterMode::OnFrame(Timestamp post_time, + int frames_scheduled_for_processing, + const VideoFrame& frame) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << "ZeroHertzAdapterMode::" << __func__ << " this " + << this; + refresh_frame_requester_.Stop(); + + // Assume all enabled layers are unconverged after frame entry. + ResetQualityConvergenceInfo(); + + // Remove stored repeating frame if needed. + if (scheduled_repeat_.has_value()) { + RTC_DCHECK(queued_frames_.size() == 1); + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this + << " cancel repeat and restart with original"; + queued_frames_.pop_front(); + } + + // Store the frame in the queue and schedule deferred processing. + queued_frames_.push_back(frame); + current_frame_id_++; + scheduled_repeat_ = absl::nullopt; + queue_->PostDelayedHighPrecisionTask( + SafeTask(safety_.flag(), + [this] { + RTC_DCHECK_RUN_ON(&sequence_checker_); + ProcessOnDelayedCadence(); + }), + frame_delay_); +} + +void ZeroHertzAdapterMode::OnDiscardedFrame() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << "ZeroHertzAdapterMode::" << __func__; + + // Under zero hertz source delivery, a discarded frame ending a sequence of + // frames which happened to contain important information can be seen as a + // capture freeze. Avoid this by starting requesting refresh frames after a + // grace period. + MaybeStartRefreshFrameRequester(); +} + +absl::optional<uint32_t> ZeroHertzAdapterMode::GetInputFrameRateFps() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return max_fps_; +} + +void ZeroHertzAdapterMode::ProcessKeyFrameRequest() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // If we're new and don't have a frame, there's no need to request refresh + // frames as this was being triggered for us when zero-hz mode was set up. + // + // The next frame encoded will be a key frame. Reset quality convergence so we + // don't get idle repeats shortly after, because key frames need a lot of + // refinement frames. + ResetQualityConvergenceInfo(); + + // If we're not repeating, or we're repeating with short duration, we will + // very soon send out a frame and don't need a refresh frame. + if (!scheduled_repeat_.has_value() || !scheduled_repeat_->idle) { + RTC_LOG(LS_INFO) << __func__ << " this " << this + << " not requesting refresh frame because of recently " + "incoming frame or short repeating."; + return; + } + + // If the repeat is scheduled within a short (i.e. frame_delay_) interval, we + // will very soon send out a frame and don't need a refresh frame. + Timestamp now = clock_->CurrentTime(); + if (scheduled_repeat_->scheduled + RepeatDuration(/*idle_repeat=*/true) - + now <= + frame_delay_) { + RTC_LOG(LS_INFO) << __func__ << " this " << this + << " not requesting refresh frame because of soon " + "happening idle repeat"; + return; + } + + // Cancel the current repeat and reschedule a short repeat now. No need for a + // new refresh frame. + RTC_LOG(LS_INFO) << __func__ << " this " << this + << " not requesting refresh frame and scheduling a short " + "repeat due to key frame request"; + ScheduleRepeat(++current_frame_id_, /*idle_repeat=*/false); + return; +} + +bool ZeroHertzAdapterMode::HasQualityConverged() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + // 1. Define ourselves as unconverged with no spatial layers configured. This + // is to keep short repeating until the layer configuration comes. + // 2. Unset layers implicitly imply that they're converged to support + // disabling layers when they're not needed. + const bool quality_converged = + !layer_trackers_.empty() && + absl::c_all_of(layer_trackers_, [](const SpatialLayerTracker& tracker) { + return tracker.quality_converged.value_or(true); + }); + return quality_converged; +} + +void ZeroHertzAdapterMode::ResetQualityConvergenceInfo() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_INFO) << __func__ << " this " << this; + for (auto& layer_tracker : layer_trackers_) { + if (layer_tracker.quality_converged.has_value()) + layer_tracker.quality_converged = false; + } +} + +void ZeroHertzAdapterMode::ProcessOnDelayedCadence() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DCHECK(!queued_frames_.empty()); + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this; + + SendFrameNow(queued_frames_.front()); + + // If there were two or more frames stored, we do not have to schedule repeats + // of the front frame. + if (queued_frames_.size() > 1) { + queued_frames_.pop_front(); + return; + } + + // There's only one frame to send. Schedule a repeat sequence, which is + // cancelled by `current_frame_id_` getting incremented should new frames + // arrive. + ScheduleRepeat(current_frame_id_, HasQualityConverged()); +} + +void ZeroHertzAdapterMode::ScheduleRepeat(int frame_id, bool idle_repeat) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this << " frame_id " + << frame_id; + Timestamp now = clock_->CurrentTime(); + if (!scheduled_repeat_.has_value()) { + scheduled_repeat_.emplace(now, queued_frames_.front().timestamp_us(), + queued_frames_.front().ntp_time_ms()); + } + scheduled_repeat_->scheduled = now; + scheduled_repeat_->idle = idle_repeat; + + TimeDelta repeat_delay = RepeatDuration(idle_repeat); + queue_->PostDelayedHighPrecisionTask( + SafeTask(safety_.flag(), + [this, frame_id] { + RTC_DCHECK_RUN_ON(&sequence_checker_); + ProcessRepeatedFrameOnDelayedCadence(frame_id); + }), + repeat_delay); +} + +void ZeroHertzAdapterMode::ProcessRepeatedFrameOnDelayedCadence(int frame_id) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this << " frame_id " + << frame_id; + RTC_DCHECK(!queued_frames_.empty()); + + // Cancel this invocation if new frames turned up. + if (frame_id != current_frame_id_) + return; + RTC_DCHECK(scheduled_repeat_.has_value()); + + VideoFrame& frame = queued_frames_.front(); + + // Since this is a repeated frame, nothing changed compared to before. + VideoFrame::UpdateRect empty_update_rect; + empty_update_rect.MakeEmptyUpdate(); + frame.set_update_rect(empty_update_rect); + + // Adjust timestamps of the frame of the repeat, accounting for the actual + // delay since we started repeating. + // + // NOTE: No need to update the RTP timestamp as the VideoStreamEncoder + // overwrites it based on its chosen NTP timestamp source. + TimeDelta total_delay = clock_->CurrentTime() - scheduled_repeat_->origin; + if (frame.timestamp_us() > 0) { + frame.set_timestamp_us(scheduled_repeat_->origin_timestamp_us + + total_delay.us()); + } + if (frame.ntp_time_ms()) { + frame.set_ntp_time_ms(scheduled_repeat_->origin_ntp_time_ms + + total_delay.ms()); + } + SendFrameNow(frame); + + // Schedule another repeat. + ScheduleRepeat(frame_id, HasQualityConverged()); +} + +void ZeroHertzAdapterMode::SendFrameNow(const VideoFrame& frame) const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this << " timestamp " + << frame.timestamp() << " timestamp_us " + << frame.timestamp_us() << " ntp_time_ms " + << frame.ntp_time_ms(); + // TODO(crbug.com/1255737): figure out if frames_scheduled_for_processing + // makes sense to compute in this implementation. + callback_->OnFrame(/*post_time=*/clock_->CurrentTime(), + /*frames_scheduled_for_processing=*/1, frame); +} + +TimeDelta ZeroHertzAdapterMode::RepeatDuration(bool idle_repeat) const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return idle_repeat + ? FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod + : frame_delay_; +} + +void ZeroHertzAdapterMode::MaybeStartRefreshFrameRequester() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_DLOG(LS_VERBOSE) << __func__; + if (!refresh_frame_requester_.Running()) { + refresh_frame_requester_ = RepeatingTaskHandle::DelayedStart( + queue_, + FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod * + frame_delay_, + [this] { + RTC_DLOG(LS_VERBOSE) << __func__ << " RequestRefreshFrame"; + if (callback_) + callback_->RequestRefreshFrame(); + return frame_delay_; + }); + } +} + +FrameCadenceAdapterImpl::FrameCadenceAdapterImpl( + Clock* clock, + TaskQueueBase* queue, + const FieldTrialsView& field_trials) + : clock_(clock), + queue_(queue), + zero_hertz_screenshare_enabled_( + !field_trials.IsDisabled("WebRTC-ZeroHertzScreenshare")) {} + +FrameCadenceAdapterImpl::~FrameCadenceAdapterImpl() { + RTC_DLOG(LS_VERBOSE) << __func__ << " this " << this; +} + +void FrameCadenceAdapterImpl::Initialize(Callback* callback) { + callback_ = callback; + passthrough_adapter_.emplace(clock_, callback); + current_adapter_mode_ = &passthrough_adapter_.value(); +} + +void FrameCadenceAdapterImpl::SetZeroHertzModeEnabled( + absl::optional<ZeroHertzModeParams> params) { + RTC_DCHECK_RUN_ON(queue_); + bool was_zero_hertz_enabled = zero_hertz_params_.has_value(); + if (params.has_value() && !was_zero_hertz_enabled) + has_reported_screenshare_frame_rate_umas_ = false; + zero_hertz_params_ = params; + 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::UpdateLayerQualityConvergence( + size_t spatial_index, + bool quality_converged) { + if (zero_hertz_adapter_.has_value()) + zero_hertz_adapter_->UpdateLayerQualityConvergence(spatial_index, + quality_converged); +} + +void FrameCadenceAdapterImpl::UpdateLayerStatus(size_t spatial_index, + bool enabled) { + if (zero_hertz_adapter_.has_value()) + zero_hertz_adapter_->UpdateLayerStatus(spatial_index, enabled); +} + +void FrameCadenceAdapterImpl::ProcessKeyFrameRequest() { + RTC_DCHECK_RUN_ON(queue_); + if (zero_hertz_adapter_) + zero_hertz_adapter_->ProcessKeyFrameRequest(); +} + +void FrameCadenceAdapterImpl::OnFrame(const VideoFrame& frame) { + // This method is called on the network thread under Chromium, or other + // various contexts in test. + RTC_DCHECK_RUNS_SERIALIZED(&incoming_frame_race_checker_); + RTC_DLOG(LS_VERBOSE) << "FrameCadenceAdapterImpl::" << __func__ << " this " + << this; + + // Local time in webrtc time base. + Timestamp post_time = clock_->CurrentTime(); + frames_scheduled_for_processing_.fetch_add(1, std::memory_order_relaxed); + queue_->PostTask(SafeTask(safety_.flag(), [this, post_time, frame] { + RTC_DCHECK_RUN_ON(queue_); + if (zero_hertz_adapter_created_timestamp_.has_value()) { + TimeDelta time_until_first_frame = + clock_->CurrentTime() - *zero_hertz_adapter_created_timestamp_; + zero_hertz_adapter_created_timestamp_ = absl::nullopt; + RTC_HISTOGRAM_COUNTS_10000( + "WebRTC.Screenshare.ZeroHz.TimeUntilFirstFrameMs", + time_until_first_frame.ms()); + } + + const int frames_scheduled_for_processing = + frames_scheduled_for_processing_.fetch_sub(1, + std::memory_order_relaxed); + OnFrameOnMainQueue(post_time, frames_scheduled_for_processing, + std::move(frame)); + MaybeReportFrameRateConstraintUmas(); + })); +} + +void FrameCadenceAdapterImpl::OnDiscardedFrame() { + callback_->OnDiscardedFrame(); + queue_->PostTask(SafeTask(safety_.flag(), [this] { + RTC_DCHECK_RUN_ON(queue_); + if (zero_hertz_adapter_) { + zero_hertz_adapter_->OnDiscardedFrame(); + } + })); +} + +void FrameCadenceAdapterImpl::OnConstraintsChanged( + const VideoTrackSourceConstraints& constraints) { + RTC_LOG(LS_INFO) << __func__ << " this " << this << " min_fps " + << constraints.min_fps.value_or(-1) << " max_fps " + << constraints.max_fps.value_or(-1); + queue_->PostTask(SafeTask(safety_.flag(), [this, constraints] { + RTC_DCHECK_RUN_ON(queue_); + bool was_zero_hertz_enabled = IsZeroHertzScreenshareEnabled(); + source_constraints_ = constraints; + MaybeReconfigureAdapters(was_zero_hertz_enabled); + })); +} + +void FrameCadenceAdapterImpl::OnFrameOnMainQueue( + Timestamp post_time, + int frames_scheduled_for_processing, + const VideoFrame& frame) { + RTC_DCHECK_RUN_ON(queue_); + current_adapter_mode_->OnFrame(post_time, frames_scheduled_for_processing, + frame); +} + +bool FrameCadenceAdapterImpl::IsZeroHertzScreenshareEnabled() const { + RTC_DCHECK_RUN_ON(queue_); + 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_params_.has_value(); +} + +void FrameCadenceAdapterImpl::MaybeReconfigureAdapters( + bool was_zero_hertz_enabled) { + RTC_DCHECK_RUN_ON(queue_); + bool is_zero_hertz_enabled = IsZeroHertzScreenshareEnabled(); + if (is_zero_hertz_enabled) { + if (!was_zero_hertz_enabled) { + zero_hertz_adapter_.emplace(queue_, clock_, callback_, + source_constraints_->max_fps.value()); + RTC_LOG(LS_INFO) << "Zero hertz mode activated."; + zero_hertz_adapter_created_timestamp_ = clock_->CurrentTime(); + } + zero_hertz_adapter_->ReconfigureParameters(zero_hertz_params_.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(); + } +} + +void FrameCadenceAdapterImpl::MaybeReportFrameRateConstraintUmas() { + RTC_DCHECK_RUN_ON(queue_); + if (has_reported_screenshare_frame_rate_umas_) + return; + has_reported_screenshare_frame_rate_umas_ = true; + if (!zero_hertz_params_.has_value()) + return; + RTC_HISTOGRAM_BOOLEAN("WebRTC.Screenshare.FrameRateConstraints.Exists", + source_constraints_.has_value()); + if (!source_constraints_.has_value()) + return; + RTC_HISTOGRAM_BOOLEAN("WebRTC.Screenshare.FrameRateConstraints.Min.Exists", + source_constraints_->min_fps.has_value()); + if (source_constraints_->min_fps.has_value()) { + RTC_HISTOGRAM_COUNTS_100( + "WebRTC.Screenshare.FrameRateConstraints.Min.Value", + source_constraints_->min_fps.value()); + } + RTC_HISTOGRAM_BOOLEAN("WebRTC.Screenshare.FrameRateConstraints.Max.Exists", + source_constraints_->max_fps.has_value()); + if (source_constraints_->max_fps.has_value()) { + RTC_HISTOGRAM_COUNTS_100( + "WebRTC.Screenshare.FrameRateConstraints.Max.Value", + source_constraints_->max_fps.value()); + } + if (!source_constraints_->min_fps.has_value()) { + if (source_constraints_->max_fps.has_value()) { + RTC_HISTOGRAM_COUNTS_100( + "WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max", + source_constraints_->max_fps.value()); + } + } else if (source_constraints_->max_fps.has_value()) { + if (source_constraints_->min_fps.value() < + source_constraints_->max_fps.value()) { + RTC_HISTOGRAM_COUNTS_100( + "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min", + source_constraints_->min_fps.value()); + RTC_HISTOGRAM_COUNTS_100( + "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max", + source_constraints_->max_fps.value()); + } + // Multi-dimensional histogram for min and max FPS making it possible to + // uncover min and max combinations. See + // https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md#multidimensional-histograms + constexpr int kMaxBucketCount = + 60 * /*max min_fps=*/60 + /*max max_fps=*/60 - 1; + RTC_HISTOGRAM_ENUMERATION_SPARSE( + "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne", + source_constraints_->min_fps.value() * 60 + + source_constraints_->max_fps.value() - 1, + /*boundary=*/kMaxBucketCount); + } +} + +} // namespace + +std::unique_ptr<FrameCadenceAdapterInterface> +FrameCadenceAdapterInterface::Create(Clock* clock, + TaskQueueBase* queue, + const FieldTrialsView& field_trials) { + return std::make_unique<FrameCadenceAdapterImpl>(clock, queue, field_trials); +} + +} // namespace webrtc |