| /* |
| * Copyright 2025 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/corruption_detection/frame_selector.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| |
| #include "api/environment/environment.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "api/video/encoded_image.h" |
| #include "api/video/video_frame.h" |
| #include "api/video/video_frame_buffer.h" |
| #include "api/video_codecs/scalability_mode.h" |
| #include "api/video_codecs/video_codec.h" |
| #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" |
| #include "modules/video_coding/svc/scalability_mode_util.h" |
| #include "rtc_base/checks.h" |
| |
| namespace webrtc { |
| |
| constexpr int kVideoRtpTicksPerMs = kVideoPayloadTypeFrequency / 1000; |
| |
| namespace { |
| bool CanNativelyHandleFormat(VideoFrameBuffer::Type type) { |
| switch (type) { |
| case VideoFrameBuffer::Type::kNV12: |
| case VideoFrameBuffer::Type::kI420: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| FrameSelector::FrameSelector(const Environment& env, |
| ScalabilityMode scalability_mode, |
| Timespan low_overhead_frame_span, |
| Timespan high_overhead_frame_span) |
| : inter_layer_pred_mode_( |
| ScalabilityModeToInterLayerPredMode(scalability_mode)), |
| low_overhead_frame_span_(low_overhead_frame_span), |
| high_overhead_frame_span_(high_overhead_frame_span), |
| random_(env.clock().TimeInMicroseconds()) { |
| RTC_DCHECK_GE(low_overhead_frame_span.upper_bound, |
| low_overhead_frame_span.lower_bound); |
| RTC_DCHECK_GE(high_overhead_frame_span.upper_bound, |
| high_overhead_frame_span.lower_bound); |
| } |
| |
| bool FrameSelector::ShouldInstrumentFrame(const VideoFrame& raw_frame, |
| const EncodedImage& encoded_frame) { |
| int layer_id = std::max(encoded_frame.SpatialIndex().value_or(0), |
| encoded_frame.SimulcastIndex().value_or(0)); |
| if (encoded_frame.IsKey()) { |
| // Always insrument keyframes. Clear any state related to this stream. |
| if (inter_layer_pred_mode_ != InterLayerPredMode::kOff) { |
| // When inter layer prediction is enabled, a keyframe clear all reference |
| // buffers and so all layers must be reset. |
| next_timestamp_cutoff_thresholds_.clear(); |
| } else { |
| // For simulcast or S-mode SVC, keyframes happen independently per layer, |
| // so clear only the state for this layer. |
| next_timestamp_cutoff_thresholds_.erase(layer_id); |
| } |
| } |
| |
| Timestamp current_time = encoded_frame.CaptureTime(); |
| if (current_time.IsZero()) { |
| // Use 90kHz RTP clock as capture time. |
| current_time = |
| Timestamp::Millis(encoded_frame.RtpTimestamp() / kVideoRtpTicksPerMs); |
| } |
| |
| bool is_low_overhead = |
| CanNativelyHandleFormat(raw_frame.video_frame_buffer()->type()); |
| const Timespan& span = |
| is_low_overhead ? low_overhead_frame_span_ : high_overhead_frame_span_; |
| |
| bool select_frame; |
| auto it = next_timestamp_cutoff_thresholds_.find(layer_id); |
| if (it == next_timestamp_cutoff_thresholds_.end()) { |
| // First frame for this layer (i.e., part of keyframe). |
| select_frame = true; |
| } else if (current_time >= it->second) { |
| select_frame = true; |
| } else { |
| select_frame = false; |
| } |
| |
| if (select_frame) { |
| // Update threshold. |
| // random uniform between lower and upper bound. |
| int32_t lower = static_cast<int32_t>(span.lower_bound.us()); |
| int32_t upper = static_cast<int32_t>(span.upper_bound.us()); |
| int32_t random_delay_us = random_.Rand(lower, upper); |
| Timestamp next_threshold = |
| current_time + TimeDelta::Micros(random_delay_us); |
| if (it != next_timestamp_cutoff_thresholds_.end()) { |
| it->second = next_threshold; |
| } else { |
| next_timestamp_cutoff_thresholds_.emplace(layer_id, next_threshold); |
| } |
| } |
| |
| return select_frame; |
| } |
| |
| } // namespace webrtc |