| /* |
| * 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/task_queue_base.h" |
| #include "api/units/time_delta.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/pending_task_safety_flag.h" |
| #include "rtc_base/task_utils/to_queued_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(int spatial_index, bool quality_converged); |
| |
| // Updates spatial layer enabled status. |
| void UpdateLayerStatus(int 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 {} |
| |
| // 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_); |
| |
| 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_); |
| |
| 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(int spatial_index, |
| bool quality_converged) override; |
| void UpdateLayerStatus(int spatial_index, bool enabled) override; |
| void ProcessKeyFrameRequest() override; |
| |
| // VideoFrameSink overrides. |
| void OnFrame(const VideoFrame& frame) override; |
| void OnDiscardedFrame() override { callback_->OnDiscardedFrame(); } |
| 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; |
| |
| // 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}; |
| |
| // Whether to ask for a refresh frame on activation of zero-hertz mode. |
| bool should_request_refresh_frame_ RTC_GUARDED_BY(queue_) = false; |
| |
| 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(); |
| } |
| |
| void ZeroHertzAdapterMode::ReconfigureParameters( |
| const FrameCadenceAdapterInterface::ZeroHertzModeParams& params) { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| RTC_LOG(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( |
| int spatial_index, |
| bool quality_converged) { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| RTC_DCHECK_LT(spatial_index, layer_trackers_.size()); |
| RTC_LOG(LS_INFO) << __func__ << " this " << this << " layer " << spatial_index |
| << " quality has converged: " << quality_converged; |
| if (layer_trackers_[spatial_index].quality_converged.has_value()) |
| layer_trackers_[spatial_index].quality_converged = quality_converged; |
| } |
| |
| void ZeroHertzAdapterMode::UpdateLayerStatus(int spatial_index, bool enabled) { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| RTC_DCHECK_LT(spatial_index, layer_trackers_.size()); |
| 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; |
| |
| // 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( |
| ToQueuedTask(safety_, |
| [this] { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| ProcessOnDelayedCadence(); |
| }), |
| frame_delay_.ms()); |
| } |
| |
| 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 no frame was ever passed to us, request a refresh frame from the source. |
| if (current_frame_id_ == 0) { |
| RTC_LOG(LS_INFO) |
| << __func__ << " this " << this |
| << " requesting refresh frame due to no frames received yet."; |
| callback_->RequestRefreshFrame(); |
| return; |
| } |
| |
| // 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; |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| bool ZeroHertzAdapterMode::HasQualityConverged() const { |
| // 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; |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| void ZeroHertzAdapterMode::ResetQualityConvergenceInfo() { |
| 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; |
| } |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| void ZeroHertzAdapterMode::ProcessOnDelayedCadence() { |
| 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()); |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| void ZeroHertzAdapterMode::ScheduleRepeat(int frame_id, bool idle_repeat) { |
| 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( |
| ToQueuedTask(safety_, |
| [this, frame_id] { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| ProcessRepeatedFrameOnDelayedCadence(frame_id); |
| }), |
| repeat_delay.ms()); |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| void ZeroHertzAdapterMode::ProcessRepeatedFrameOnDelayedCadence(int frame_id) { |
| 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()); |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| void ZeroHertzAdapterMode::SendFrameNow(const VideoFrame& frame) const { |
| 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); |
| } |
| |
| // RTC_RUN_ON(&sequence_checker_) |
| TimeDelta ZeroHertzAdapterMode::RepeatDuration(bool idle_repeat) const { |
| return idle_repeat |
| ? FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod |
| : 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( |
| int spatial_index, |
| bool quality_converged) { |
| if (zero_hertz_adapter_.has_value()) |
| zero_hertz_adapter_->UpdateLayerQualityConvergence(spatial_index, |
| quality_converged); |
| } |
| |
| void FrameCadenceAdapterImpl::UpdateLayerStatus(int 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(); |
| else |
| should_request_refresh_frame_ = true; |
| } |
| |
| 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(ToQueuedTask(safety_.flag(), [this, post_time, frame] { |
| RTC_DCHECK_RUN_ON(queue_); |
| 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::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(ToQueuedTask(safety_.flag(), [this, constraints] { |
| RTC_DCHECK_RUN_ON(queue_); |
| bool was_zero_hertz_enabled = IsZeroHertzScreenshareEnabled(); |
| source_constraints_ = constraints; |
| MaybeReconfigureAdapters(was_zero_hertz_enabled); |
| })); |
| } |
| |
| // RTC_RUN_ON(queue_) |
| void FrameCadenceAdapterImpl::OnFrameOnMainQueue( |
| Timestamp post_time, |
| int frames_scheduled_for_processing, |
| const VideoFrame& 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_params_.has_value(); |
| } |
| |
| // 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(queue_, clock_, callback_, |
| source_constraints_->max_fps.value()); |
| RTC_LOG(LS_INFO) << "Zero hertz mode activated."; |
| |
| if (should_request_refresh_frame_) { |
| // Ensure we get a first frame to work with. |
| should_request_refresh_frame_ = false; |
| callback_->RequestRefreshFrame(); |
| } |
| } |
| 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(); |
| } |
| } |
| |
| // RTC_RUN_ON(queue_) |
| void FrameCadenceAdapterImpl::MaybeReportFrameRateConstraintUmas() { |
| 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 |