Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,148 @@
/*
* 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 "modules/video_coding/utility/bandwidth_quality_scaler.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "api/video/video_adaptation_reason.h"
#include "api/video_codecs/video_encoder.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/bandwidth_quality_scaler_settings.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/exp_filter.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/weak_ptr.h"
namespace webrtc {
namespace {
constexpr int kDefaultMaxWindowSizeMs = 5000;
constexpr float kHigherMaxBitrateTolerationFactor = 0.95;
constexpr float kLowerMinBitrateTolerationFactor = 0.8;
constexpr int kDefaultBitrateStateUpdateIntervalSeconds = 5;
} // namespace
BandwidthQualityScaler::BandwidthQualityScaler(
BandwidthQualityScalerUsageHandlerInterface* handler)
: kBitrateStateUpdateInterval(TimeDelta::Seconds(
BandwidthQualityScalerSettings::ParseFromFieldTrials()
.BitrateStateUpdateInterval()
.value_or(kDefaultBitrateStateUpdateIntervalSeconds))),
handler_(handler),
encoded_bitrate_(kDefaultMaxWindowSizeMs, RateStatistics::kBpsScale),
weak_ptr_factory_(this) {
RTC_DCHECK_RUN_ON(&task_checker_);
RTC_DCHECK(handler_ != nullptr);
StartCheckForBitrate();
}
BandwidthQualityScaler::~BandwidthQualityScaler() {
RTC_DCHECK_RUN_ON(&task_checker_);
}
void BandwidthQualityScaler::StartCheckForBitrate() {
RTC_DCHECK_RUN_ON(&task_checker_);
TaskQueueBase::Current()->PostDelayedTask(
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), this] {
if (!this_weak_ptr) {
// The caller BandwidthQualityScaler has been deleted.
return;
}
RTC_DCHECK_RUN_ON(&task_checker_);
switch (CheckBitrate()) {
case BandwidthQualityScaler::CheckBitrateResult::kHighBitRate: {
handler_->OnReportUsageBandwidthHigh();
last_frame_size_pixels_.reset();
break;
}
case BandwidthQualityScaler::CheckBitrateResult::kLowBitRate: {
handler_->OnReportUsageBandwidthLow();
last_frame_size_pixels_.reset();
break;
}
case BandwidthQualityScaler::CheckBitrateResult::kNormalBitrate: {
break;
}
case BandwidthQualityScaler::CheckBitrateResult::
kInsufficientSamples: {
break;
}
}
StartCheckForBitrate();
},
kBitrateStateUpdateInterval);
}
void BandwidthQualityScaler::ReportEncodeInfo(int frame_size_bytes,
int64_t time_sent_in_ms,
uint32_t encoded_width,
uint32_t encoded_height) {
RTC_DCHECK_RUN_ON(&task_checker_);
last_time_sent_in_ms_ = time_sent_in_ms;
last_frame_size_pixels_ = encoded_width * encoded_height;
encoded_bitrate_.Update(frame_size_bytes, time_sent_in_ms);
}
void BandwidthQualityScaler::SetResolutionBitrateLimits(
const std::vector<VideoEncoder::ResolutionBitrateLimits>&
resolution_bitrate_limits) {
if (resolution_bitrate_limits.empty()) {
resolution_bitrate_limits_ = EncoderInfoSettings::
GetDefaultSinglecastBitrateLimitsWhenQpIsUntrusted();
} else {
resolution_bitrate_limits_ = resolution_bitrate_limits;
}
}
BandwidthQualityScaler::CheckBitrateResult
BandwidthQualityScaler::CheckBitrate() {
RTC_DCHECK_RUN_ON(&task_checker_);
if (!last_frame_size_pixels_.has_value() ||
!last_time_sent_in_ms_.has_value()) {
return BandwidthQualityScaler::CheckBitrateResult::kInsufficientSamples;
}
absl::optional<int64_t> current_bitrate_bps =
encoded_bitrate_.Rate(last_time_sent_in_ms_.value());
if (!current_bitrate_bps.has_value()) {
// We can't get a valid bitrate due to not enough data points.
return BandwidthQualityScaler::CheckBitrateResult::kInsufficientSamples;
}
absl::optional<VideoEncoder::ResolutionBitrateLimits> suitable_bitrate_limit =
EncoderInfoSettings::
GetSinglecastBitrateLimitForResolutionWhenQpIsUntrusted(
last_frame_size_pixels_, resolution_bitrate_limits_);
if (!suitable_bitrate_limit.has_value()) {
return BandwidthQualityScaler::CheckBitrateResult::kInsufficientSamples;
}
// Multiply by toleration factor to solve the frequent adaptation due to
// critical value.
if (current_bitrate_bps > suitable_bitrate_limit->max_bitrate_bps *
kHigherMaxBitrateTolerationFactor) {
return BandwidthQualityScaler::CheckBitrateResult::kLowBitRate;
} else if (current_bitrate_bps <
suitable_bitrate_limit->min_start_bitrate_bps *
kLowerMinBitrateTolerationFactor) {
return BandwidthQualityScaler::CheckBitrateResult::kHighBitRate;
}
return BandwidthQualityScaler::CheckBitrateResult::kNormalBitrate;
}
BandwidthQualityScalerUsageHandlerInterface::
~BandwidthQualityScalerUsageHandlerInterface() {}
} // namespace webrtc

View file

@ -0,0 +1,93 @@
/*
* 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_BANDWIDTH_QUALITY_SCALER_H_
#define MODULES_VIDEO_CODING_UTILITY_BANDWIDTH_QUALITY_SCALER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "absl/types/optional.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/video_codecs/video_encoder.h"
#include "rtc_base/experiments/encoder_info_settings.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/exp_filter.h"
#include "rtc_base/rate_statistics.h"
#include "rtc_base/ref_count.h"
#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/weak_ptr.h"
namespace webrtc {
class BandwidthQualityScalerUsageHandlerInterface {
public:
virtual ~BandwidthQualityScalerUsageHandlerInterface();
virtual void OnReportUsageBandwidthHigh() = 0;
virtual void OnReportUsageBandwidthLow() = 0;
};
// BandwidthQualityScaler runs asynchronously and monitors bandwidth values of
// encoded frames. It holds a reference to a
// BandwidthQualityScalerUsageHandlerInterface implementation to signal an
// overuse or underuse of bandwidth (which indicate a desire to scale the video
// stream down or up).
class BandwidthQualityScaler {
public:
explicit BandwidthQualityScaler(
BandwidthQualityScalerUsageHandlerInterface* handler);
virtual ~BandwidthQualityScaler();
void ReportEncodeInfo(int frame_size_bytes,
int64_t time_sent_in_ms,
uint32_t encoded_width,
uint32_t encoded_height);
// We prioritise to using the |resolution_bitrate_limits| provided by the
// current decoder. If not provided, we will use the default data by
// GetDefaultResolutionBitrateLimits().
void SetResolutionBitrateLimits(
const std::vector<VideoEncoder::ResolutionBitrateLimits>&
resolution_bitrate_limits);
const TimeDelta kBitrateStateUpdateInterval;
private:
enum class CheckBitrateResult {
kInsufficientSamples,
kNormalBitrate,
kHighBitRate,
kLowBitRate,
};
// We will periodically check encode bitrate, this function will make
// resolution up or down decisions and report the decision to the adapter.
void StartCheckForBitrate();
CheckBitrateResult CheckBitrate();
RTC_NO_UNIQUE_ADDRESS SequenceChecker task_checker_;
BandwidthQualityScalerUsageHandlerInterface* const handler_
RTC_GUARDED_BY(&task_checker_);
absl::optional<int64_t> last_time_sent_in_ms_ RTC_GUARDED_BY(&task_checker_);
RateStatistics encoded_bitrate_ RTC_GUARDED_BY(&task_checker_);
absl::optional<int> last_frame_size_pixels_ RTC_GUARDED_BY(&task_checker_);
rtc::WeakPtrFactory<BandwidthQualityScaler> weak_ptr_factory_;
std::vector<VideoEncoder::ResolutionBitrateLimits> resolution_bitrate_limits_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_BANDWIDTH_QUALITY_SCALER_H_

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2019 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 "modules/video_coding/utility/decoded_frames_history.h"
#include <algorithm>
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace video_coding {
DecodedFramesHistory::DecodedFramesHistory(size_t window_size)
: buffer_(window_size) {}
DecodedFramesHistory::~DecodedFramesHistory() = default;
void DecodedFramesHistory::InsertDecoded(int64_t frame_id, uint32_t timestamp) {
last_decoded_frame_ = frame_id;
last_decoded_frame_timestamp_ = timestamp;
int new_index = FrameIdToIndex(frame_id);
RTC_DCHECK(last_frame_id_ < frame_id);
// Clears expired values from the cyclic buffer_.
if (last_frame_id_) {
int64_t id_jump = frame_id - *last_frame_id_;
int last_index = FrameIdToIndex(*last_frame_id_);
if (id_jump >= static_cast<int64_t>(buffer_.size())) {
std::fill(buffer_.begin(), buffer_.end(), false);
} else if (new_index > last_index) {
std::fill(buffer_.begin() + last_index + 1, buffer_.begin() + new_index,
false);
} else {
std::fill(buffer_.begin() + last_index + 1, buffer_.end(), false);
std::fill(buffer_.begin(), buffer_.begin() + new_index, false);
}
}
buffer_[new_index] = true;
last_frame_id_ = frame_id;
}
bool DecodedFramesHistory::WasDecoded(int64_t frame_id) const {
if (!last_frame_id_)
return false;
// Reference to the picture_id out of the stored should happen.
if (frame_id <= *last_frame_id_ - static_cast<int64_t>(buffer_.size())) {
RTC_LOG(LS_WARNING) << "Referencing a frame out of the window. "
"Assuming it was undecoded to avoid artifacts.";
return false;
}
if (frame_id > last_frame_id_)
return false;
return buffer_[FrameIdToIndex(frame_id)];
}
void DecodedFramesHistory::Clear() {
last_decoded_frame_timestamp_.reset();
last_decoded_frame_.reset();
std::fill(buffer_.begin(), buffer_.end(), false);
last_frame_id_.reset();
}
absl::optional<int64_t> DecodedFramesHistory::GetLastDecodedFrameId() const {
return last_decoded_frame_;
}
absl::optional<uint32_t> DecodedFramesHistory::GetLastDecodedFrameTimestamp()
const {
return last_decoded_frame_timestamp_;
}
int DecodedFramesHistory::FrameIdToIndex(int64_t frame_id) const {
int m = frame_id % buffer_.size();
return m >= 0 ? m : m + buffer_.size();
}
} // namespace video_coding
} // namespace webrtc

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_DECODED_FRAMES_HISTORY_H_
#define MODULES_VIDEO_CODING_UTILITY_DECODED_FRAMES_HISTORY_H_
#include <stdint.h>
#include <bitset>
#include <vector>
#include "absl/types/optional.h"
#include "api/video/encoded_frame.h"
namespace webrtc {
namespace video_coding {
class DecodedFramesHistory {
public:
// window_size - how much frames back to the past are actually remembered.
explicit DecodedFramesHistory(size_t window_size);
~DecodedFramesHistory();
// Called for each decoded frame. Assumes frame id's are non-decreasing.
void InsertDecoded(int64_t frame_id, uint32_t timestamp);
// Query if the following (frame_id, spatial_id) pair was inserted before.
// Should be at most less by window_size-1 than the last inserted frame id.
bool WasDecoded(int64_t frame_id) const;
void Clear();
absl::optional<int64_t> GetLastDecodedFrameId() const;
absl::optional<uint32_t> GetLastDecodedFrameTimestamp() const;
private:
int FrameIdToIndex(int64_t frame_id) const;
std::vector<bool> buffer_;
absl::optional<int64_t> last_frame_id_;
absl::optional<int64_t> last_decoded_frame_;
absl::optional<uint32_t> last_decoded_frame_timestamp_;
};
} // namespace video_coding
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_DECODED_FRAMES_HISTORY_H_

View file

@ -0,0 +1,268 @@
/*
* Copyright (c) 2011 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 "modules/video_coding/utility/frame_dropper.h"
#include <algorithm>
namespace webrtc {
namespace {
const float kDefaultFrameSizeAlpha = 0.9f;
const float kDefaultKeyFrameRatioAlpha = 0.99f;
// 1 key frame every 10th second in 30 fps.
const float kDefaultKeyFrameRatioValue = 1 / 300.0f;
const float kDefaultDropRatioAlpha = 0.9f;
const float kDefaultDropRatioValue = 0.96f;
// Maximum duration over which frames are continuously dropped.
const float kDefaultMaxDropDurationSecs = 4.0f;
// Default target bitrate.
// TODO(isheriff): Should this be higher to avoid dropping too many packets when
// the bandwidth is unknown at the start ?
const float kDefaultTargetBitrateKbps = 300.0f;
const float kDefaultIncomingFrameRate = 30;
const float kLeakyBucketSizeSeconds = 0.5f;
// A delta frame that is bigger than `kLargeDeltaFactor` times the average
// delta frame is a large frame that is spread out for accumulation.
const int kLargeDeltaFactor = 3;
// Cap on the frame size accumulator to prevent excessive drops.
const float kAccumulatorCapBufferSizeSecs = 3.0f;
} // namespace
FrameDropper::FrameDropper()
: key_frame_ratio_(kDefaultKeyFrameRatioAlpha),
delta_frame_size_avg_kbits_(kDefaultFrameSizeAlpha),
drop_ratio_(kDefaultDropRatioAlpha, kDefaultDropRatioValue),
enabled_(true),
max_drop_duration_secs_(kDefaultMaxDropDurationSecs) {
Reset();
}
FrameDropper::~FrameDropper() = default;
void FrameDropper::Reset() {
key_frame_ratio_.Reset(kDefaultKeyFrameRatioAlpha);
key_frame_ratio_.Apply(1.0f, kDefaultKeyFrameRatioValue);
delta_frame_size_avg_kbits_.Reset(kDefaultFrameSizeAlpha);
accumulator_ = 0.0f;
accumulator_max_ = kDefaultTargetBitrateKbps / 2;
target_bitrate_ = kDefaultTargetBitrateKbps;
incoming_frame_rate_ = kDefaultIncomingFrameRate;
large_frame_accumulation_count_ = 0;
large_frame_accumulation_chunk_size_ = 0;
large_frame_accumulation_spread_ = 0.5 * kDefaultIncomingFrameRate;
drop_next_ = false;
drop_ratio_.Reset(0.9f);
drop_ratio_.Apply(0.0f, 0.0f);
drop_count_ = 0;
was_below_max_ = true;
}
void FrameDropper::Enable(bool enable) {
enabled_ = enable;
}
void FrameDropper::Fill(size_t framesize_bytes, bool delta_frame) {
if (!enabled_) {
return;
}
float framesize_kbits = 8.0f * static_cast<float>(framesize_bytes) / 1000.0f;
if (!delta_frame) {
key_frame_ratio_.Apply(1.0, 1.0);
// Do not spread if we are already doing it (or we risk dropping bits that
// need accumulation). Given we compute the key frame ratio and spread
// based on that, this should not normally happen.
if (large_frame_accumulation_count_ == 0) {
if (key_frame_ratio_.filtered() > 1e-5 &&
1 / key_frame_ratio_.filtered() < large_frame_accumulation_spread_) {
large_frame_accumulation_count_ =
static_cast<int32_t>(1 / key_frame_ratio_.filtered() + 0.5);
} else {
large_frame_accumulation_count_ =
static_cast<int32_t>(large_frame_accumulation_spread_ + 0.5);
}
large_frame_accumulation_chunk_size_ =
framesize_kbits / large_frame_accumulation_count_;
framesize_kbits = 0;
}
} else {
// Identify if it is an unusually large delta frame and spread accumulation
// if that is the case.
if (delta_frame_size_avg_kbits_.filtered() != -1 &&
(framesize_kbits >
kLargeDeltaFactor * delta_frame_size_avg_kbits_.filtered()) &&
large_frame_accumulation_count_ == 0) {
large_frame_accumulation_count_ =
static_cast<int32_t>(large_frame_accumulation_spread_ + 0.5);
large_frame_accumulation_chunk_size_ =
framesize_kbits / large_frame_accumulation_count_;
framesize_kbits = 0;
} else {
delta_frame_size_avg_kbits_.Apply(1, framesize_kbits);
}
key_frame_ratio_.Apply(1.0, 0.0);
}
// Change the level of the accumulator (bucket)
accumulator_ += framesize_kbits;
CapAccumulator();
}
void FrameDropper::Leak(uint32_t input_framerate) {
if (!enabled_) {
return;
}
if (input_framerate < 1) {
return;
}
if (target_bitrate_ < 0.0f) {
return;
}
// Add lower bound for large frame accumulation spread.
large_frame_accumulation_spread_ = std::max(0.5 * input_framerate, 5.0);
// Expected bits per frame based on current input frame rate.
float expected_bits_per_frame = target_bitrate_ / input_framerate;
if (large_frame_accumulation_count_ > 0) {
expected_bits_per_frame -= large_frame_accumulation_chunk_size_;
--large_frame_accumulation_count_;
}
accumulator_ -= expected_bits_per_frame;
if (accumulator_ < 0.0f) {
accumulator_ = 0.0f;
}
UpdateRatio();
}
void FrameDropper::UpdateRatio() {
if (accumulator_ > 1.3f * accumulator_max_) {
// Too far above accumulator max, react faster.
drop_ratio_.UpdateBase(0.8f);
} else {
// Go back to normal reaction.
drop_ratio_.UpdateBase(0.9f);
}
if (accumulator_ > accumulator_max_) {
// We are above accumulator max, and should ideally drop a frame. Increase
// the drop_ratio_ and drop the frame later.
if (was_below_max_) {
drop_next_ = true;
}
drop_ratio_.Apply(1.0f, 1.0f);
drop_ratio_.UpdateBase(0.9f);
} else {
drop_ratio_.Apply(1.0f, 0.0f);
}
was_below_max_ = accumulator_ < accumulator_max_;
}
// This function signals when to drop frames to the caller. It makes use of the
// drop_ratio_ to smooth out the drops over time.
bool FrameDropper::DropFrame() {
if (!enabled_) {
return false;
}
if (drop_next_) {
drop_next_ = false;
drop_count_ = 0;
}
if (drop_ratio_.filtered() >= 0.5f) { // Drops per keep
// Limit is the number of frames we should drop between each kept frame
// to keep our drop ratio. limit is positive in this case.
float denom = 1.0f - drop_ratio_.filtered();
if (denom < 1e-5) {
denom = 1e-5f;
}
int32_t limit = static_cast<int32_t>(1.0f / denom - 1.0f + 0.5f);
// Put a bound on the max amount of dropped frames between each kept
// frame, in terms of frame rate and window size (secs).
int max_limit =
static_cast<int>(incoming_frame_rate_ * max_drop_duration_secs_);
if (limit > max_limit) {
limit = max_limit;
}
if (drop_count_ < 0) {
// Reset the drop_count_ since it was negative and should be positive.
drop_count_ = -drop_count_;
}
if (drop_count_ < limit) {
// As long we are below the limit we should drop frames.
drop_count_++;
return true;
} else {
// Only when we reset drop_count_ a frame should be kept.
drop_count_ = 0;
return false;
}
} else if (drop_ratio_.filtered() > 0.0f &&
drop_ratio_.filtered() < 0.5f) { // Keeps per drop
// Limit is the number of frames we should keep between each drop
// in order to keep the drop ratio. limit is negative in this case,
// and the drop_count_ is also negative.
float denom = drop_ratio_.filtered();
if (denom < 1e-5) {
denom = 1e-5f;
}
int32_t limit = -static_cast<int32_t>(1.0f / denom - 1.0f + 0.5f);
if (drop_count_ > 0) {
// Reset the drop_count_ since we have a positive
// drop_count_, and it should be negative.
drop_count_ = -drop_count_;
}
if (drop_count_ > limit) {
if (drop_count_ == 0) {
// Drop frames when we reset drop_count_.
drop_count_--;
return true;
} else {
// Keep frames as long as we haven't reached limit.
drop_count_--;
return false;
}
} else {
drop_count_ = 0;
return false;
}
}
drop_count_ = 0;
return false;
}
void FrameDropper::SetRates(float bitrate, float incoming_frame_rate) {
// Bit rate of -1 means infinite bandwidth.
accumulator_max_ = bitrate * kLeakyBucketSizeSeconds;
if (target_bitrate_ > 0.0f && bitrate < target_bitrate_ &&
accumulator_ > accumulator_max_) {
// Rescale the accumulator level if the accumulator max decreases
accumulator_ = bitrate / target_bitrate_ * accumulator_;
}
target_bitrate_ = bitrate;
CapAccumulator();
incoming_frame_rate_ = incoming_frame_rate;
}
// Put a cap on the accumulator, i.e., don't let it grow beyond some level.
// This is a temporary fix for screencasting where very large frames from
// encoder will cause very slow response (too many frame drops).
// TODO(isheriff): Remove this now that large delta frames are also spread out ?
void FrameDropper::CapAccumulator() {
float max_accumulator = target_bitrate_ * kAccumulatorCapBufferSizeSecs;
if (accumulator_ > max_accumulator) {
accumulator_ = max_accumulator;
}
}
} // namespace webrtc

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2011 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_FRAME_DROPPER_H_
#define MODULES_VIDEO_CODING_UTILITY_FRAME_DROPPER_H_
#include <stddef.h>
#include <stdint.h>
#include "rtc_base/numerics/exp_filter.h"
namespace webrtc {
// The Frame Dropper implements a variant of the leaky bucket algorithm
// for keeping track of when to drop frames to avoid bit rate
// over use when the encoder can't keep its bit rate.
class FrameDropper {
public:
FrameDropper();
~FrameDropper();
// Resets the FrameDropper to its initial state.
void Reset();
void Enable(bool enable);
// Answers the question if it's time to drop a frame if we want to reach a
// given frame rate. Must be called for every frame.
//
// Return value : True if we should drop the current frame.
bool DropFrame();
// Updates the FrameDropper with the size of the latest encoded frame.
// The FrameDropper calculates a new drop ratio (can be seen as the
// probability to drop a frame) and updates its internal statistics.
//
// Input:
// - framesize_bytes : The size of the latest frame returned
// from the encoder.
// - delta_frame : True if the encoder returned a delta frame.
void Fill(size_t framesize_bytes, bool delta_frame);
void Leak(uint32_t input_framerate);
// Sets the target bit rate and the frame rate produced by the camera.
//
// Input:
// - bitrate : The target bit rate.
void SetRates(float bitrate, float incoming_frame_rate);
private:
void UpdateRatio();
void CapAccumulator();
rtc::ExpFilter key_frame_ratio_;
rtc::ExpFilter delta_frame_size_avg_kbits_;
// Key frames and large delta frames are not immediately accumulated in the
// bucket since they can immediately overflow the bucket leading to large
// drops on the following packets that may be much smaller. Instead these
// large frames are accumulated over several frames when the bucket leaks.
// `large_frame_accumulation_spread_` represents the number of frames over
// which a large frame is accumulated.
float large_frame_accumulation_spread_;
// `large_frame_accumulation_count_` represents the number of frames left
// to finish accumulating a large frame.
int large_frame_accumulation_count_;
// `large_frame_accumulation_chunk_size_` represents the size of a single
// chunk for large frame accumulation.
float large_frame_accumulation_chunk_size_;
float accumulator_;
float accumulator_max_;
float target_bitrate_;
bool drop_next_;
rtc::ExpFilter drop_ratio_;
int drop_count_;
float incoming_frame_rate_;
bool was_below_max_;
bool enabled_;
const float max_drop_duration_secs_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_FRAME_DROPPER_H_

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 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 "modules/video_coding/utility/framerate_controller_deprecated.h"
#include <stddef.h>
#include <cstdint>
namespace webrtc {
FramerateControllerDeprecated::FramerateControllerDeprecated(
float target_framerate_fps)
: min_frame_interval_ms_(0), framerate_estimator_(1000.0, 1000.0) {
SetTargetRate(target_framerate_fps);
}
void FramerateControllerDeprecated::SetTargetRate(float target_framerate_fps) {
if (target_framerate_fps_ != target_framerate_fps) {
framerate_estimator_.Reset();
if (last_timestamp_ms_) {
framerate_estimator_.Update(1, *last_timestamp_ms_);
}
const size_t target_frame_interval_ms = 1000 / target_framerate_fps;
target_framerate_fps_ = target_framerate_fps;
min_frame_interval_ms_ = 85 * target_frame_interval_ms / 100;
}
}
float FramerateControllerDeprecated::GetTargetRate() {
return *target_framerate_fps_;
}
void FramerateControllerDeprecated::Reset() {
framerate_estimator_.Reset();
last_timestamp_ms_.reset();
}
bool FramerateControllerDeprecated::DropFrame(uint32_t timestamp_ms) const {
if (timestamp_ms < last_timestamp_ms_) {
// Timestamp jumps backward. We can't make adequate drop decision. Don't
// drop this frame. Stats will be reset in AddFrame().
return false;
}
if (Rate(timestamp_ms).value_or(*target_framerate_fps_) >
target_framerate_fps_) {
return true;
}
if (last_timestamp_ms_) {
const int64_t diff_ms =
static_cast<int64_t>(timestamp_ms) - *last_timestamp_ms_;
if (diff_ms < min_frame_interval_ms_) {
return true;
}
}
return false;
}
void FramerateControllerDeprecated::AddFrame(uint32_t timestamp_ms) {
if (timestamp_ms < last_timestamp_ms_) {
// Timestamp jumps backward.
Reset();
}
framerate_estimator_.Update(1, timestamp_ms);
last_timestamp_ms_ = timestamp_ms;
}
absl::optional<float> FramerateControllerDeprecated::Rate(
uint32_t timestamp_ms) const {
return framerate_estimator_.Rate(timestamp_ms);
}
} // namespace webrtc

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_FRAMERATE_CONTROLLER_DEPRECATED_H_
#define MODULES_VIDEO_CODING_UTILITY_FRAMERATE_CONTROLLER_DEPRECATED_H_
#include <stdint.h>
#include "absl/types/optional.h"
#include "rtc_base/rate_statistics.h"
namespace webrtc {
// Please use webrtc::FramerateController instead.
class FramerateControllerDeprecated {
public:
explicit FramerateControllerDeprecated(float target_framerate_fps);
void SetTargetRate(float target_framerate_fps);
float GetTargetRate();
// Advices user to drop next frame in order to reach target framerate.
bool DropFrame(uint32_t timestamp_ms) const;
void AddFrame(uint32_t timestamp_ms);
void Reset();
private:
absl::optional<float> Rate(uint32_t timestamp_ms) const;
absl::optional<float> target_framerate_fps_;
absl::optional<uint32_t> last_timestamp_ms_;
uint32_t min_frame_interval_ms_;
RateStatistics framerate_estimator_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_FRAMERATE_CONTROLLER_DEPRECATED_H_

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
/*
* This file contains definitions that are common to the IvfFileReader and
* IvfFileWriter classes.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_IVF_DEFINES_H_
#define MODULES_VIDEO_CODING_UTILITY_IVF_DEFINES_H_
#include <stddef.h>
namespace webrtc {
constexpr size_t kIvfHeaderSize = 32;
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_IVF_DEFINES_H_

View file

@ -0,0 +1,242 @@
/*
* Copyright (c) 2019 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 "modules/video_coding/utility/ivf_file_reader.h"
#include <string>
#include <vector>
#include "api/video_codecs/video_codec.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "modules/video_coding/utility/ivf_defines.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
constexpr size_t kIvfFrameHeaderSize = 12;
constexpr int kCodecTypeBytesCount = 4;
constexpr uint8_t kFileHeaderStart[kCodecTypeBytesCount] = {'D', 'K', 'I', 'F'};
constexpr uint8_t kVp8Header[kCodecTypeBytesCount] = {'V', 'P', '8', '0'};
constexpr uint8_t kVp9Header[kCodecTypeBytesCount] = {'V', 'P', '9', '0'};
constexpr uint8_t kAv1Header[kCodecTypeBytesCount] = {'A', 'V', '0', '1'};
constexpr uint8_t kH264Header[kCodecTypeBytesCount] = {'H', '2', '6', '4'};
constexpr uint8_t kH265Header[kCodecTypeBytesCount] = {'H', '2', '6', '5'};
// RTP standard required 90kHz clock rate.
constexpr int32_t kRtpClockRateHz = 90000;
} // namespace
std::unique_ptr<IvfFileReader> IvfFileReader::Create(FileWrapper file) {
auto reader =
std::unique_ptr<IvfFileReader>(new IvfFileReader(std::move(file)));
if (!reader->Reset()) {
return nullptr;
}
return reader;
}
IvfFileReader::~IvfFileReader() {
Close();
}
bool IvfFileReader::Reset() {
// Set error to true while initialization.
has_error_ = true;
if (!file_.Rewind()) {
RTC_LOG(LS_ERROR) << "Failed to rewind IVF file";
return false;
}
uint8_t ivf_header[kIvfHeaderSize] = {0};
size_t read = file_.Read(&ivf_header, kIvfHeaderSize);
if (read != kIvfHeaderSize) {
RTC_LOG(LS_ERROR) << "Failed to read IVF header";
return false;
}
if (memcmp(&ivf_header[0], kFileHeaderStart, 4) != 0) {
RTC_LOG(LS_ERROR) << "File is not in IVF format: DKIF header expected";
return false;
}
absl::optional<VideoCodecType> codec_type = ParseCodecType(ivf_header, 8);
if (!codec_type) {
return false;
}
codec_type_ = *codec_type;
width_ = ByteReader<uint16_t>::ReadLittleEndian(&ivf_header[12]);
height_ = ByteReader<uint16_t>::ReadLittleEndian(&ivf_header[14]);
if (width_ == 0 || height_ == 0) {
RTC_LOG(LS_ERROR) << "Invalid IVF header: width or height is 0";
return false;
}
time_scale_ = ByteReader<uint32_t>::ReadLittleEndian(&ivf_header[16]);
if (time_scale_ == 0) {
RTC_LOG(LS_ERROR) << "Invalid IVF header: time scale can't be 0";
return false;
}
num_frames_ = static_cast<size_t>(
ByteReader<uint32_t>::ReadLittleEndian(&ivf_header[24]));
if (num_frames_ <= 0) {
RTC_LOG(LS_ERROR) << "Invalid IVF header: number of frames 0 or negative";
return false;
}
num_read_frames_ = 0;
next_frame_header_ = ReadNextFrameHeader();
if (!next_frame_header_) {
RTC_LOG(LS_ERROR) << "Failed to read 1st frame header";
return false;
}
// Initialization succeed: reset error.
has_error_ = false;
const char* codec_name = CodecTypeToPayloadString(codec_type_);
RTC_LOG(LS_INFO) << "Opened IVF file with codec data of type " << codec_name
<< " at resolution " << width_ << " x " << height_
<< ", using " << time_scale_ << "Hz clock resolution.";
return true;
}
absl::optional<EncodedImage> IvfFileReader::NextFrame() {
if (has_error_ || !HasMoreFrames()) {
return absl::nullopt;
}
rtc::scoped_refptr<EncodedImageBuffer> payload = EncodedImageBuffer::Create();
std::vector<size_t> layer_sizes;
// next_frame_header_ have to be presented by the way how it was loaded. If it
// is missing it means there is a bug in error handling.
RTC_DCHECK(next_frame_header_);
int64_t current_timestamp = next_frame_header_->timestamp;
// The first frame from the file should be marked as Key frame.
bool is_first_frame = num_read_frames_ == 0;
while (next_frame_header_ &&
current_timestamp == next_frame_header_->timestamp) {
// Resize payload to fit next spatial layer.
size_t current_layer_size = next_frame_header_->frame_size;
size_t current_layer_start_pos = payload->size();
payload->Realloc(payload->size() + current_layer_size);
layer_sizes.push_back(current_layer_size);
// Read next layer into payload
size_t read = file_.Read(&payload->data()[current_layer_start_pos],
current_layer_size);
if (read != current_layer_size) {
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
<< ": failed to read frame payload";
has_error_ = true;
return absl::nullopt;
}
num_read_frames_++;
current_timestamp = next_frame_header_->timestamp;
next_frame_header_ = ReadNextFrameHeader();
}
if (!next_frame_header_) {
// If EOF was reached, we need to check that all frames were met.
if (!has_error_ && num_read_frames_ != num_frames_) {
RTC_LOG(LS_ERROR) << "Unexpected EOF";
has_error_ = true;
return absl::nullopt;
}
}
EncodedImage image;
image.capture_time_ms_ = current_timestamp;
image.SetRtpTimestamp(
static_cast<uint32_t>(current_timestamp * kRtpClockRateHz / time_scale_));
image.SetEncodedData(payload);
image.SetSpatialIndex(static_cast<int>(layer_sizes.size()) - 1);
for (size_t i = 0; i < layer_sizes.size(); ++i) {
image.SetSpatialLayerFrameSize(static_cast<int>(i), layer_sizes[i]);
}
if (is_first_frame) {
image._frameType = VideoFrameType::kVideoFrameKey;
}
return image;
}
bool IvfFileReader::Close() {
if (!file_.is_open())
return false;
file_.Close();
return true;
}
absl::optional<VideoCodecType> IvfFileReader::ParseCodecType(uint8_t* buffer,
size_t start_pos) {
if (memcmp(&buffer[start_pos], kVp8Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecVP8;
}
if (memcmp(&buffer[start_pos], kVp9Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecVP9;
}
if (memcmp(&buffer[start_pos], kAv1Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecAV1;
}
if (memcmp(&buffer[start_pos], kH264Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecH264;
}
if (memcmp(&buffer[start_pos], kH265Header, kCodecTypeBytesCount) == 0) {
return VideoCodecType::kVideoCodecH265;
}
has_error_ = true;
RTC_LOG(LS_ERROR) << "Unknown codec type: "
<< std::string(
reinterpret_cast<char const*>(&buffer[start_pos]),
kCodecTypeBytesCount);
return absl::nullopt;
}
absl::optional<IvfFileReader::FrameHeader>
IvfFileReader::ReadNextFrameHeader() {
uint8_t ivf_frame_header[kIvfFrameHeaderSize] = {0};
size_t read = file_.Read(&ivf_frame_header, kIvfFrameHeaderSize);
if (read != kIvfFrameHeaderSize) {
if (read != 0 || !file_.ReadEof()) {
has_error_ = true;
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
<< ": failed to read IVF frame header";
}
return absl::nullopt;
}
FrameHeader header;
header.frame_size = static_cast<size_t>(
ByteReader<uint32_t>::ReadLittleEndian(&ivf_frame_header[0]));
header.timestamp =
ByteReader<uint64_t>::ReadLittleEndian(&ivf_frame_header[4]);
if (header.frame_size == 0) {
has_error_ = true;
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
<< ": invalid frame size";
return absl::nullopt;
}
if (header.timestamp < 0) {
has_error_ = true;
RTC_LOG(LS_ERROR) << "Frame #" << num_read_frames_
<< ": negative timestamp";
return absl::nullopt;
}
return header;
}
} // namespace webrtc

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_
#define MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_
#include <memory>
#include <utility>
#include "absl/types/optional.h"
#include "api/video/encoded_image.h"
#include "api/video_codecs/video_codec.h"
#include "rtc_base/system/file_wrapper.h"
namespace webrtc {
class IvfFileReader {
public:
// Creates IvfFileReader. Returns nullptr if error acquired.
static std::unique_ptr<IvfFileReader> Create(FileWrapper file);
~IvfFileReader();
IvfFileReader(const IvfFileReader&) = delete;
IvfFileReader& operator=(const IvfFileReader&) = delete;
// Reinitializes reader. Returns false if any error acquired.
bool Reset();
// Returns codec type which was used to create this IVF file and which should
// be used to decode EncodedImages from this file.
VideoCodecType GetVideoCodecType() const { return codec_type_; }
// Returns count of frames in this file.
size_t GetFramesCount() const { return num_frames_; }
// Returns next frame or absl::nullopt if any error acquired. Always returns
// absl::nullopt after first error was spotted.
absl::optional<EncodedImage> NextFrame();
bool HasMoreFrames() const { return num_read_frames_ < num_frames_; }
bool HasError() const { return has_error_; }
uint16_t GetFrameWidth() const { return width_; }
uint16_t GetFrameHeight() const { return height_; }
bool Close();
private:
struct FrameHeader {
size_t frame_size;
int64_t timestamp;
};
explicit IvfFileReader(FileWrapper file) : file_(std::move(file)) {}
// Parses codec type from specified position of the buffer. Codec type
// contains kCodecTypeBytesCount bytes and caller has to ensure that buffer
// won't overflow.
absl::optional<VideoCodecType> ParseCodecType(uint8_t* buffer,
size_t start_pos);
absl::optional<FrameHeader> ReadNextFrameHeader();
VideoCodecType codec_type_;
size_t num_frames_;
size_t num_read_frames_;
uint16_t width_;
uint16_t height_;
uint32_t time_scale_;
FileWrapper file_;
absl::optional<FrameHeader> next_frame_header_;
bool has_error_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_IVF_FILE_READER_H_

View file

@ -0,0 +1,255 @@
/*
* Copyright (c) 2016 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 "modules/video_coding/utility/ivf_file_writer.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/video/encoded_image.h"
#include "api/video/video_codec_type.h"
#include "api/video_codecs/video_codec.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "modules/video_coding/utility/ivf_defines.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/system/file_wrapper.h"
// TODO(palmkvist): make logging more informative in the absence of a file name
// (or get one)
namespace webrtc {
namespace {
constexpr int kDefaultWidth = 1280;
constexpr int kDefaultHeight = 720;
} // namespace
IvfFileWriter::IvfFileWriter(FileWrapper file, size_t byte_limit)
: codec_type_(kVideoCodecGeneric),
bytes_written_(0),
byte_limit_(byte_limit),
num_frames_(0),
width_(0),
height_(0),
last_timestamp_(-1),
using_capture_timestamps_(false),
file_(std::move(file)) {
RTC_DCHECK(byte_limit == 0 || kIvfHeaderSize <= byte_limit)
<< "The byte_limit is too low, not even the header will fit.";
}
IvfFileWriter::~IvfFileWriter() {
Close();
}
std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(FileWrapper file,
size_t byte_limit) {
return std::unique_ptr<IvfFileWriter>(
new IvfFileWriter(std::move(file), byte_limit));
}
std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(absl::string_view filename,
size_t byte_limit) {
return std::unique_ptr<IvfFileWriter>(
new IvfFileWriter(FileWrapper::OpenWriteOnly(filename), byte_limit));
}
bool IvfFileWriter::WriteHeader() {
if (!file_.Rewind()) {
RTC_LOG(LS_WARNING) << "Unable to rewind ivf output file.";
return false;
}
uint8_t ivf_header[kIvfHeaderSize] = {0};
ivf_header[0] = 'D';
ivf_header[1] = 'K';
ivf_header[2] = 'I';
ivf_header[3] = 'F';
ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[4], 0); // Version.
ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[6], 32); // Header size.
switch (codec_type_) {
case kVideoCodecVP8:
ivf_header[8] = 'V';
ivf_header[9] = 'P';
ivf_header[10] = '8';
ivf_header[11] = '0';
break;
case kVideoCodecVP9:
ivf_header[8] = 'V';
ivf_header[9] = 'P';
ivf_header[10] = '9';
ivf_header[11] = '0';
break;
case kVideoCodecAV1:
ivf_header[8] = 'A';
ivf_header[9] = 'V';
ivf_header[10] = '0';
ivf_header[11] = '1';
break;
case kVideoCodecH264:
ivf_header[8] = 'H';
ivf_header[9] = '2';
ivf_header[10] = '6';
ivf_header[11] = '4';
break;
case kVideoCodecH265:
ivf_header[8] = 'H';
ivf_header[9] = '2';
ivf_header[10] = '6';
ivf_header[11] = '5';
break;
default:
// For unknown codec type use **** code. You can specify actual payload
// format when playing the video with ffplay: ffplay -f H263 file.ivf
ivf_header[8] = '*';
ivf_header[9] = '*';
ivf_header[10] = '*';
ivf_header[11] = '*';
break;
}
ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[12], width_);
ByteWriter<uint16_t>::WriteLittleEndian(&ivf_header[14], height_);
// Render timestamps are in ms (1/1000 scale), while RTP timestamps use a
// 90kHz clock.
ByteWriter<uint32_t>::WriteLittleEndian(
&ivf_header[16], using_capture_timestamps_ ? 1000 : 90000);
ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[20], 1);
ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[24],
static_cast<uint32_t>(num_frames_));
ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0); // Reserved.
if (!file_.Write(ivf_header, kIvfHeaderSize)) {
RTC_LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
return false;
}
if (bytes_written_ < kIvfHeaderSize) {
bytes_written_ = kIvfHeaderSize;
}
return true;
}
bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type) {
if (encoded_image._encodedWidth == 0 || encoded_image._encodedHeight == 0) {
width_ = kDefaultWidth;
height_ = kDefaultHeight;
} else {
width_ = encoded_image._encodedWidth;
height_ = encoded_image._encodedHeight;
}
using_capture_timestamps_ = encoded_image.RtpTimestamp() == 0;
codec_type_ = codec_type;
if (!WriteHeader())
return false;
const char* codec_name = CodecTypeToPayloadString(codec_type_);
RTC_LOG(LS_WARNING) << "Created IVF file for codec data of type "
<< codec_name << " at resolution " << width_ << " x "
<< height_ << ", using "
<< (using_capture_timestamps_ ? "1" : "90")
<< "kHz clock resolution.";
return true;
}
bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type) {
if (!file_.is_open())
return false;
if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
return false;
RTC_DCHECK_EQ(codec_type_, codec_type);
int64_t timestamp = using_capture_timestamps_
? encoded_image.capture_time_ms_
: wrap_handler_.Unwrap(encoded_image.RtpTimestamp());
if (last_timestamp_ != -1 && timestamp < last_timestamp_) {
RTC_LOG(LS_WARNING) << "Timestamp not increasing: " << last_timestamp_
<< " -> " << timestamp;
}
last_timestamp_ = timestamp;
bool written_frames = false;
size_t max_sl_index = encoded_image.SpatialIndex().value_or(0);
const uint8_t* data = encoded_image.data();
for (size_t sl_idx = 0; sl_idx <= max_sl_index; ++sl_idx) {
size_t cur_size = encoded_image.SpatialLayerFrameSize(sl_idx).value_or(0);
if (cur_size > 0) {
written_frames = true;
if (!WriteOneSpatialLayer(timestamp, data, cur_size)) {
return false;
}
data += cur_size;
}
}
// If frame has only one spatial layer it won't have any spatial layers'
// sizes. Therefore this case should be addressed separately.
if (!written_frames) {
return WriteOneSpatialLayer(timestamp, data, encoded_image.size());
} else {
return true;
}
}
bool IvfFileWriter::WriteOneSpatialLayer(int64_t timestamp,
const uint8_t* data,
size_t size) {
const size_t kFrameHeaderSize = 12;
if (byte_limit_ != 0 &&
bytes_written_ + kFrameHeaderSize + size > byte_limit_) {
RTC_LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
<< byte_limit_ << " bytes.";
Close();
return false;
}
uint8_t frame_header[kFrameHeaderSize] = {};
ByteWriter<uint32_t>::WriteLittleEndian(&frame_header[0],
static_cast<uint32_t>(size));
ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
if (!file_.Write(frame_header, kFrameHeaderSize) ||
!file_.Write(data, size)) {
RTC_LOG(LS_ERROR) << "Unable to write frame to file.";
return false;
}
bytes_written_ += kFrameHeaderSize + size;
++num_frames_;
return true;
}
bool IvfFileWriter::Close() {
if (!file_.is_open())
return false;
if (num_frames_ == 0) {
file_.Close();
return true;
}
bool ret = WriteHeader();
file_.Close();
return ret;
}
} // namespace webrtc

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2016 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_IVF_FILE_WRITER_H_
#define MODULES_VIDEO_CODING_UTILITY_IVF_FILE_WRITER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "absl/strings/string_view.h"
#include "api/video/encoded_image.h"
#include "api/video/video_codec_type.h"
#include "rtc_base/numerics/sequence_number_unwrapper.h"
#include "rtc_base/system/file_wrapper.h"
namespace webrtc {
class IvfFileWriter {
public:
// Takes ownership of the file, which will be closed either through
// Close or ~IvfFileWriter. If writing a frame would take the file above the
// `byte_limit` the file will be closed, the write (and all future writes)
// will fail. A `byte_limit` of 0 is equivalent to no limit.
static std::unique_ptr<IvfFileWriter> Wrap(FileWrapper file,
size_t byte_limit);
static std::unique_ptr<IvfFileWriter> Wrap(absl::string_view filename,
size_t byte_limit);
~IvfFileWriter();
IvfFileWriter(const IvfFileWriter&) = delete;
IvfFileWriter& operator=(const IvfFileWriter&) = delete;
bool WriteFrame(const EncodedImage& encoded_image, VideoCodecType codec_type);
bool Close();
private:
explicit IvfFileWriter(FileWrapper file, size_t byte_limit);
bool WriteHeader();
bool InitFromFirstFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type);
bool WriteOneSpatialLayer(int64_t timestamp,
const uint8_t* data,
size_t size);
VideoCodecType codec_type_;
size_t bytes_written_;
size_t byte_limit_;
size_t num_frames_;
uint16_t width_;
uint16_t height_;
int64_t last_timestamp_;
bool using_capture_timestamps_;
RtpTimestampUnwrapper wrap_handler_;
FileWrapper file_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_IVF_FILE_WRITER_H_

View file

@ -0,0 +1,69 @@
/*
* 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 "modules/video_coding/utility/qp_parser.h"
#include "modules/video_coding/utility/vp8_header_parser.h"
#include "modules/video_coding/utility/vp9_uncompressed_header_parser.h"
namespace webrtc {
absl::optional<uint32_t> QpParser::Parse(VideoCodecType codec_type,
size_t spatial_idx,
const uint8_t* frame_data,
size_t frame_size) {
if (frame_data == nullptr || frame_size == 0 ||
spatial_idx >= kMaxSimulcastStreams) {
return absl::nullopt;
}
if (codec_type == kVideoCodecVP8) {
int qp = -1;
if (vp8::GetQp(frame_data, frame_size, &qp)) {
return qp;
}
} else if (codec_type == kVideoCodecVP9) {
int qp = -1;
if (vp9::GetQp(frame_data, frame_size, &qp)) {
return qp;
}
} else if (codec_type == kVideoCodecH264) {
return h264_parsers_[spatial_idx].Parse(frame_data, frame_size);
} else if (codec_type == kVideoCodecH265) {
// H.265 bitstream parser is conditionally built.
#ifdef RTC_ENABLE_H265
return h265_parsers_[spatial_idx].Parse(frame_data, frame_size);
#endif
}
return absl::nullopt;
}
absl::optional<uint32_t> QpParser::H264QpParser::Parse(
const uint8_t* frame_data,
size_t frame_size) {
MutexLock lock(&mutex_);
bitstream_parser_.ParseBitstream(
rtc::ArrayView<const uint8_t>(frame_data, frame_size));
return bitstream_parser_.GetLastSliceQp();
}
#ifdef RTC_ENABLE_H265
absl::optional<uint32_t> QpParser::H265QpParser::Parse(
const uint8_t* frame_data,
size_t frame_size) {
MutexLock lock(&mutex_);
bitstream_parser_.ParseBitstream(
rtc::ArrayView<const uint8_t>(frame_data, frame_size));
return bitstream_parser_.GetLastSliceQp();
}
#endif
} // namespace webrtc

View file

@ -0,0 +1,63 @@
/*
* 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_
#define MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_
#include "absl/types/optional.h"
#include "api/video/video_codec_constants.h"
#include "api/video/video_codec_type.h"
#include "common_video/h264/h264_bitstream_parser.h"
#ifdef RTC_ENABLE_H265
#include "common_video/h265/h265_bitstream_parser.h"
#endif
#include "rtc_base/synchronization/mutex.h"
namespace webrtc {
class QpParser {
public:
absl::optional<uint32_t> Parse(VideoCodecType codec_type,
size_t spatial_idx,
const uint8_t* frame_data,
size_t frame_size);
private:
// A thread safe wrapper for H264 bitstream parser.
class H264QpParser {
public:
absl::optional<uint32_t> Parse(const uint8_t* frame_data,
size_t frame_size);
private:
Mutex mutex_;
H264BitstreamParser bitstream_parser_ RTC_GUARDED_BY(mutex_);
};
H264QpParser h264_parsers_[kMaxSimulcastStreams];
#ifdef RTC_ENABLE_H265
// A thread safe wrapper for H.265 bitstream parser.
class H265QpParser {
public:
absl::optional<uint32_t> Parse(const uint8_t* frame_data,
size_t frame_size);
private:
Mutex mutex_;
H265BitstreamParser bitstream_parser_ RTC_GUARDED_BY(mutex_);
};
H265QpParser h265_parsers_[kMaxSimulcastStreams];
#endif
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_QP_PARSER_H_

View file

@ -0,0 +1,336 @@
/*
* Copyright (c) 2014 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 "modules/video_coding/utility/quality_scaler.h"
#include <memory>
#include <utility>
#include "api/field_trials_view.h"
#include "api/units/time_delta.h"
#include "api/video/video_adaptation_reason.h"
#include "rtc_base/checks.h"
#include "rtc_base/experiments/quality_scaler_settings.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/exp_filter.h"
#include "rtc_base/weak_ptr.h"
namespace webrtc {
namespace {
// Threshold constant used until first downscale (to permit fast rampup).
static const int kMeasureMs = 2000;
static const float kSamplePeriodScaleFactor = 2.5;
static const int kFramedropPercentThreshold = 60;
static const size_t kMinFramesNeededToScale = 2 * 30;
} // namespace
class QualityScaler::QpSmoother {
public:
explicit QpSmoother(float alpha)
: alpha_(alpha),
// The initial value of last_sample_ms doesn't matter since the smoother
// will ignore the time delta for the first update.
last_sample_ms_(0),
smoother_(alpha) {}
absl::optional<int> GetAvg() const {
float value = smoother_.filtered();
if (value == rtc::ExpFilter::kValueUndefined) {
return absl::nullopt;
}
return static_cast<int>(value);
}
void Add(float sample, int64_t time_sent_us) {
int64_t now_ms = time_sent_us / 1000;
smoother_.Apply(static_cast<float>(now_ms - last_sample_ms_), sample);
last_sample_ms_ = now_ms;
}
void Reset() { smoother_.Reset(alpha_); }
private:
const float alpha_;
int64_t last_sample_ms_;
rtc::ExpFilter smoother_;
};
// The QualityScaler checks for QP periodically by queuing CheckQpTasks. The
// task will either run to completion and trigger a new task being queued, or it
// will be destroyed because the QualityScaler is destroyed.
//
// When high or low QP is reported, the task will be pending until a callback is
// invoked. This lets the QualityScalerQpUsageHandlerInterface react to QP usage
// asynchronously and prevents checking for QP until the stream has potentially
// been reconfigured.
class QualityScaler::CheckQpTask {
public:
// The result of one CheckQpTask may influence the delay of the next
// CheckQpTask.
struct Result {
bool observed_enough_frames = false;
bool qp_usage_reported = false;
};
CheckQpTask(QualityScaler* quality_scaler, Result previous_task_result)
: quality_scaler_(quality_scaler),
state_(State::kNotStarted),
previous_task_result_(previous_task_result),
weak_ptr_factory_(this) {}
void StartDelayedTask() {
RTC_DCHECK_EQ(state_, State::kNotStarted);
state_ = State::kCheckingQp;
TaskQueueBase::Current()->PostDelayedTask(
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), this] {
if (!this_weak_ptr) {
// The task has been cancelled through destruction.
return;
}
RTC_DCHECK_EQ(state_, State::kCheckingQp);
RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
switch (quality_scaler_->CheckQp()) {
case QualityScaler::CheckQpResult::kInsufficientSamples: {
result_.observed_enough_frames = false;
// After this line, `this` may be deleted.
break;
}
case QualityScaler::CheckQpResult::kNormalQp: {
result_.observed_enough_frames = true;
break;
}
case QualityScaler::CheckQpResult::kHighQp: {
result_.observed_enough_frames = true;
result_.qp_usage_reported = true;
quality_scaler_->fast_rampup_ = false;
quality_scaler_->handler_->OnReportQpUsageHigh();
quality_scaler_->ClearSamples();
break;
}
case QualityScaler::CheckQpResult::kLowQp: {
result_.observed_enough_frames = true;
result_.qp_usage_reported = true;
quality_scaler_->handler_->OnReportQpUsageLow();
quality_scaler_->ClearSamples();
break;
}
}
state_ = State::kCompleted;
// Starting the next task deletes the pending task. After this line,
// `this` has been deleted.
quality_scaler_->StartNextCheckQpTask();
},
TimeDelta::Millis(GetCheckingQpDelayMs()));
}
bool HasCompletedTask() const { return state_ == State::kCompleted; }
Result result() const {
RTC_DCHECK(HasCompletedTask());
return result_;
}
private:
enum class State {
kNotStarted,
kCheckingQp,
kCompleted,
};
// Determines the sampling period of CheckQpTasks.
int64_t GetCheckingQpDelayMs() const {
RTC_DCHECK_RUN_ON(&quality_scaler_->task_checker_);
if (quality_scaler_->fast_rampup_) {
return quality_scaler_->sampling_period_ms_;
}
if (quality_scaler_->experiment_enabled_ &&
!previous_task_result_.observed_enough_frames) {
// Use half the interval while waiting for enough frames.
return quality_scaler_->sampling_period_ms_ / 2;
}
if (quality_scaler_->scale_factor_ &&
!previous_task_result_.qp_usage_reported) {
// Last CheckQp did not call AdaptDown/Up, possibly reduce interval.
return quality_scaler_->sampling_period_ms_ *
quality_scaler_->scale_factor_.value();
}
return quality_scaler_->sampling_period_ms_ *
quality_scaler_->initial_scale_factor_;
}
QualityScaler* const quality_scaler_;
State state_;
const Result previous_task_result_;
Result result_;
rtc::WeakPtrFactory<CheckQpTask> weak_ptr_factory_;
};
QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
VideoEncoder::QpThresholds thresholds,
const FieldTrialsView& field_trials)
: QualityScaler(handler, thresholds, field_trials, kMeasureMs) {}
// Protected ctor, should not be called directly.
QualityScaler::QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
VideoEncoder::QpThresholds thresholds,
const FieldTrialsView& field_trials,
int64_t default_sampling_period_ms)
: handler_(handler),
thresholds_(thresholds),
sampling_period_ms_(QualityScalerSettings(field_trials)
.SamplingPeriodMs()
.value_or(default_sampling_period_ms)),
fast_rampup_(true),
// Arbitrarily choose size based on 30 fps for 5 seconds.
average_qp_(QualityScalerSettings(field_trials)
.AverageQpWindow()
.value_or(5 * 30)),
framedrop_percent_media_opt_(5 * 30),
framedrop_percent_all_(5 * 30),
experiment_enabled_(QualityScalingExperiment::Enabled(field_trials)),
min_frames_needed_(QualityScalerSettings(field_trials)
.MinFrames()
.value_or(kMinFramesNeededToScale)),
initial_scale_factor_(QualityScalerSettings(field_trials)
.InitialScaleFactor()
.value_or(kSamplePeriodScaleFactor)),
scale_factor_(QualityScalerSettings(field_trials).ScaleFactor()) {
RTC_DCHECK_RUN_ON(&task_checker_);
if (experiment_enabled_) {
config_ = QualityScalingExperiment::GetConfig(field_trials);
qp_smoother_high_.reset(new QpSmoother(config_.alpha_high));
qp_smoother_low_.reset(new QpSmoother(config_.alpha_low));
}
RTC_DCHECK(handler_ != nullptr);
StartNextCheckQpTask();
RTC_LOG(LS_INFO) << "QP thresholds: low: " << thresholds_.low
<< ", high: " << thresholds_.high;
}
QualityScaler::~QualityScaler() {
RTC_DCHECK_RUN_ON(&task_checker_);
}
void QualityScaler::StartNextCheckQpTask() {
RTC_DCHECK_RUN_ON(&task_checker_);
RTC_DCHECK(!pending_qp_task_ || pending_qp_task_->HasCompletedTask())
<< "A previous CheckQpTask has not completed yet!";
CheckQpTask::Result previous_task_result;
if (pending_qp_task_) {
previous_task_result = pending_qp_task_->result();
}
pending_qp_task_ = std::make_unique<CheckQpTask>(this, previous_task_result);
pending_qp_task_->StartDelayedTask();
}
void QualityScaler::SetQpThresholds(VideoEncoder::QpThresholds thresholds) {
RTC_DCHECK_RUN_ON(&task_checker_);
thresholds_ = thresholds;
}
void QualityScaler::ReportDroppedFrameByMediaOpt() {
RTC_DCHECK_RUN_ON(&task_checker_);
framedrop_percent_media_opt_.AddSample(100);
framedrop_percent_all_.AddSample(100);
}
void QualityScaler::ReportDroppedFrameByEncoder() {
RTC_DCHECK_RUN_ON(&task_checker_);
framedrop_percent_all_.AddSample(100);
}
void QualityScaler::ReportQp(int qp, int64_t time_sent_us) {
RTC_DCHECK_RUN_ON(&task_checker_);
framedrop_percent_media_opt_.AddSample(0);
framedrop_percent_all_.AddSample(0);
average_qp_.AddSample(qp);
if (qp_smoother_high_)
qp_smoother_high_->Add(qp, time_sent_us);
if (qp_smoother_low_)
qp_smoother_low_->Add(qp, time_sent_us);
}
bool QualityScaler::QpFastFilterLow() const {
RTC_DCHECK_RUN_ON(&task_checker_);
size_t num_frames = config_.use_all_drop_reasons
? framedrop_percent_all_.Size()
: framedrop_percent_media_opt_.Size();
const size_t kMinNumFrames = 10;
if (num_frames < kMinNumFrames) {
return false; // Wait for more frames before making a decision.
}
absl::optional<int> avg_qp_high = qp_smoother_high_
? qp_smoother_high_->GetAvg()
: average_qp_.GetAverageRoundedDown();
return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false;
}
QualityScaler::CheckQpResult QualityScaler::CheckQp() const {
RTC_DCHECK_RUN_ON(&task_checker_);
// Should be set through InitEncode -> Should be set by now.
RTC_DCHECK_GE(thresholds_.low, 0);
// If we have not observed at least this many frames we can't make a good
// scaling decision.
const size_t frames = config_.use_all_drop_reasons
? framedrop_percent_all_.Size()
: framedrop_percent_media_opt_.Size();
if (frames < min_frames_needed_) {
return CheckQpResult::kInsufficientSamples;
}
// Check if we should scale down due to high frame drop.
const absl::optional<int> drop_rate =
config_.use_all_drop_reasons
? framedrop_percent_all_.GetAverageRoundedDown()
: framedrop_percent_media_opt_.GetAverageRoundedDown();
if (drop_rate && *drop_rate >= kFramedropPercentThreshold) {
RTC_LOG(LS_INFO) << "Reporting high QP, framedrop percent " << *drop_rate;
return CheckQpResult::kHighQp;
}
// Check if we should scale up or down based on QP.
const absl::optional<int> avg_qp_high =
qp_smoother_high_ ? qp_smoother_high_->GetAvg()
: average_qp_.GetAverageRoundedDown();
const absl::optional<int> avg_qp_low =
qp_smoother_low_ ? qp_smoother_low_->GetAvg()
: average_qp_.GetAverageRoundedDown();
if (avg_qp_high && avg_qp_low) {
RTC_LOG(LS_INFO) << "Checking average QP " << *avg_qp_high << " ("
<< *avg_qp_low << ").";
if (*avg_qp_high > thresholds_.high) {
return CheckQpResult::kHighQp;
}
if (*avg_qp_low <= thresholds_.low) {
// QP has been low. We want to try a higher resolution.
return CheckQpResult::kLowQp;
}
}
return CheckQpResult::kNormalQp;
}
void QualityScaler::ClearSamples() {
RTC_DCHECK_RUN_ON(&task_checker_);
framedrop_percent_media_opt_.Reset();
framedrop_percent_all_.Reset();
average_qp_.Reset();
if (qp_smoother_high_)
qp_smoother_high_->Reset();
if (qp_smoother_low_)
qp_smoother_low_->Reset();
}
QualityScalerQpUsageHandlerInterface::~QualityScalerQpUsageHandlerInterface() {}
} // namespace webrtc

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
#define MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "absl/types/optional.h"
#include "api/field_trials_view.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/video_codecs/video_encoder.h"
#include "rtc_base/experiments/quality_scaling_experiment.h"
#include "rtc_base/numerics/moving_average.h"
#include "rtc_base/ref_count.h"
#include "rtc_base/system/no_unique_address.h"
namespace webrtc {
class QualityScalerQpUsageHandlerCallbackInterface;
class QualityScalerQpUsageHandlerInterface;
// QualityScaler runs asynchronously and monitors QP values of encoded frames.
// It holds a reference to a QualityScalerQpUsageHandlerInterface implementation
// to signal an overuse or underuse of QP (which indicate a desire to scale the
// video stream down or up).
class QualityScaler {
public:
// Construct a QualityScaler with given `thresholds` and `handler`.
// This starts the quality scaler periodically checking what the average QP
// has been recently.
QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
VideoEncoder::QpThresholds thresholds,
const FieldTrialsView& field_trials);
virtual ~QualityScaler();
// Should be called each time a frame is dropped at encoding.
void ReportDroppedFrameByMediaOpt();
void ReportDroppedFrameByEncoder();
// Inform the QualityScaler of the last seen QP.
void ReportQp(int qp, int64_t time_sent_us);
void SetQpThresholds(VideoEncoder::QpThresholds thresholds);
bool QpFastFilterLow() const;
// The following members declared protected for testing purposes.
protected:
QualityScaler(QualityScalerQpUsageHandlerInterface* handler,
VideoEncoder::QpThresholds thresholds,
const FieldTrialsView& field_trials,
int64_t sampling_period_ms);
private:
class QpSmoother;
class CheckQpTask;
class CheckQpTaskHandlerCallback;
enum class CheckQpResult {
kInsufficientSamples,
kNormalQp,
kHighQp,
kLowQp,
};
// Starts checking for QP in a delayed task. When the resulting CheckQpTask
// completes, it will invoke this method again, ensuring that we always
// periodically check for QP. See CheckQpTask for more details. We never run
// more than one CheckQpTask at a time.
void StartNextCheckQpTask();
CheckQpResult CheckQp() const;
void ClearSamples();
std::unique_ptr<CheckQpTask> pending_qp_task_ RTC_GUARDED_BY(&task_checker_);
QualityScalerQpUsageHandlerInterface* const handler_
RTC_GUARDED_BY(&task_checker_);
RTC_NO_UNIQUE_ADDRESS SequenceChecker task_checker_;
VideoEncoder::QpThresholds thresholds_ RTC_GUARDED_BY(&task_checker_);
const int64_t sampling_period_ms_;
bool fast_rampup_ RTC_GUARDED_BY(&task_checker_);
rtc::MovingAverage average_qp_ RTC_GUARDED_BY(&task_checker_);
rtc::MovingAverage framedrop_percent_media_opt_
RTC_GUARDED_BY(&task_checker_);
rtc::MovingAverage framedrop_percent_all_ RTC_GUARDED_BY(&task_checker_);
// Used by QualityScalingExperiment.
const bool experiment_enabled_;
QualityScalingExperiment::Config config_ RTC_GUARDED_BY(&task_checker_);
std::unique_ptr<QpSmoother> qp_smoother_high_ RTC_GUARDED_BY(&task_checker_);
std::unique_ptr<QpSmoother> qp_smoother_low_ RTC_GUARDED_BY(&task_checker_);
const size_t min_frames_needed_;
const double initial_scale_factor_;
const absl::optional<double> scale_factor_;
};
// Reacts to QP being too high or too low. For best quality, when QP is high it
// is desired to decrease the resolution or frame rate of the stream and when QP
// is low it is desired to increase the resolution or frame rate of the stream.
// Whether to reconfigure the stream is ultimately up to the handler, which is
// able to respond asynchronously.
class QualityScalerQpUsageHandlerInterface {
public:
virtual ~QualityScalerQpUsageHandlerInterface();
virtual void OnReportQpUsageHigh() = 0;
virtual void OnReportQpUsageLow() = 0;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_

View file

@ -0,0 +1,343 @@
/*
* Copyright (c) 2016 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 "modules/video_coding/utility/simulcast_rate_allocator.h"
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <numeric>
#include <string>
#include <tuple>
#include <vector>
#include "rtc_base/checks.h"
#include "rtc_base/experiments/rate_control_settings.h"
#include "system_wrappers/include/field_trial.h"
namespace webrtc {
namespace {
// Ratio allocation between temporal streams:
// Values as required for the VP8 codec (accumulating).
static const float
kLayerRateAllocation[kMaxTemporalStreams][kMaxTemporalStreams] = {
{1.0f, 1.0f, 1.0f, 1.0f}, // 1 layer
{0.6f, 1.0f, 1.0f, 1.0f}, // 2 layers {60%, 40%}
{0.4f, 0.6f, 1.0f, 1.0f}, // 3 layers {40%, 20%, 40%}
{0.25f, 0.4f, 0.6f, 1.0f} // 4 layers {25%, 15%, 20%, 40%}
};
static const float kBaseHeavy3TlRateAllocation[kMaxTemporalStreams] = {
0.6f, 0.8f, 1.0f, 1.0f // 3 layers {60%, 20%, 20%}
};
const uint32_t kLegacyScreenshareTl0BitrateKbps = 200;
const uint32_t kLegacyScreenshareTl1BitrateKbps = 1000;
} // namespace
float SimulcastRateAllocator::GetTemporalRateAllocation(
int num_layers,
int temporal_id,
bool base_heavy_tl3_alloc) {
RTC_CHECK_GT(num_layers, 0);
RTC_CHECK_LE(num_layers, kMaxTemporalStreams);
RTC_CHECK_GE(temporal_id, 0);
RTC_CHECK_LT(temporal_id, num_layers);
if (num_layers == 3 && base_heavy_tl3_alloc) {
return kBaseHeavy3TlRateAllocation[temporal_id];
}
return kLayerRateAllocation[num_layers - 1][temporal_id];
}
SimulcastRateAllocator::SimulcastRateAllocator(const VideoCodec& codec)
: codec_(codec),
stable_rate_settings_(StableTargetRateExperiment::ParseFromFieldTrials()),
rate_control_settings_(RateControlSettings::ParseFromFieldTrials()),
legacy_conference_mode_(false) {}
SimulcastRateAllocator::~SimulcastRateAllocator() = default;
VideoBitrateAllocation SimulcastRateAllocator::Allocate(
VideoBitrateAllocationParameters parameters) {
VideoBitrateAllocation allocated_bitrates;
DataRate stable_rate = parameters.total_bitrate;
if (stable_rate_settings_.IsEnabled() &&
parameters.stable_bitrate > DataRate::Zero()) {
stable_rate = std::min(parameters.stable_bitrate, parameters.total_bitrate);
}
DistributeAllocationToSimulcastLayers(parameters.total_bitrate, stable_rate,
&allocated_bitrates);
DistributeAllocationToTemporalLayers(&allocated_bitrates);
return allocated_bitrates;
}
void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers(
DataRate total_bitrate,
DataRate stable_bitrate,
VideoBitrateAllocation* allocated_bitrates) {
DataRate left_in_total_allocation = total_bitrate;
DataRate left_in_stable_allocation = stable_bitrate;
if (codec_.maxBitrate) {
DataRate max_rate = DataRate::KilobitsPerSec(codec_.maxBitrate);
left_in_total_allocation = std::min(left_in_total_allocation, max_rate);
left_in_stable_allocation = std::min(left_in_stable_allocation, max_rate);
}
if (codec_.numberOfSimulcastStreams == 0) {
// No simulcast, just set the target as this has been capped already.
if (codec_.active) {
allocated_bitrates->SetBitrate(
0, 0,
std::max(DataRate::KilobitsPerSec(codec_.minBitrate),
left_in_total_allocation)
.bps());
}
return;
}
// Sort the layers by maxFramerate, they might not always be from smallest
// to biggest
std::vector<size_t> layer_index(codec_.numberOfSimulcastStreams);
std::iota(layer_index.begin(), layer_index.end(), 0);
std::stable_sort(layer_index.begin(), layer_index.end(),
[this](size_t a, size_t b) {
return std::tie(codec_.simulcastStream[a].maxBitrate) <
std::tie(codec_.simulcastStream[b].maxBitrate);
});
// Find the first active layer. We don't allocate to inactive layers.
size_t active_layer = 0;
for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
if (codec_.simulcastStream[layer_index[active_layer]].active) {
// Found the first active layer.
break;
}
}
// All streams could be inactive, and nothing more to do.
if (active_layer == codec_.numberOfSimulcastStreams) {
return;
}
// Always allocate enough bitrate for the minimum bitrate of the first
// active layer. Suspending below min bitrate is controlled outside the
// codec implementation and is not overridden by this.
DataRate min_rate = DataRate::KilobitsPerSec(
codec_.simulcastStream[layer_index[active_layer]].minBitrate);
left_in_total_allocation = std::max(left_in_total_allocation, min_rate);
left_in_stable_allocation = std::max(left_in_stable_allocation, min_rate);
// Begin by allocating bitrate to simulcast streams, putting all bitrate in
// temporal layer 0. We'll then distribute this bitrate, across potential
// temporal layers, when stream allocation is done.
bool first_allocation = false;
if (stream_enabled_.empty()) {
// First time allocating, this means we should not include hysteresis in
// case this is a reconfiguration of an existing enabled stream.
first_allocation = true;
stream_enabled_.resize(codec_.numberOfSimulcastStreams, false);
}
size_t top_active_layer = active_layer;
// Allocate up to the target bitrate for each active simulcast layer.
for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
const SimulcastStream& stream =
codec_.simulcastStream[layer_index[active_layer]];
if (!stream.active) {
stream_enabled_[layer_index[active_layer]] = false;
continue;
}
// If we can't allocate to the current layer we can't allocate to higher
// layers because they require a higher minimum bitrate.
DataRate min_bitrate = DataRate::KilobitsPerSec(stream.minBitrate);
DataRate target_bitrate = DataRate::KilobitsPerSec(stream.targetBitrate);
double hysteresis_factor =
codec_.mode == VideoCodecMode::kRealtimeVideo
? stable_rate_settings_.GetVideoHysteresisFactor()
: stable_rate_settings_.GetScreenshareHysteresisFactor();
if (!first_allocation && !stream_enabled_[layer_index[active_layer]]) {
min_bitrate = std::min(hysteresis_factor * min_bitrate, target_bitrate);
}
if (left_in_stable_allocation < min_bitrate) {
allocated_bitrates->set_bw_limited(true);
break;
}
// We are allocating to this layer so it is the current active allocation.
top_active_layer = layer_index[active_layer];
stream_enabled_[layer_index[active_layer]] = true;
DataRate layer_rate = std::min(left_in_total_allocation, target_bitrate);
allocated_bitrates->SetBitrate(layer_index[active_layer], 0,
layer_rate.bps());
left_in_total_allocation -= layer_rate;
left_in_stable_allocation -=
std::min(left_in_stable_allocation, target_bitrate);
}
// All layers above this one are not active.
for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) {
stream_enabled_[layer_index[active_layer]] = false;
}
// Next, try allocate remaining bitrate, up to max bitrate, in top active
// stream.
// TODO(sprang): Allocate up to max bitrate for all layers once we have a
// better idea of possible performance implications.
if (left_in_total_allocation > DataRate::Zero()) {
const SimulcastStream& stream = codec_.simulcastStream[top_active_layer];
DataRate initial_layer_rate = DataRate::BitsPerSec(
allocated_bitrates->GetSpatialLayerSum(top_active_layer));
DataRate additional_allocation = std::min(
left_in_total_allocation,
DataRate::KilobitsPerSec(stream.maxBitrate) - initial_layer_rate);
allocated_bitrates->SetBitrate(
top_active_layer, 0,
(initial_layer_rate + additional_allocation).bps());
}
}
void SimulcastRateAllocator::DistributeAllocationToTemporalLayers(
VideoBitrateAllocation* allocated_bitrates_bps) const {
const int num_spatial_streams =
std::max(1, static_cast<int>(codec_.numberOfSimulcastStreams));
// Finally, distribute the bitrate for the simulcast streams across the
// available temporal layers.
for (int simulcast_id = 0; simulcast_id < num_spatial_streams;
++simulcast_id) {
uint32_t target_bitrate_kbps =
allocated_bitrates_bps->GetBitrate(simulcast_id, 0) / 1000;
if (target_bitrate_kbps == 0) {
continue;
}
const uint32_t expected_allocated_bitrate_kbps = target_bitrate_kbps;
RTC_DCHECK_EQ(
target_bitrate_kbps,
allocated_bitrates_bps->GetSpatialLayerSum(simulcast_id) / 1000);
const int num_temporal_streams = NumTemporalStreams(simulcast_id);
uint32_t max_bitrate_kbps;
// Legacy temporal-layered only screenshare, or simulcast screenshare
// with legacy mode for simulcast stream 0.
if (codec_.mode == VideoCodecMode::kScreensharing &&
legacy_conference_mode_ && simulcast_id == 0) {
// TODO(holmer): This is a "temporary" hack for screensharing, where we
// interpret the startBitrate as the encoder target bitrate. This is
// to allow for a different max bitrate, so if the codec can't meet
// the target we still allow it to overshoot up to the max before dropping
// frames. This hack should be improved.
max_bitrate_kbps =
std::min(kLegacyScreenshareTl1BitrateKbps, target_bitrate_kbps);
target_bitrate_kbps =
std::min(kLegacyScreenshareTl0BitrateKbps, target_bitrate_kbps);
} else if (num_spatial_streams == 1) {
max_bitrate_kbps = codec_.maxBitrate;
} else {
max_bitrate_kbps = codec_.simulcastStream[simulcast_id].maxBitrate;
}
std::vector<uint32_t> tl_allocation;
if (num_temporal_streams == 1) {
tl_allocation.push_back(target_bitrate_kbps);
} else {
if (codec_.mode == VideoCodecMode::kScreensharing &&
legacy_conference_mode_ && simulcast_id == 0) {
tl_allocation = ScreenshareTemporalLayerAllocation(
target_bitrate_kbps, max_bitrate_kbps, simulcast_id);
} else {
tl_allocation = DefaultTemporalLayerAllocation(
target_bitrate_kbps, max_bitrate_kbps, simulcast_id);
}
}
RTC_DCHECK_GT(tl_allocation.size(), 0);
RTC_DCHECK_LE(tl_allocation.size(), num_temporal_streams);
uint64_t tl_allocation_sum_kbps = 0;
for (size_t tl_index = 0; tl_index < tl_allocation.size(); ++tl_index) {
uint32_t layer_rate_kbps = tl_allocation[tl_index];
if (layer_rate_kbps > 0) {
allocated_bitrates_bps->SetBitrate(simulcast_id, tl_index,
layer_rate_kbps * 1000);
}
tl_allocation_sum_kbps += layer_rate_kbps;
}
RTC_DCHECK_LE(tl_allocation_sum_kbps, expected_allocated_bitrate_kbps);
}
}
std::vector<uint32_t> SimulcastRateAllocator::DefaultTemporalLayerAllocation(
int bitrate_kbps,
int max_bitrate_kbps,
int simulcast_id) const {
const size_t num_temporal_layers = NumTemporalStreams(simulcast_id);
std::vector<uint32_t> bitrates;
for (size_t i = 0; i < num_temporal_layers; ++i) {
float layer_bitrate =
bitrate_kbps *
GetTemporalRateAllocation(
num_temporal_layers, i,
rate_control_settings_.Vp8BaseHeavyTl3RateAllocation());
bitrates.push_back(static_cast<uint32_t>(layer_bitrate + 0.5));
}
// Allocation table is of aggregates, transform to individual rates.
uint32_t sum = 0;
for (size_t i = 0; i < num_temporal_layers; ++i) {
uint32_t layer_bitrate = bitrates[i];
RTC_DCHECK_LE(sum, bitrates[i]);
bitrates[i] -= sum;
sum = layer_bitrate;
if (sum >= static_cast<uint32_t>(bitrate_kbps)) {
// Sum adds up; any subsequent layers will be 0.
bitrates.resize(i + 1);
break;
}
}
return bitrates;
}
std::vector<uint32_t>
SimulcastRateAllocator::ScreenshareTemporalLayerAllocation(
int bitrate_kbps,
int max_bitrate_kbps,
int simulcast_id) const {
if (simulcast_id > 0) {
return DefaultTemporalLayerAllocation(bitrate_kbps, max_bitrate_kbps,
simulcast_id);
}
std::vector<uint32_t> allocation;
allocation.push_back(bitrate_kbps);
if (max_bitrate_kbps > bitrate_kbps)
allocation.push_back(max_bitrate_kbps - bitrate_kbps);
return allocation;
}
const VideoCodec& webrtc::SimulcastRateAllocator::GetCodec() const {
return codec_;
}
int SimulcastRateAllocator::NumTemporalStreams(size_t simulcast_id) const {
return std::max<uint8_t>(
1,
codec_.codecType == kVideoCodecVP8 && codec_.numberOfSimulcastStreams == 0
? codec_.VP8().numberOfTemporalLayers
: codec_.simulcastStream[simulcast_id].numberOfTemporalLayers);
}
void SimulcastRateAllocator::SetLegacyConferenceMode(bool enabled) {
legacy_conference_mode_ = enabled;
}
} // namespace webrtc

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2016 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_SIMULCAST_RATE_ALLOCATOR_H_
#define MODULES_VIDEO_CODING_UTILITY_SIMULCAST_RATE_ALLOCATOR_H_
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "api/video/video_bitrate_allocation.h"
#include "api/video/video_bitrate_allocator.h"
#include "api/video_codecs/video_codec.h"
#include "rtc_base/experiments/rate_control_settings.h"
#include "rtc_base/experiments/stable_target_rate_experiment.h"
namespace webrtc {
class SimulcastRateAllocator : public VideoBitrateAllocator {
public:
explicit SimulcastRateAllocator(const VideoCodec& codec);
~SimulcastRateAllocator() override;
SimulcastRateAllocator(const SimulcastRateAllocator&) = delete;
SimulcastRateAllocator& operator=(const SimulcastRateAllocator&) = delete;
VideoBitrateAllocation Allocate(
VideoBitrateAllocationParameters parameters) override;
const VideoCodec& GetCodec() const;
static float GetTemporalRateAllocation(int num_layers,
int temporal_id,
bool base_heavy_tl3_alloc);
void SetLegacyConferenceMode(bool mode) override;
private:
void DistributeAllocationToSimulcastLayers(
DataRate total_bitrate,
DataRate stable_bitrate,
VideoBitrateAllocation* allocated_bitrates);
void DistributeAllocationToTemporalLayers(
VideoBitrateAllocation* allocated_bitrates) const;
std::vector<uint32_t> DefaultTemporalLayerAllocation(int bitrate_kbps,
int max_bitrate_kbps,
int simulcast_id) const;
std::vector<uint32_t> ScreenshareTemporalLayerAllocation(
int bitrate_kbps,
int max_bitrate_kbps,
int simulcast_id) const;
int NumTemporalStreams(size_t simulcast_id) const;
const VideoCodec codec_;
const StableTargetRateExperiment stable_rate_settings_;
const RateControlSettings rate_control_settings_;
std::vector<bool> stream_enabled_;
bool legacy_conference_mode_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_SIMULCAST_RATE_ALLOCATOR_H_

View file

@ -0,0 +1,970 @@
/*
* Copyright (c) 2014 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 "modules/video_coding/utility/simulcast_test_fixture_impl.h"
#include <algorithm>
#include <map>
#include <memory>
#include <vector>
#include "api/environment/environment.h"
#include "api/environment/environment_factory.h"
#include "api/video/encoded_image.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_encoder.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/include/video_coding_defines.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::Return;
namespace webrtc {
namespace test {
namespace {
const int kDefaultWidth = 1280;
const int kDefaultHeight = 720;
const int kNumberOfSimulcastStreams = 3;
const int kColorY = 66;
const int kColorU = 22;
const int kColorV = 33;
const int kMaxBitrates[kNumberOfSimulcastStreams] = {150, 600, 1200};
const int kMinBitrates[kNumberOfSimulcastStreams] = {50, 150, 600};
const int kTargetBitrates[kNumberOfSimulcastStreams] = {100, 450, 1000};
const float kMaxFramerates[kNumberOfSimulcastStreams] = {30, 30, 30};
const int kScaleResolutionDownBy[kNumberOfSimulcastStreams] = {4, 2, 1};
const int kDefaultTemporalLayerProfile[3] = {3, 3, 3};
const int kNoTemporalLayerProfile[3] = {0, 0, 0};
const VideoEncoder::Capabilities kCapabilities(false);
const VideoEncoder::Settings kSettings(kCapabilities, 1, 1200);
template <typename T>
void SetExpectedValues3(T value0, T value1, T value2, T* expected_values) {
expected_values[0] = value0;
expected_values[1] = value1;
expected_values[2] = value2;
}
enum PlaneType {
kYPlane = 0,
kUPlane = 1,
kVPlane = 2,
kNumOfPlanes = 3,
};
} // namespace
class SimulcastTestFixtureImpl::TestEncodedImageCallback
: public EncodedImageCallback {
public:
TestEncodedImageCallback() {
memset(temporal_layer_, -1, sizeof(temporal_layer_));
memset(layer_sync_, false, sizeof(layer_sync_));
}
Result OnEncodedImage(const EncodedImage& encoded_image,
const CodecSpecificInfo* codec_specific_info) override {
bool is_vp8 = (codec_specific_info->codecType == kVideoCodecVP8);
bool is_h264 = (codec_specific_info->codecType == kVideoCodecH264);
// Only store the base layer.
if (encoded_image.SimulcastIndex().value_or(0) == 0) {
if (encoded_image._frameType == VideoFrameType::kVideoFrameKey) {
encoded_key_frame_.SetEncodedData(EncodedImageBuffer::Create(
encoded_image.data(), encoded_image.size()));
encoded_key_frame_._frameType = VideoFrameType::kVideoFrameKey;
} else {
encoded_frame_.SetEncodedData(EncodedImageBuffer::Create(
encoded_image.data(), encoded_image.size()));
}
}
if (is_vp8) {
layer_sync_[encoded_image.SimulcastIndex().value_or(0)] =
codec_specific_info->codecSpecific.VP8.layerSync;
temporal_layer_[encoded_image.SimulcastIndex().value_or(0)] =
codec_specific_info->codecSpecific.VP8.temporalIdx;
} else if (is_h264) {
layer_sync_[encoded_image.SimulcastIndex().value_or(0)] =
codec_specific_info->codecSpecific.H264.base_layer_sync;
temporal_layer_[encoded_image.SimulcastIndex().value_or(0)] =
codec_specific_info->codecSpecific.H264.temporal_idx;
}
return Result(Result::OK, encoded_image.RtpTimestamp());
}
// This method only makes sense for VP8.
void GetLastEncodedFrameInfo(int* temporal_layer,
bool* layer_sync,
int stream) {
*temporal_layer = temporal_layer_[stream];
*layer_sync = layer_sync_[stream];
}
void GetLastEncodedKeyFrame(EncodedImage* encoded_key_frame) {
*encoded_key_frame = encoded_key_frame_;
}
void GetLastEncodedFrame(EncodedImage* encoded_frame) {
*encoded_frame = encoded_frame_;
}
private:
EncodedImage encoded_key_frame_;
EncodedImage encoded_frame_;
int temporal_layer_[kNumberOfSimulcastStreams];
bool layer_sync_[kNumberOfSimulcastStreams];
};
class SimulcastTestFixtureImpl::TestDecodedImageCallback
: public DecodedImageCallback {
public:
TestDecodedImageCallback() : decoded_frames_(0) {}
int32_t Decoded(VideoFrame& decoded_image) override {
rtc::scoped_refptr<I420BufferInterface> i420_buffer =
decoded_image.video_frame_buffer()->ToI420();
for (int i = 0; i < decoded_image.width(); ++i) {
EXPECT_NEAR(kColorY, i420_buffer->DataY()[i], 1);
}
// TODO(mikhal): Verify the difference between U,V and the original.
for (int i = 0; i < i420_buffer->ChromaWidth(); ++i) {
EXPECT_NEAR(kColorU, i420_buffer->DataU()[i], 4);
EXPECT_NEAR(kColorV, i420_buffer->DataV()[i], 4);
}
decoded_frames_++;
return 0;
}
int32_t Decoded(VideoFrame& decoded_image, int64_t decode_time_ms) override {
RTC_DCHECK_NOTREACHED();
return -1;
}
void Decoded(VideoFrame& decoded_image,
absl::optional<int32_t> decode_time_ms,
absl::optional<uint8_t> qp) override {
Decoded(decoded_image);
}
int DecodedFrames() { return decoded_frames_; }
private:
int decoded_frames_;
};
namespace {
void SetPlane(uint8_t* data, uint8_t value, int width, int height, int stride) {
for (int i = 0; i < height; i++, data += stride) {
// Setting allocated area to zero - setting only image size to
// requested values - will make it easier to distinguish between image
// size and frame size (accounting for stride).
memset(data, value, width);
memset(data + width, 0, stride - width);
}
}
// Fills in an I420Buffer from `plane_colors`.
void CreateImage(const rtc::scoped_refptr<I420Buffer>& buffer,
int plane_colors[kNumOfPlanes]) {
SetPlane(buffer->MutableDataY(), plane_colors[0], buffer->width(),
buffer->height(), buffer->StrideY());
SetPlane(buffer->MutableDataU(), plane_colors[1], buffer->ChromaWidth(),
buffer->ChromaHeight(), buffer->StrideU());
SetPlane(buffer->MutableDataV(), plane_colors[2], buffer->ChromaWidth(),
buffer->ChromaHeight(), buffer->StrideV());
}
void ConfigureStream(int width,
int height,
int max_bitrate,
int min_bitrate,
int target_bitrate,
float max_framerate,
SimulcastStream* stream,
int num_temporal_layers) {
RTC_DCHECK(stream);
stream->width = width;
stream->height = height;
stream->maxBitrate = max_bitrate;
stream->minBitrate = min_bitrate;
stream->targetBitrate = target_bitrate;
stream->maxFramerate = max_framerate;
if (num_temporal_layers >= 0) {
stream->numberOfTemporalLayers = num_temporal_layers;
}
stream->qpMax = 45;
stream->active = true;
}
} // namespace
void SimulcastTestFixtureImpl::DefaultSettings(
VideoCodec* settings,
const int* temporal_layer_profile,
VideoCodecType codec_type,
bool reverse_layer_order) {
RTC_CHECK(settings);
*settings = {};
settings->codecType = codec_type;
settings->startBitrate = 300;
settings->minBitrate = 30;
settings->maxBitrate = 0;
settings->maxFramerate = 30;
settings->width = kDefaultWidth;
settings->height = kDefaultHeight;
settings->numberOfSimulcastStreams = kNumberOfSimulcastStreams;
settings->active = true;
ASSERT_EQ(3, kNumberOfSimulcastStreams);
int layer_order[3] = {0, 1, 2};
if (reverse_layer_order) {
layer_order[0] = 2;
layer_order[2] = 0;
}
settings->timing_frame_thresholds = {kDefaultTimingFramesDelayMs,
kDefaultOutlierFrameSizePercent};
ConfigureStream(kDefaultWidth / 4, kDefaultHeight / 4, kMaxBitrates[0],
kMinBitrates[0], kTargetBitrates[0], kMaxFramerates[0],
&settings->simulcastStream[layer_order[0]],
temporal_layer_profile[0]);
ConfigureStream(kDefaultWidth / 2, kDefaultHeight / 2, kMaxBitrates[1],
kMinBitrates[1], kTargetBitrates[1], kMaxFramerates[1],
&settings->simulcastStream[layer_order[1]],
temporal_layer_profile[1]);
ConfigureStream(kDefaultWidth, kDefaultHeight, kMaxBitrates[2],
kMinBitrates[2], kTargetBitrates[2], kMaxFramerates[2],
&settings->simulcastStream[layer_order[2]],
temporal_layer_profile[2]);
settings->SetFrameDropEnabled(true);
if (codec_type == kVideoCodecVP8) {
settings->VP8()->denoisingOn = true;
settings->VP8()->automaticResizeOn = false;
settings->VP8()->keyFrameInterval = 3000;
} else {
settings->H264()->keyFrameInterval = 3000;
}
}
SimulcastTestFixtureImpl::SimulcastTestFixtureImpl(
std::unique_ptr<VideoEncoderFactory> encoder_factory,
std::unique_ptr<VideoDecoderFactory> decoder_factory,
SdpVideoFormat video_format)
: codec_type_(PayloadStringToCodecType(video_format.name)) {
Environment env = CreateEnvironment();
encoder_ = encoder_factory->CreateVideoEncoder(video_format);
decoder_ = decoder_factory->Create(env, video_format);
SetUpCodec((codec_type_ == kVideoCodecVP8 || codec_type_ == kVideoCodecH264)
? kDefaultTemporalLayerProfile
: kNoTemporalLayerProfile);
}
SimulcastTestFixtureImpl::~SimulcastTestFixtureImpl() {
encoder_->Release();
decoder_->Release();
}
void SimulcastTestFixtureImpl::SetUpCodec(const int* temporal_layer_profile) {
encoder_->RegisterEncodeCompleteCallback(&encoder_callback_);
decoder_->RegisterDecodeCompleteCallback(&decoder_callback_);
DefaultSettings(&settings_, temporal_layer_profile, codec_type_);
SetUpRateAllocator();
EXPECT_EQ(0, encoder_->InitEncode(&settings_, kSettings));
VideoDecoder::Settings decoder_settings;
decoder_settings.set_max_render_resolution({kDefaultWidth, kDefaultHeight});
decoder_settings.set_codec_type(codec_type_);
EXPECT_TRUE(decoder_->Configure(decoder_settings));
input_buffer_ = I420Buffer::Create(kDefaultWidth, kDefaultHeight);
input_buffer_->InitializeData();
input_frame_ = std::make_unique<webrtc::VideoFrame>(
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(input_buffer_)
.set_rotation(webrtc::kVideoRotation_0)
.set_timestamp_us(0)
.build());
}
void SimulcastTestFixtureImpl::SetUpRateAllocator() {
rate_allocator_.reset(new SimulcastRateAllocator(settings_));
}
void SimulcastTestFixtureImpl::SetRates(uint32_t bitrate_kbps, uint32_t fps) {
encoder_->SetRates(VideoEncoder::RateControlParameters(
rate_allocator_->Allocate(
VideoBitrateAllocationParameters(bitrate_kbps * 1000, fps)),
static_cast<double>(fps)));
}
void SimulcastTestFixtureImpl::RunActiveStreamsTest(
const std::vector<bool> active_streams) {
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
UpdateActiveStreams(active_streams);
// Set sufficient bitrate for all streams so we can test active without
// bitrate being an issue.
SetRates(kMaxBitrates[0] + kMaxBitrates[1] + kMaxBitrates[2], 30);
ExpectStreams(VideoFrameType::kVideoFrameKey, active_streams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, active_streams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::UpdateActiveStreams(
const std::vector<bool> active_streams) {
ASSERT_EQ(static_cast<int>(active_streams.size()), kNumberOfSimulcastStreams);
for (size_t i = 0; i < active_streams.size(); ++i) {
settings_.simulcastStream[i].active = active_streams[i];
}
// Re initialize the allocator and encoder with the new settings.
// TODO(bugs.webrtc.org/8807): Currently, we do a full "hard"
// reconfiguration of the allocator and encoder. When the video bitrate
// allocator has support for updating active streams without a
// reinitialization, we can just call that here instead.
SetUpRateAllocator();
EXPECT_EQ(0, encoder_->InitEncode(&settings_, kSettings));
}
void SimulcastTestFixtureImpl::ExpectStream(VideoFrameType frame_type,
int scaleResolutionDownBy) {
EXPECT_CALL(
encoder_callback_,
OnEncodedImage(AllOf(Field(&EncodedImage::_frameType, frame_type),
Field(&EncodedImage::_encodedWidth,
kDefaultWidth / scaleResolutionDownBy),
Field(&EncodedImage::_encodedHeight,
kDefaultHeight / scaleResolutionDownBy)),
_))
.Times(1)
.WillRepeatedly(Return(
EncodedImageCallback::Result(EncodedImageCallback::Result::OK, 0)));
}
void SimulcastTestFixtureImpl::ExpectStreams(
VideoFrameType frame_type,
const std::vector<bool> expected_streams_active) {
ASSERT_EQ(static_cast<int>(expected_streams_active.size()),
kNumberOfSimulcastStreams);
for (size_t i = 0; i < kNumberOfSimulcastStreams; i++) {
if (expected_streams_active[i]) {
ExpectStream(frame_type, kScaleResolutionDownBy[i]);
}
}
}
void SimulcastTestFixtureImpl::ExpectStreams(VideoFrameType frame_type,
int expected_video_streams) {
ASSERT_GE(expected_video_streams, 0);
ASSERT_LE(expected_video_streams, kNumberOfSimulcastStreams);
std::vector<bool> expected_streams_active(kNumberOfSimulcastStreams, false);
for (int i = 0; i < expected_video_streams; ++i) {
expected_streams_active[i] = true;
}
ExpectStreams(frame_type, expected_streams_active);
}
void SimulcastTestFixtureImpl::VerifyTemporalIdxAndSyncForAllSpatialLayers(
TestEncodedImageCallback* encoder_callback,
const int* expected_temporal_idx,
const bool* expected_layer_sync,
int num_spatial_layers) {
int temporal_layer = -1;
bool layer_sync = false;
for (int i = 0; i < num_spatial_layers; i++) {
encoder_callback->GetLastEncodedFrameInfo(&temporal_layer, &layer_sync, i);
EXPECT_EQ(expected_temporal_idx[i], temporal_layer);
EXPECT_EQ(expected_layer_sync[i], layer_sync);
}
}
// For some codecs (VP8) expect all active streams to generate a key frame even
// though a key frame was only requested for some of them.
void SimulcastTestFixtureImpl::TestKeyFrameRequestsOnAllStreams() {
SetRates(kMaxBitrates[2], 30); // To get all three streams.
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, kNumberOfSimulcastStreams);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
frame_types[0] = VideoFrameType::kVideoFrameKey;
ExpectStreams(VideoFrameType::kVideoFrameKey, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
frame_types[1] = VideoFrameType::kVideoFrameKey;
ExpectStreams(VideoFrameType::kVideoFrameKey, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
frame_types[2] = VideoFrameType::kVideoFrameKey;
ExpectStreams(VideoFrameType::kVideoFrameKey, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameDelta, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
// For some codecs (H264) expect only particular active streams to generate a
// key frame when a key frame was only requested for some of them.
void SimulcastTestFixtureImpl::TestKeyFrameRequestsOnSpecificStreams() {
SetRates(kMaxBitrates[2], 30); // To get all three streams.
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, kNumberOfSimulcastStreams);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
frame_types[0] = VideoFrameType::kVideoFrameKey;
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[0]);
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[1]);
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[2]);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
frame_types[1] = VideoFrameType::kVideoFrameKey;
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[0]);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[1]);
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[2]);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
frame_types[2] = VideoFrameType::kVideoFrameKey;
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[0]);
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[1]);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[2]);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
frame_types[0] = VideoFrameType::kVideoFrameKey;
frame_types[2] = VideoFrameType::kVideoFrameKey;
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[0]);
ExpectStream(VideoFrameType::kVideoFrameDelta, kScaleResolutionDownBy[1]);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[2]);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameKey);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[0]);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[1]);
ExpectStream(VideoFrameType::kVideoFrameKey, kScaleResolutionDownBy[2]);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
std::fill(frame_types.begin(), frame_types.end(),
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameDelta, kNumberOfSimulcastStreams);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestPaddingAllStreams() {
// We should always encode the base layer.
SetRates(kMinBitrates[0] - 1, 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 1);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 1);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestPaddingTwoStreams() {
// We have just enough to get only the first stream and padding for two.
SetRates(kMinBitrates[0], 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 1);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 1);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestPaddingTwoStreamsOneMaxedOut() {
// We are just below limit of sending second stream, so we should get
// the first stream maxed out (at `maxBitrate`), and padding for two.
SetRates(kTargetBitrates[0] + kMinBitrates[1] - 1, 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 1);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 1);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestPaddingOneStream() {
// We have just enough to send two streams, so padding for one stream.
SetRates(kTargetBitrates[0] + kMinBitrates[1], 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 2);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 2);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestPaddingOneStreamTwoMaxedOut() {
// We are just below limit of sending third stream, so we should get
// first stream's rate maxed out at `targetBitrate`, second at `maxBitrate`.
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] - 1, 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 2);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 2);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestSendAllStreams() {
// We have just enough to send all streams.
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2], 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 3);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 3);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestDisablingStreams() {
// We should get three media streams.
SetRates(kMaxBitrates[0] + kMaxBitrates[1] + kMaxBitrates[2], 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
ExpectStreams(VideoFrameType::kVideoFrameKey, 3);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
ExpectStreams(VideoFrameType::kVideoFrameDelta, 3);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// We should only get two streams and padding for one.
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
ExpectStreams(VideoFrameType::kVideoFrameDelta, 2);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// We should only get the first stream and padding for two.
SetRates(kTargetBitrates[0] + kMinBitrates[1] / 2, 30);
ExpectStreams(VideoFrameType::kVideoFrameDelta, 1);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// We don't have enough bitrate for the thumbnail stream, but we should get
// it anyway with current configuration.
SetRates(kTargetBitrates[0] - 1, 30);
ExpectStreams(VideoFrameType::kVideoFrameDelta, 1);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// We should only get two streams and padding for one.
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
// We get a key frame because a new stream is being enabled.
ExpectStreams(VideoFrameType::kVideoFrameKey, 2);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// We should get all three streams.
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kTargetBitrates[2], 30);
// We get a key frame because a new stream is being enabled.
ExpectStreams(VideoFrameType::kVideoFrameKey, 3);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestActiveStreams() {
// All streams on.
RunActiveStreamsTest({true, true, true});
// All streams off.
RunActiveStreamsTest({false, false, false});
// Low stream off.
RunActiveStreamsTest({false, true, true});
// Middle stream off.
RunActiveStreamsTest({true, false, true});
// High stream off.
RunActiveStreamsTest({true, true, false});
// Only low stream turned on.
RunActiveStreamsTest({true, false, false});
// Only middle stream turned on.
RunActiveStreamsTest({false, true, false});
// Only high stream turned on.
RunActiveStreamsTest({false, false, true});
}
void SimulcastTestFixtureImpl::SwitchingToOneStream(int width, int height) {
const int* temporal_layer_profile = nullptr;
// Disable all streams except the last and set the bitrate of the last to
// 100 kbps. This verifies the way GTP switches to screenshare mode.
if (codec_type_ == kVideoCodecVP8) {
settings_.VP8()->numberOfTemporalLayers = 1;
temporal_layer_profile = kDefaultTemporalLayerProfile;
} else {
settings_.H264()->numberOfTemporalLayers = 1;
temporal_layer_profile = kNoTemporalLayerProfile;
}
settings_.maxBitrate = 100;
settings_.startBitrate = 100;
settings_.width = width;
settings_.height = height;
for (int i = 0; i < settings_.numberOfSimulcastStreams - 1; ++i) {
settings_.simulcastStream[i].maxBitrate = 0;
settings_.simulcastStream[i].width = settings_.width;
settings_.simulcastStream[i].height = settings_.height;
settings_.simulcastStream[i].numberOfTemporalLayers = 1;
}
// Setting input image to new resolution.
input_buffer_ = I420Buffer::Create(settings_.width, settings_.height);
input_buffer_->InitializeData();
input_frame_ = std::make_unique<webrtc::VideoFrame>(
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(input_buffer_)
.set_rotation(webrtc::kVideoRotation_0)
.set_timestamp_us(0)
.build());
// The for loop above did not set the bitrate of the highest layer.
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].maxBitrate =
0;
// The highest layer has to correspond to the non-simulcast resolution.
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].width =
settings_.width;
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].height =
settings_.height;
SetUpRateAllocator();
EXPECT_EQ(0, encoder_->InitEncode(&settings_, kSettings));
// Encode one frame and verify.
SetRates(kMaxBitrates[0] + kMaxBitrates[1], 30);
std::vector<VideoFrameType> frame_types(kNumberOfSimulcastStreams,
VideoFrameType::kVideoFrameDelta);
EXPECT_CALL(
encoder_callback_,
OnEncodedImage(AllOf(Field(&EncodedImage::_frameType,
VideoFrameType::kVideoFrameKey),
Field(&EncodedImage::_encodedWidth, width),
Field(&EncodedImage::_encodedHeight, height)),
_))
.Times(1)
.WillRepeatedly(Return(
EncodedImageCallback::Result(EncodedImageCallback::Result::OK, 0)));
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
// Switch back.
DefaultSettings(&settings_, temporal_layer_profile, codec_type_);
// Start at the lowest bitrate for enabling base stream.
settings_.startBitrate = kMinBitrates[0];
SetUpRateAllocator();
EXPECT_EQ(0, encoder_->InitEncode(&settings_, kSettings));
SetRates(settings_.startBitrate, 30);
ExpectStreams(VideoFrameType::kVideoFrameKey, 1);
// Resize `input_frame_` to the new resolution.
input_buffer_ = I420Buffer::Create(settings_.width, settings_.height);
input_buffer_->InitializeData();
input_frame_ = std::make_unique<webrtc::VideoFrame>(
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(input_buffer_)
.set_rotation(webrtc::kVideoRotation_0)
.set_timestamp_us(0)
.build());
EXPECT_EQ(0, encoder_->Encode(*input_frame_, &frame_types));
}
void SimulcastTestFixtureImpl::TestSwitchingToOneStream() {
SwitchingToOneStream(1024, 768);
}
void SimulcastTestFixtureImpl::TestSwitchingToOneOddStream() {
SwitchingToOneStream(1023, 769);
}
void SimulcastTestFixtureImpl::TestSwitchingToOneSmallStream() {
SwitchingToOneStream(4, 4);
}
// Test the layer pattern and sync flag for various spatial-temporal patterns.
// 3-3-3 pattern: 3 temporal layers for all spatial streams, so same
// temporal_layer id and layer_sync is expected for all streams.
void SimulcastTestFixtureImpl::TestSpatioTemporalLayers333PatternEncoder() {
bool is_h264 = codec_type_ == kVideoCodecH264;
TestEncodedImageCallback encoder_callback;
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
SetRates(kMaxBitrates[2], 30); // To get all three streams.
int expected_temporal_idx[3] = {-1, -1, -1};
bool expected_layer_sync[3] = {false, false, false};
// First frame: #0.
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(0, 0, 0, expected_temporal_idx);
SetExpectedValues3<bool>(!is_h264, !is_h264, !is_h264, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #1.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
SetExpectedValues3<bool>(true, true, true, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #2.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(1, 1, 1, expected_temporal_idx);
SetExpectedValues3<bool>(true, true, true, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #3.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #4.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(0, 0, 0, expected_temporal_idx);
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #5.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 2, 2, expected_temporal_idx);
SetExpectedValues3<bool>(is_h264, is_h264, is_h264, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
}
// Test the layer pattern and sync flag for various spatial-temporal patterns.
// 3-2-1 pattern: 3 temporal layers for lowest resolution, 2 for middle, and
// 1 temporal layer for highest resolution.
// For this profile, we expect the temporal index pattern to be:
// 1st stream: 0, 2, 1, 2, ....
// 2nd stream: 0, 1, 0, 1, ...
// 3rd stream: -1, -1, -1, -1, ....
// Regarding the 3rd stream, note that a stream/encoder with 1 temporal layer
// should always have temporal layer idx set to kNoTemporalIdx = -1.
// Since CodecSpecificInfoVP8.temporalIdx is uint8_t, this will wrap to 255.
// TODO(marpan): Although this seems safe for now, we should fix this.
void SimulcastTestFixtureImpl::TestSpatioTemporalLayers321PatternEncoder() {
EXPECT_EQ(codec_type_, kVideoCodecVP8);
int temporal_layer_profile[3] = {3, 2, 1};
SetUpCodec(temporal_layer_profile);
TestEncodedImageCallback encoder_callback;
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
SetRates(kMaxBitrates[2], 30); // To get all three streams.
int expected_temporal_idx[3] = {-1, -1, -1};
bool expected_layer_sync[3] = {false, false, false};
// First frame: #0.
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(0, 0, 255, expected_temporal_idx);
SetExpectedValues3<bool>(true, true, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #1.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
SetExpectedValues3<bool>(true, true, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #2.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(1, 0, 255, expected_temporal_idx);
SetExpectedValues3<bool>(true, false, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #3.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #4.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(0, 0, 255, expected_temporal_idx);
SetExpectedValues3<bool>(false, false, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
// Next frame: #5.
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
SetExpectedValues3<int>(2, 1, 255, expected_temporal_idx);
SetExpectedValues3<bool>(false, true, false, expected_layer_sync);
VerifyTemporalIdxAndSyncForAllSpatialLayers(
&encoder_callback, expected_temporal_idx, expected_layer_sync, 3);
}
void SimulcastTestFixtureImpl::TestStrideEncodeDecode() {
TestEncodedImageCallback encoder_callback;
TestDecodedImageCallback decoder_callback;
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
decoder_->RegisterDecodeCompleteCallback(&decoder_callback);
SetRates(kMaxBitrates[2], 30); // To get all three streams.
// Setting two (possibly) problematic use cases for stride:
// 1. stride > width 2. stride_y != stride_uv/2
int stride_y = kDefaultWidth + 20;
int stride_uv = ((kDefaultWidth + 1) / 2) + 5;
input_buffer_ = I420Buffer::Create(kDefaultWidth, kDefaultHeight, stride_y,
stride_uv, stride_uv);
input_frame_ = std::make_unique<webrtc::VideoFrame>(
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(input_buffer_)
.set_rotation(webrtc::kVideoRotation_0)
.set_timestamp_us(0)
.build());
// Set color.
int plane_offset[kNumOfPlanes];
plane_offset[kYPlane] = kColorY;
plane_offset[kUPlane] = kColorU;
plane_offset[kVPlane] = kColorV;
CreateImage(input_buffer_, plane_offset);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
// Change color.
plane_offset[kYPlane] += 1;
plane_offset[kUPlane] += 1;
plane_offset[kVPlane] += 1;
CreateImage(input_buffer_, plane_offset);
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
EncodedImage encoded_frame;
// Only encoding one frame - so will be a key frame.
encoder_callback.GetLastEncodedKeyFrame(&encoded_frame);
EXPECT_EQ(0, decoder_->Decode(encoded_frame, 0));
encoder_callback.GetLastEncodedFrame(&encoded_frame);
decoder_->Decode(encoded_frame, 0);
EXPECT_EQ(2, decoder_callback.DecodedFrames());
}
void SimulcastTestFixtureImpl::TestDecodeWidthHeightSet() {
MockEncodedImageCallback encoder_callback;
MockDecodedImageCallback decoder_callback;
EncodedImage encoded_frame[3];
SetRates(kMaxBitrates[2], 30); // To get all three streams.
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
decoder_->RegisterDecodeCompleteCallback(&decoder_callback);
EXPECT_CALL(encoder_callback, OnEncodedImage(_, _))
.Times(3)
.WillRepeatedly(
::testing::Invoke([&](const EncodedImage& encoded_image,
const CodecSpecificInfo* codec_specific_info) {
EXPECT_EQ(encoded_image._frameType, VideoFrameType::kVideoFrameKey);
size_t index = encoded_image.SimulcastIndex().value_or(0);
encoded_frame[index].SetEncodedData(EncodedImageBuffer::Create(
encoded_image.data(), encoded_image.size()));
encoded_frame[index]._frameType = encoded_image._frameType;
return EncodedImageCallback::Result(
EncodedImageCallback::Result::OK, 0);
}));
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL));
EXPECT_CALL(decoder_callback, Decoded(_, _, _))
.WillOnce(::testing::Invoke([](VideoFrame& decodedImage,
absl::optional<int32_t> decode_time_ms,
absl::optional<uint8_t> qp) {
EXPECT_EQ(decodedImage.width(), kDefaultWidth / 4);
EXPECT_EQ(decodedImage.height(), kDefaultHeight / 4);
}));
EXPECT_EQ(0, decoder_->Decode(encoded_frame[0], 0));
EXPECT_CALL(decoder_callback, Decoded(_, _, _))
.WillOnce(::testing::Invoke([](VideoFrame& decodedImage,
absl::optional<int32_t> decode_time_ms,
absl::optional<uint8_t> qp) {
EXPECT_EQ(decodedImage.width(), kDefaultWidth / 2);
EXPECT_EQ(decodedImage.height(), kDefaultHeight / 2);
}));
EXPECT_EQ(0, decoder_->Decode(encoded_frame[1], 0));
EXPECT_CALL(decoder_callback, Decoded(_, _, _))
.WillOnce(::testing::Invoke([](VideoFrame& decodedImage,
absl::optional<int32_t> decode_time_ms,
absl::optional<uint8_t> qp) {
EXPECT_EQ(decodedImage.width(), kDefaultWidth);
EXPECT_EQ(decodedImage.height(), kDefaultHeight);
}));
EXPECT_EQ(0, decoder_->Decode(encoded_frame[2], 0));
}
void SimulcastTestFixtureImpl::
TestEncoderInfoForDefaultTemporalLayerProfileHasFpsAllocation() {
VideoEncoder::EncoderInfo encoder_info = encoder_->GetEncoderInfo();
EXPECT_EQ(encoder_info.fps_allocation[0].size(),
static_cast<size_t>(kDefaultTemporalLayerProfile[0]));
EXPECT_EQ(encoder_info.fps_allocation[1].size(),
static_cast<size_t>(kDefaultTemporalLayerProfile[1]));
EXPECT_EQ(encoder_info.fps_allocation[2].size(),
static_cast<size_t>(kDefaultTemporalLayerProfile[2]));
}
} // namespace test
} // namespace webrtc

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_SIMULCAST_TEST_FIXTURE_IMPL_H_
#define MODULES_VIDEO_CODING_UTILITY_SIMULCAST_TEST_FIXTURE_IMPL_H_
#include <memory>
#include <vector>
#include "api/test/mock_video_decoder.h"
#include "api/test/mock_video_encoder.h"
#include "api/test/simulcast_test_fixture.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "api/video_codecs/video_decoder_factory.h"
#include "api/video_codecs/video_encoder_factory.h"
#include "modules/video_coding/utility/simulcast_rate_allocator.h"
namespace webrtc {
namespace test {
class SimulcastTestFixtureImpl final : public SimulcastTestFixture {
public:
SimulcastTestFixtureImpl(std::unique_ptr<VideoEncoderFactory> encoder_factory,
std::unique_ptr<VideoDecoderFactory> decoder_factory,
SdpVideoFormat video_format);
~SimulcastTestFixtureImpl() final;
// Implements SimulcastTestFixture.
void TestKeyFrameRequestsOnAllStreams() override;
void TestKeyFrameRequestsOnSpecificStreams() override;
void TestPaddingAllStreams() override;
void TestPaddingTwoStreams() override;
void TestPaddingTwoStreamsOneMaxedOut() override;
void TestPaddingOneStream() override;
void TestPaddingOneStreamTwoMaxedOut() override;
void TestSendAllStreams() override;
void TestDisablingStreams() override;
void TestActiveStreams() override;
void TestSwitchingToOneStream() override;
void TestSwitchingToOneOddStream() override;
void TestSwitchingToOneSmallStream() override;
void TestSpatioTemporalLayers333PatternEncoder() override;
void TestSpatioTemporalLayers321PatternEncoder() override;
void TestStrideEncodeDecode() override;
void TestDecodeWidthHeightSet() override;
void TestEncoderInfoForDefaultTemporalLayerProfileHasFpsAllocation() override;
static void DefaultSettings(VideoCodec* settings,
const int* temporal_layer_profile,
VideoCodecType codec_type,
bool reverse_layer_order = false);
private:
class TestEncodedImageCallback;
class TestDecodedImageCallback;
void SetUpCodec(const int* temporal_layer_profile);
void SetUpRateAllocator();
void SetRates(uint32_t bitrate_kbps, uint32_t fps);
void RunActiveStreamsTest(std::vector<bool> active_streams);
void UpdateActiveStreams(std::vector<bool> active_streams);
void ExpectStream(VideoFrameType frame_type, int scaleResolutionDownBy);
void ExpectStreams(VideoFrameType frame_type,
std::vector<bool> expected_streams_active);
void ExpectStreams(VideoFrameType frame_type, int expected_video_streams);
void VerifyTemporalIdxAndSyncForAllSpatialLayers(
TestEncodedImageCallback* encoder_callback,
const int* expected_temporal_idx,
const bool* expected_layer_sync,
int num_spatial_layers);
void SwitchingToOneStream(int width, int height);
std::unique_ptr<VideoEncoder> encoder_;
MockEncodedImageCallback encoder_callback_;
std::unique_ptr<VideoDecoder> decoder_;
MockDecodedImageCallback decoder_callback_;
VideoCodec settings_;
rtc::scoped_refptr<I420Buffer> input_buffer_;
std::unique_ptr<VideoFrame> input_frame_;
std::unique_ptr<SimulcastRateAllocator> rate_allocator_;
VideoCodecType codec_type_;
};
} // namespace test
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_SIMULCAST_TEST_FIXTURE_IMPL_H_

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 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 "modules/video_coding/utility/simulcast_utility.h"
#include <algorithm>
#include <cmath>
#include "modules/video_coding/svc/scalability_mode_util.h"
#include "rtc_base/checks.h"
namespace webrtc {
uint32_t SimulcastUtility::SumStreamMaxBitrate(int streams,
const VideoCodec& codec) {
uint32_t bitrate_sum = 0;
for (int i = 0; i < streams; ++i) {
bitrate_sum += codec.simulcastStream[i].maxBitrate;
}
return bitrate_sum;
}
int SimulcastUtility::NumberOfSimulcastStreams(const VideoCodec& codec) {
int streams =
codec.numberOfSimulcastStreams < 1 ? 1 : codec.numberOfSimulcastStreams;
uint32_t simulcast_max_bitrate = SumStreamMaxBitrate(streams, codec);
if (simulcast_max_bitrate == 0) {
streams = 1;
}
return streams;
}
bool SimulcastUtility::ValidSimulcastParameters(const VideoCodec& codec,
int num_streams) {
// Check resolution.
if (codec.width != codec.simulcastStream[num_streams - 1].width ||
codec.height != codec.simulcastStream[num_streams - 1].height) {
return false;
}
for (int i = 0; i < num_streams; ++i) {
if (codec.width * codec.simulcastStream[i].height !=
codec.height * codec.simulcastStream[i].width) {
return false;
}
}
for (int i = 1; i < num_streams; ++i) {
if (codec.simulcastStream[i].width < codec.simulcastStream[i - 1].width) {
return false;
}
}
// Check frame-rate.
for (int i = 1; i < num_streams; ++i) {
if (fabs(codec.simulcastStream[i].maxFramerate -
codec.simulcastStream[i - 1].maxFramerate) > 1e-9) {
return false;
}
}
// Check temporal layers.
for (int i = 0; i < num_streams - 1; ++i) {
if (codec.simulcastStream[i].numberOfTemporalLayers !=
codec.simulcastStream[i + 1].numberOfTemporalLayers)
return false;
}
return true;
}
bool SimulcastUtility::IsConferenceModeScreenshare(const VideoCodec& codec) {
return codec.mode == VideoCodecMode::kScreensharing &&
codec.legacy_conference_mode;
}
int SimulcastUtility::NumberOfTemporalLayers(const VideoCodec& codec,
int spatial_id) {
int num_temporal_layers = 0;
if (auto scalability_mode = codec.GetScalabilityMode(); scalability_mode) {
num_temporal_layers = ScalabilityModeToNumTemporalLayers(*scalability_mode);
} else {
switch (codec.codecType) {
case kVideoCodecVP8:
num_temporal_layers = codec.VP8().numberOfTemporalLayers;
break;
case kVideoCodecVP9:
num_temporal_layers = codec.VP9().numberOfTemporalLayers;
break;
case kVideoCodecH264:
num_temporal_layers = codec.H264().numberOfTemporalLayers;
break;
case kVideoCodecH265:
// TODO(bugs.webrtc.org/13485)
break;
default:
break;
}
}
if (codec.numberOfSimulcastStreams > 0) {
RTC_DCHECK_LT(spatial_id, codec.numberOfSimulcastStreams);
num_temporal_layers =
std::max(num_temporal_layers,
static_cast<int>(
codec.simulcastStream[spatial_id].numberOfTemporalLayers));
}
return std::max(1, num_temporal_layers);
}
} // namespace webrtc

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_SIMULCAST_UTILITY_H_
#define MODULES_VIDEO_CODING_UTILITY_SIMULCAST_UTILITY_H_
#include <stdint.h>
#include "api/video_codecs/video_codec.h"
namespace webrtc {
class SimulcastUtility {
public:
static uint32_t SumStreamMaxBitrate(int streams, const VideoCodec& codec);
static int NumberOfSimulcastStreams(const VideoCodec& codec);
static bool ValidSimulcastParameters(const VideoCodec& codec,
int num_streams);
static int NumberOfTemporalLayers(const VideoCodec& codec, int spatial_id);
// TODO(sprang): Remove this hack when ScreenshareLayers is gone.
static bool IsConferenceModeScreenshare(const VideoCodec& codec);
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_SIMULCAST_UTILITY_H_

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_VP8_CONSTANTS_H_
#define MODULES_VIDEO_CODING_UTILITY_VP8_CONSTANTS_H_
#include <stddef.h>
#include <stdint.h>
#include <string>
namespace webrtc {
// QP level below which VP8 variable framerate and zero hertz screencast reduces
// framerate due to diminishing quality enhancement returns.
constexpr int kVp8SteadyStateQpThreshold = 15;
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_VP8_CONSTANTS_H_

View file

@ -0,0 +1,200 @@
/*
* Copyright (c) 2015 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 "modules/video_coding/utility/vp8_header_parser.h"
#include "rtc_base/logging.h"
#include "rtc_base/system/arch.h"
namespace webrtc {
namespace vp8 {
namespace {
const size_t kCommonPayloadHeaderLength = 3;
const size_t kKeyPayloadHeaderLength = 10;
const int kMbFeatureTreeProbs = 3;
const int kNumMbSegments = 4;
const int kNumRefLfDeltas = 4;
const int kNumModeLfDeltas = 4;
} // namespace
// Bitstream parser according to
// https://tools.ietf.org/html/rfc6386#section-7.3
void VP8InitBitReader(VP8BitReader* const br,
const uint8_t* start,
const uint8_t* end) {
br->range_ = 255;
br->buf_ = start;
br->buf_end_ = end;
br->value_ = 0;
br->bits_ = 0;
// Read 2 bytes.
int i = 0;
while (++i <= 2) {
if (br->buf_ != br->buf_end_) {
br->value_ = br->value_ << 8 | *br->buf_++;
} else {
br->value_ = br->value_ << 8;
}
}
}
// Bit decoder according to https://tools.ietf.org/html/rfc6386#section-7.3
// Reads one bit from the bitstream, given that it has probability prob/256 to
// be 1.
int Vp8BitReaderGetBool(VP8BitReader* br, int prob) {
uint32_t split = 1 + (((br->range_ - 1) * prob) >> 8);
uint32_t split_hi = split << 8;
int retval = 0;
if (br->value_ >= split_hi) {
retval = 1;
br->range_ -= split;
br->value_ -= split_hi;
} else {
retval = 0;
br->range_ = split;
}
while (br->range_ < 128) {
br->value_ <<= 1;
br->range_ <<= 1;
if (++br->bits_ == 8) {
br->bits_ = 0;
if (br->buf_ != br->buf_end_) {
br->value_ |= *br->buf_++;
}
}
}
return retval;
}
uint32_t VP8GetValue(VP8BitReader* br, int num_bits) {
uint32_t v = 0;
while (num_bits--) {
// According to https://tools.ietf.org/html/rfc6386
// Probability 128/256 is used to encode header fields.
v = (v << 1) | Vp8BitReaderGetBool(br, 128);
}
return v;
}
// Not a read_signed_literal() from RFC 6386!
// This one is used to read e.g. quantizer_update, which is written as:
// L(num_bits), sign-bit.
int32_t VP8GetSignedValue(VP8BitReader* br, int num_bits) {
int v = VP8GetValue(br, num_bits);
int sign = VP8GetValue(br, 1);
return sign ? -v : v;
}
static void ParseSegmentHeader(VP8BitReader* br) {
int use_segment = VP8GetValue(br, 1);
if (use_segment) {
int update_map = VP8GetValue(br, 1);
if (VP8GetValue(br, 1)) { // update_segment_feature_data.
VP8GetValue(br, 1); // segment_feature_mode.
int s;
for (s = 0; s < kNumMbSegments; ++s) {
bool quantizer_update = VP8GetValue(br, 1);
if (quantizer_update) {
VP8GetSignedValue(br, 7);
}
}
for (s = 0; s < kNumMbSegments; ++s) {
bool loop_filter_update = VP8GetValue(br, 1);
if (loop_filter_update) {
VP8GetSignedValue(br, 6);
}
}
}
if (update_map) {
int s;
for (s = 0; s < kMbFeatureTreeProbs; ++s) {
bool segment_prob_update = VP8GetValue(br, 1);
if (segment_prob_update) {
VP8GetValue(br, 8);
}
}
}
}
}
static void ParseFilterHeader(VP8BitReader* br) {
VP8GetValue(br, 1); // filter_type.
VP8GetValue(br, 6); // loop_filter_level.
VP8GetValue(br, 3); // sharpness_level.
// mb_lf_adjustments.
int loop_filter_adj_enable = VP8GetValue(br, 1);
if (loop_filter_adj_enable) {
int mode_ref_lf_delta_update = VP8GetValue(br, 1);
if (mode_ref_lf_delta_update) {
int i;
for (i = 0; i < kNumRefLfDeltas; ++i) {
int ref_frame_delta_update_flag = VP8GetValue(br, 1);
if (ref_frame_delta_update_flag) {
VP8GetSignedValue(br, 6); // delta_magnitude.
}
}
for (i = 0; i < kNumModeLfDeltas; ++i) {
int mb_mode_delta_update_flag = VP8GetValue(br, 1);
if (mb_mode_delta_update_flag) {
VP8GetSignedValue(br, 6); // delta_magnitude.
}
}
}
}
}
bool GetQp(const uint8_t* buf, size_t length, int* qp) {
if (length < kCommonPayloadHeaderLength) {
RTC_LOG(LS_WARNING) << "Failed to get QP, invalid length.";
return false;
}
VP8BitReader br;
const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16);
int key_frame = !(bits & 1);
// Size of first partition in bytes.
uint32_t partition_length = (bits >> 5);
size_t header_length = kCommonPayloadHeaderLength;
if (key_frame) {
header_length = kKeyPayloadHeaderLength;
}
if (header_length + partition_length > length) {
RTC_LOG(LS_WARNING) << "Failed to get QP, invalid length: " << length;
return false;
}
buf += header_length;
VP8InitBitReader(&br, buf, buf + partition_length);
if (key_frame) {
// Color space and pixel type.
VP8GetValue(&br, 1);
VP8GetValue(&br, 1);
}
ParseSegmentHeader(&br);
ParseFilterHeader(&br);
// Parse log2_nbr_of_dct_partitions value.
VP8GetValue(&br, 2);
// Base QP.
const int base_q0 = VP8GetValue(&br, 7);
if (br.buf_ == br.buf_end_) {
RTC_LOG(LS_WARNING) << "Failed to get QP, bitstream is truncated or"
" corrupted.";
return false;
}
*qp = base_q0;
return true;
}
} // namespace vp8
} // namespace webrtc

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_VP8_HEADER_PARSER_H_
#define MODULES_VIDEO_CODING_UTILITY_VP8_HEADER_PARSER_H_
#include <stdint.h>
#include <stdio.h>
namespace webrtc {
namespace vp8 {
typedef struct VP8BitReader VP8BitReader;
struct VP8BitReader {
// Boolean decoder.
uint32_t value_; // Current value (2 bytes).
uint32_t range_; // Current range (always in [128..255] interval).
int bits_; // Number of bits shifted out of value, at most 7.
// Read buffer.
const uint8_t* buf_; // Next byte to be read.
const uint8_t* buf_end_; // End of read buffer.
};
// Gets the QP, QP range: [0, 127].
// Returns true on success, false otherwise.
bool GetQp(const uint8_t* buf, size_t length, int* qp);
} // namespace vp8
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_VP8_HEADER_PARSER_H_

View file

@ -0,0 +1,198 @@
/*
* 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_VP9_CONSTANTS_H_
#define MODULES_VIDEO_CODING_UTILITY_VP9_CONSTANTS_H_
#include <stddef.h>
#include <stdint.h>
#include <string>
namespace webrtc {
// Number of frames that can be stored for future reference.
constexpr size_t kVp9NumRefFrames = 8;
// Number of frame contexts that can be store for future reference.
constexpr size_t kVp9NumFrameContexts = 4;
// Each inter frame can use up to 3 frames for reference.
constexpr size_t kVp9RefsPerFrame = 3;
// Number of values that can be decoded for mv_fr.
constexpr size_t kVp9MvFrSize = 4;
// Number of positions to search in motion vector prediction.
constexpr size_t kVp9MvrefNeighbours = 8;
// Number of contexts when decoding intra_mode .
constexpr size_t kVp9BlockSizeGroups = 4;
// Number of different block sizes used.
constexpr size_t kVp9BlockSizes = 13;
// Sentinel value to mark partition choices that are illegal.
constexpr size_t kVp9BlockInvalid = 14;
// Number of contexts when decoding partition.
constexpr size_t kVp9PartitionContexts = 16;
// Smallest size of a mode info block.
constexpr size_t kVp9MiSize = 8;
// Minimum width of a tile in units of superblocks (although tiles on
// the right hand edge can be narrower).
constexpr size_t kVp9MinTileWidth_B64 = 4;
// Maximum width of a tile in units of superblocks.
constexpr size_t kVp9MaxTileWidth_B64 = 64;
// Number of motion vectors returned by find_mv_refs process.
constexpr size_t kVp9MaxMvRefCandidates = 2;
// Number of values that can be derived for ref_frame.
constexpr size_t kVp9MaxRefFrames = 4;
// Number of contexts for is_inter.
constexpr size_t kVp9IsInterContexts = 4;
// Number of contexts for comp_mode.
constexpr size_t kVp9CompModeContexts = 5;
// Number of contexts for single_ref and comp_ref.
constexpr size_t kVp9RefContexts = 5;
// Number of segments allowed in segmentation map.
constexpr size_t kVp9MaxSegments = 8;
// Index for quantizer segment feature.
constexpr size_t kVp9SegLvlAlt_Q = 0;
// Index for loop filter segment feature.
constexpr size_t kVp9SegLvlAlt_L = 1;
// Index for reference frame segment feature.
constexpr size_t kVp9SegLvlRefFrame = 2;
// Index for skip segment feature.
constexpr size_t kVp9SegLvlSkip = 3;
// Number of segment features.
constexpr size_t kVp9SegLvlMax = 4;
// Number of different plane types (Y or UV).
constexpr size_t kVp9BlockTypes = 2;
// Number of different prediction types (intra or inter).
constexpr size_t kVp9RefTypes = 2;
// Number of coefficient bands.
constexpr size_t kVp9CoefBands = 6;
// Number of contexts for decoding coefficients.
constexpr size_t kVp9PrevCoefContexts = 6;
// Number of coefficient probabilities that are directly transmitted.
constexpr size_t kVp9UnconstrainedNodes = 3;
// Number of contexts for transform size.
constexpr size_t kVp9TxSizeContexts = 2;
// Number of values for interp_filter.
constexpr size_t kVp9SwitchableFilters = 3;
// Number of contexts for interp_filter.
constexpr size_t kVp9InterpFilterContexts = 4;
// Number of contexts for decoding skip.
constexpr size_t kVp9SkipContexts = 3;
// Number of values for partition.
constexpr size_t kVp9PartitionTypes = 4;
// Number of values for tx_size.
constexpr size_t kVp9TxSizes = 4;
// Number of values for tx_mode.
constexpr size_t kVp9TxModes = 5;
// Inverse transform rows with DCT and columns with DCT.
constexpr size_t kVp9DctDct = 0;
// Inverse transform rows with DCT and columns with ADST.
constexpr size_t kVp9AdstDct = 1;
// Inverse transform rows with ADST and columns with DCT.
constexpr size_t kVp9DctAdst = 2;
// Inverse transform rows with ADST and columns with ADST.
constexpr size_t kVp9AdstAdst = 3;
// Number of values for y_mode.
constexpr size_t kVp9MbModeCount = 14;
// Number of values for intra_mode.
constexpr size_t kVp9IntraModes = 10;
// Number of values for inter_mode.
constexpr size_t kVp9InterModes = 4;
// Number of contexts for inter_mode.
constexpr size_t kVp9InterModeContexts = 7;
// Number of values for mv_joint.
constexpr size_t kVp9MvJoints = 4;
// Number of values for mv_class.
constexpr size_t kVp9MvClasses = 11;
// Number of values for mv_class0_bit.
constexpr size_t kVp9Class0Size = 2;
// Maximum number of bits for decoding motion vectors.
constexpr size_t kVp9MvOffsetBits = 10;
// Number of values allowed for a probability adjustment.
constexpr size_t kVp9MaxProb = 255;
// Number of different mode types for loop filtering.
constexpr size_t kVp9MaxModeLfDeltas = 2;
// Threshold at which motion vectors are considered large.
constexpr size_t kVp9CompandedMvrefThresh = 8;
// Maximum value used for loop filtering.
constexpr size_t kVp9MaxLoopFilter = 63;
// Number of bits of precision when scaling reference frames.
constexpr size_t kVp9RefScaleShift = 14;
// Number of bits of precision when performing inter prediction.
constexpr size_t kVp9SubpelBits = 4;
// 1 << kVp9SubpelBits.
constexpr size_t kVp9SubpelShifts = 16;
// kVp9SubpelShifts - 1.
constexpr size_t kVp9SubpelMask = 15;
// Value used when clipping motion vectors.
constexpr size_t kVp9MvBorder = 128;
// Value used when clipping motion vectors.
constexpr size_t kVp9InterpExtend = 4;
// Value used when clipping motion vectors.
constexpr size_t kVp9Borderinpixels = 160;
// Value used in adapting probabilities.
constexpr size_t kVp9MaxUpdateFactor = 128;
// Value used in adapting probabilities.
constexpr size_t kVp9CountSat = 20;
// Both candidates use ZEROMV.
constexpr size_t kVp9BothZero = 0;
// One candidate uses ZEROMV, one uses NEARMV or NEARESTMV.
constexpr size_t kVp9ZeroPlusPredicted = 1;
// Both candidates use NEARMV or NEARESTMV.
constexpr size_t kVp9BothPredicted = 2;
// One candidate uses NEWMV, one uses ZEROMV.
constexpr size_t kVp9NewPlusNonIntra = 3;
// Both candidates use NEWMV.
constexpr size_t kVp9BothNew = 4;
// One candidate uses intra prediction, one uses inter prediction.
constexpr size_t kVp9IntraPlusNonIntra = 5;
// Both candidates use intra prediction.
constexpr size_t kVp9BothIntra = 6;
// Sentinel value marking a case that can never occur.
constexpr size_t kVp9InvalidCase = 9;
enum class Vp9TxMode : uint8_t {
kOnly4X4 = 0,
kAllow8X8 = 1,
kAllow16x16 = 2,
kAllow32x32 = 3,
kTxModeSelect = 4
};
enum Vp9BlockSize : uint8_t {
kBlock4X4 = 0,
kBlock4X8 = 1,
kBlock8X4 = 2,
kBlock8X8 = 3,
kBlock8X16 = 4,
kBlock16X8 = 5,
kBlock16X16 = 6,
kBlock16X32 = 7,
kBlock32X16 = 8,
kBlock32X32 = 9,
kBlock32X64 = 10,
kBlock64X32 = 11,
kBlock64X64 = 12
};
enum Vp9Partition : uint8_t {
kPartitionNone = 0,
kPartitionHorizontal = 1,
kPartitionVertical = 2,
kPartitionSplit = 3
};
enum class Vp9ReferenceMode : uint8_t {
kSingleReference = 0,
kCompoundReference = 1,
kReferenceModeSelect = 2,
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_VP9_CONSTANTS_H_

View file

@ -0,0 +1,533 @@
/*
* Copyright (c) 2017 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 "modules/video_coding/utility/vp9_uncompressed_header_parser.h"
#include "absl/numeric/bits.h"
#include "absl/strings/string_view.h"
#include "rtc_base/bitstream_reader.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
namespace {
const size_t kVp9NumRefsPerFrame = 3;
const size_t kVp9MaxRefLFDeltas = 4;
const size_t kVp9MaxModeLFDeltas = 2;
const size_t kVp9MinTileWidthB64 = 4;
const size_t kVp9MaxTileWidthB64 = 64;
void Vp9ReadColorConfig(BitstreamReader& br,
Vp9UncompressedHeader* frame_info) {
if (frame_info->profile == 2 || frame_info->profile == 3) {
frame_info->bit_detph =
br.Read<bool>() ? Vp9BitDept::k12Bit : Vp9BitDept::k10Bit;
} else {
frame_info->bit_detph = Vp9BitDept::k8Bit;
}
frame_info->color_space = static_cast<Vp9ColorSpace>(br.ReadBits(3));
if (frame_info->color_space != Vp9ColorSpace::CS_RGB) {
frame_info->color_range =
br.Read<bool>() ? Vp9ColorRange::kFull : Vp9ColorRange::kStudio;
if (frame_info->profile == 1 || frame_info->profile == 3) {
static constexpr Vp9YuvSubsampling kSubSamplings[] = {
Vp9YuvSubsampling::k444, Vp9YuvSubsampling::k440,
Vp9YuvSubsampling::k422, Vp9YuvSubsampling::k420};
frame_info->sub_sampling = kSubSamplings[br.ReadBits(2)];
if (br.Read<bool>()) {
RTC_LOG(LS_WARNING) << "Failed to parse header. Reserved bit set.";
br.Invalidate();
return;
}
} else {
// Profile 0 or 2.
frame_info->sub_sampling = Vp9YuvSubsampling::k420;
}
} else {
// SRGB
frame_info->color_range = Vp9ColorRange::kFull;
if (frame_info->profile == 1 || frame_info->profile == 3) {
frame_info->sub_sampling = Vp9YuvSubsampling::k444;
if (br.Read<bool>()) {
RTC_LOG(LS_WARNING) << "Failed to parse header. Reserved bit set.";
br.Invalidate();
}
} else {
RTC_LOG(LS_WARNING) << "Failed to parse header. 4:4:4 color not supported"
" in profile 0 or 2.";
br.Invalidate();
}
}
}
void ReadRefreshFrameFlags(BitstreamReader& br,
Vp9UncompressedHeader* frame_info) {
// Refresh frame flags.
uint8_t flags = br.Read<uint8_t>();
for (int i = 0; i < 8; ++i) {
frame_info->updated_buffers.set(i, (flags & (0x01 << (7 - i))) != 0);
}
}
void Vp9ReadFrameSize(BitstreamReader& br, Vp9UncompressedHeader* frame_info) {
// 16 bits: frame (width|height) - 1.
frame_info->frame_width = br.Read<uint16_t>() + 1;
frame_info->frame_height = br.Read<uint16_t>() + 1;
}
void Vp9ReadRenderSize(size_t total_buffer_size_bits,
BitstreamReader& br,
Vp9UncompressedHeader* frame_info) {
// render_and_frame_size_different
if (br.Read<bool>()) {
frame_info->render_size_offset_bits =
total_buffer_size_bits - br.RemainingBitCount();
// 16 bits: render (width|height) - 1.
frame_info->render_width = br.Read<uint16_t>() + 1;
frame_info->render_height = br.Read<uint16_t>() + 1;
} else {
frame_info->render_height = frame_info->frame_height;
frame_info->render_width = frame_info->frame_width;
}
}
void Vp9ReadFrameSizeFromRefs(BitstreamReader& br,
Vp9UncompressedHeader* frame_info) {
for (size_t i = 0; i < kVp9NumRefsPerFrame; i++) {
// Size in refs.
if (br.Read<bool>()) {
frame_info->infer_size_from_reference = frame_info->reference_buffers[i];
return;
}
}
Vp9ReadFrameSize(br, frame_info);
}
void Vp9ReadLoopfilter(BitstreamReader& br) {
// 6 bits: filter level.
// 3 bits: sharpness level.
br.ConsumeBits(9);
if (!br.Read<bool>()) { // mode_ref_delta_enabled
return;
}
if (!br.Read<bool>()) { // mode_ref_delta_update
return;
}
for (size_t i = 0; i < kVp9MaxRefLFDeltas; i++) {
if (br.Read<bool>()) { // update_ref_delta
br.ConsumeBits(7);
}
}
for (size_t i = 0; i < kVp9MaxModeLFDeltas; i++) {
if (br.Read<bool>()) { // update_mode_delta
br.ConsumeBits(7);
}
}
}
void Vp9ReadQp(BitstreamReader& br, Vp9UncompressedHeader* frame_info) {
frame_info->base_qp = br.Read<uint8_t>();
// yuv offsets
frame_info->is_lossless = frame_info->base_qp == 0;
for (int i = 0; i < 3; ++i) {
if (br.Read<bool>()) { // if delta_coded
// delta_q is a signed integer with leading 4 bits containing absolute
// value and last bit containing sign. There are are two ways to represent
// zero with such encoding.
if ((br.ReadBits(5) & 0b1111'0) != 0) { // delta_q
frame_info->is_lossless = false;
}
}
}
}
void Vp9ReadSegmentationParams(BitstreamReader& br,
Vp9UncompressedHeader* frame_info) {
constexpr int kSegmentationFeatureBits[kVp9SegLvlMax] = {8, 6, 2, 0};
constexpr bool kSegmentationFeatureSigned[kVp9SegLvlMax] = {true, true, false,
false};
frame_info->segmentation_enabled = br.Read<bool>();
if (!frame_info->segmentation_enabled) {
return;
}
if (br.Read<bool>()) { // update_map
frame_info->segmentation_tree_probs.emplace();
for (int i = 0; i < 7; ++i) {
if (br.Read<bool>()) {
(*frame_info->segmentation_tree_probs)[i] = br.Read<uint8_t>();
} else {
(*frame_info->segmentation_tree_probs)[i] = 255;
}
}
// temporal_update
frame_info->segmentation_pred_prob.emplace();
if (br.Read<bool>()) {
for (int i = 0; i < 3; ++i) {
if (br.Read<bool>()) {
(*frame_info->segmentation_pred_prob)[i] = br.Read<uint8_t>();
} else {
(*frame_info->segmentation_pred_prob)[i] = 255;
}
}
} else {
frame_info->segmentation_pred_prob->fill(255);
}
}
if (br.Read<bool>()) { // segmentation_update_data
frame_info->segmentation_is_delta = br.Read<bool>();
for (size_t i = 0; i < kVp9MaxSegments; ++i) {
for (size_t j = 0; j < kVp9SegLvlMax; ++j) {
if (!br.Read<bool>()) { // feature_enabled
continue;
}
if (kSegmentationFeatureBits[j] == 0) {
// No feature bits used and no sign, just mark it and return.
frame_info->segmentation_features[i][j] = 1;
continue;
}
frame_info->segmentation_features[i][j] =
br.ReadBits(kSegmentationFeatureBits[j]);
if (kSegmentationFeatureSigned[j] && br.Read<bool>()) {
(*frame_info->segmentation_features[i][j]) *= -1;
}
}
}
}
}
void Vp9ReadTileInfo(BitstreamReader& br, Vp9UncompressedHeader* frame_info) {
size_t mi_cols = (frame_info->frame_width + 7) >> 3;
size_t sb64_cols = (mi_cols + 7) >> 3;
size_t min_log2 = 0;
while ((kVp9MaxTileWidthB64 << min_log2) < sb64_cols) {
++min_log2;
}
size_t max_log2 = 1;
while ((sb64_cols >> max_log2) >= kVp9MinTileWidthB64) {
++max_log2;
}
--max_log2;
frame_info->tile_cols_log2 = min_log2;
while (frame_info->tile_cols_log2 < max_log2) {
if (br.Read<bool>()) {
++frame_info->tile_cols_log2;
} else {
break;
}
}
frame_info->tile_rows_log2 = 0;
if (br.Read<bool>()) {
++frame_info->tile_rows_log2;
if (br.Read<bool>()) {
++frame_info->tile_rows_log2;
}
}
}
const Vp9InterpolationFilter kLiteralToType[4] = {
Vp9InterpolationFilter::kEightTapSmooth, Vp9InterpolationFilter::kEightTap,
Vp9InterpolationFilter::kEightTapSharp, Vp9InterpolationFilter::kBilinear};
} // namespace
std::string Vp9UncompressedHeader::ToString() const {
char buf[1024];
rtc::SimpleStringBuilder oss(buf);
oss << "Vp9UncompressedHeader { "
<< "profile = " << profile;
if (show_existing_frame) {
oss << ", show_existing_frame = " << *show_existing_frame << " }";
return oss.str();
}
oss << ", frame type = " << (is_keyframe ? "key" : "delta")
<< ", show_frame = " << (show_frame ? "true" : "false")
<< ", error_resilient = " << (error_resilient ? "true" : "false");
oss << ", bit_depth = ";
switch (bit_detph) {
case Vp9BitDept::k8Bit:
oss << "8bit";
break;
case Vp9BitDept::k10Bit:
oss << "10bit";
break;
case Vp9BitDept::k12Bit:
oss << "12bit";
break;
}
if (color_space) {
oss << ", color_space = ";
switch (*color_space) {
case Vp9ColorSpace::CS_UNKNOWN:
oss << "unknown";
break;
case Vp9ColorSpace::CS_BT_601:
oss << "CS_BT_601 Rec. ITU-R BT.601-7";
break;
case Vp9ColorSpace::CS_BT_709:
oss << "Rec. ITU-R BT.709-6";
break;
case Vp9ColorSpace::CS_SMPTE_170:
oss << "SMPTE-170";
break;
case Vp9ColorSpace::CS_SMPTE_240:
oss << "SMPTE-240";
break;
case Vp9ColorSpace::CS_BT_2020:
oss << "Rec. ITU-R BT.2020-2";
break;
case Vp9ColorSpace::CS_RESERVED:
oss << "Reserved";
break;
case Vp9ColorSpace::CS_RGB:
oss << "sRGB (IEC 61966-2-1)";
break;
}
}
if (color_range) {
oss << ", color_range = ";
switch (*color_range) {
case Vp9ColorRange::kFull:
oss << "full";
break;
case Vp9ColorRange::kStudio:
oss << "studio";
break;
}
}
if (sub_sampling) {
oss << ", sub_sampling = ";
switch (*sub_sampling) {
case Vp9YuvSubsampling::k444:
oss << "444";
break;
case Vp9YuvSubsampling::k440:
oss << "440";
break;
case Vp9YuvSubsampling::k422:
oss << "422";
break;
case Vp9YuvSubsampling::k420:
oss << "420";
break;
}
}
if (infer_size_from_reference) {
oss << ", infer_frame_resolution_from = " << *infer_size_from_reference;
} else {
oss << ", frame_width = " << frame_width
<< ", frame_height = " << frame_height;
}
if (render_width != 0 && render_height != 0) {
oss << ", render_width = " << render_width
<< ", render_height = " << render_height;
}
oss << ", base qp = " << base_qp;
if (reference_buffers[0] != -1) {
oss << ", last_buffer = " << reference_buffers[0];
}
if (reference_buffers[1] != -1) {
oss << ", golden_buffer = " << reference_buffers[1];
}
if (reference_buffers[2] != -1) {
oss << ", altref_buffer = " << reference_buffers[2];
}
oss << ", updated buffers = { ";
bool first = true;
for (int i = 0; i < 8; ++i) {
if (updated_buffers.test(i)) {
if (first) {
first = false;
} else {
oss << ", ";
}
oss << i;
}
}
oss << " }";
oss << ", compressed_header_size_bytes = " << compressed_header_size;
oss << " }";
return oss.str();
}
void Parse(BitstreamReader& br,
Vp9UncompressedHeader* frame_info,
bool qp_only) {
const size_t total_buffer_size_bits = br.RemainingBitCount();
// Frame marker.
if (br.ReadBits(2) != 0b10) {
RTC_LOG(LS_WARNING) << "Failed to parse header. Frame marker should be 2.";
br.Invalidate();
return;
}
// Profile has low bit first.
frame_info->profile = br.ReadBit();
frame_info->profile |= br.ReadBit() << 1;
if (frame_info->profile > 2 && br.Read<bool>()) {
RTC_LOG(LS_WARNING)
<< "Failed to parse header. Unsupported bitstream profile.";
br.Invalidate();
return;
}
// Show existing frame.
if (br.Read<bool>()) {
frame_info->show_existing_frame = br.ReadBits(3);
return;
}
// Frame type: KEY_FRAME(0), INTER_FRAME(1).
frame_info->is_keyframe = !br.Read<bool>();
frame_info->show_frame = br.Read<bool>();
frame_info->error_resilient = br.Read<bool>();
if (frame_info->is_keyframe) {
if (br.ReadBits(24) != 0x498342) {
RTC_LOG(LS_WARNING) << "Failed to parse header. Invalid sync code.";
br.Invalidate();
return;
}
Vp9ReadColorConfig(br, frame_info);
Vp9ReadFrameSize(br, frame_info);
Vp9ReadRenderSize(total_buffer_size_bits, br, frame_info);
// Key-frames implicitly update all buffers.
frame_info->updated_buffers.set();
} else {
// Non-keyframe.
bool is_intra_only = false;
if (!frame_info->show_frame) {
is_intra_only = br.Read<bool>();
}
if (!frame_info->error_resilient) {
br.ConsumeBits(2); // Reset frame context.
}
if (is_intra_only) {
if (br.ReadBits(24) != 0x498342) {
RTC_LOG(LS_WARNING) << "Failed to parse header. Invalid sync code.";
br.Invalidate();
return;
}
if (frame_info->profile > 0) {
Vp9ReadColorConfig(br, frame_info);
} else {
frame_info->color_space = Vp9ColorSpace::CS_BT_601;
frame_info->sub_sampling = Vp9YuvSubsampling::k420;
frame_info->bit_detph = Vp9BitDept::k8Bit;
}
frame_info->reference_buffers.fill(-1);
ReadRefreshFrameFlags(br, frame_info);
Vp9ReadFrameSize(br, frame_info);
Vp9ReadRenderSize(total_buffer_size_bits, br, frame_info);
} else {
ReadRefreshFrameFlags(br, frame_info);
frame_info->reference_buffers_sign_bias[0] = false;
for (size_t i = 0; i < kVp9NumRefsPerFrame; i++) {
frame_info->reference_buffers[i] = br.ReadBits(3);
frame_info->reference_buffers_sign_bias[Vp9ReferenceFrame::kLast + i] =
br.Read<bool>();
}
Vp9ReadFrameSizeFromRefs(br, frame_info);
Vp9ReadRenderSize(total_buffer_size_bits, br, frame_info);
frame_info->allow_high_precision_mv = br.Read<bool>();
// Interpolation filter.
if (br.Read<bool>()) {
frame_info->interpolation_filter = Vp9InterpolationFilter::kSwitchable;
} else {
frame_info->interpolation_filter = kLiteralToType[br.ReadBits(2)];
}
}
}
if (!frame_info->error_resilient) {
// 1 bit: Refresh frame context.
// 1 bit: Frame parallel decoding mode.
br.ConsumeBits(2);
}
// Frame context index.
frame_info->frame_context_idx = br.ReadBits(2);
Vp9ReadLoopfilter(br);
// Read base QP.
Vp9ReadQp(br, frame_info);
if (qp_only) {
// Not interested in the rest of the header, return early.
return;
}
Vp9ReadSegmentationParams(br, frame_info);
Vp9ReadTileInfo(br, frame_info);
frame_info->compressed_header_size = br.Read<uint16_t>();
frame_info->uncompressed_header_size =
(total_buffer_size_bits / 8) - (br.RemainingBitCount() / 8);
}
absl::optional<Vp9UncompressedHeader> ParseUncompressedVp9Header(
rtc::ArrayView<const uint8_t> buf) {
BitstreamReader reader(buf);
Vp9UncompressedHeader frame_info;
Parse(reader, &frame_info, /*qp_only=*/false);
if (reader.Ok() && frame_info.frame_width > 0) {
return frame_info;
}
return absl::nullopt;
}
namespace vp9 {
bool GetQp(const uint8_t* buf, size_t length, int* qp) {
BitstreamReader reader(rtc::MakeArrayView(buf, length));
Vp9UncompressedHeader frame_info;
Parse(reader, &frame_info, /*qp_only=*/true);
if (!reader.Ok()) {
return false;
}
*qp = frame_info.base_qp;
return true;
}
} // namespace vp9
} // namespace webrtc

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2017 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_VP9_UNCOMPRESSED_HEADER_PARSER_H_
#define MODULES_VIDEO_CODING_UTILITY_VP9_UNCOMPRESSED_HEADER_PARSER_H_
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <bitset>
#include <string>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "modules/video_coding/utility/vp9_constants.h"
namespace webrtc {
namespace vp9 {
// Gets the QP, QP range: [0, 255].
// Returns true on success, false otherwise.
bool GetQp(const uint8_t* buf, size_t length, int* qp);
} // namespace vp9
// Bit depth per channel. Support varies by profile.
enum class Vp9BitDept : uint8_t {
k8Bit = 8,
k10Bit = 10,
k12Bit = 12,
};
enum class Vp9ColorSpace : uint8_t {
CS_UNKNOWN = 0, // Unknown (in this case the color space must be signaled
// outside the VP9 bitstream).
CS_BT_601 = 1, // CS_BT_601 Rec. ITU-R BT.601-7
CS_BT_709 = 2, // Rec. ITU-R BT.709-6
CS_SMPTE_170 = 3, // SMPTE-170
CS_SMPTE_240 = 4, // SMPTE-240
CS_BT_2020 = 5, // Rec. ITU-R BT.2020-2
CS_RESERVED = 6, // Reserved
CS_RGB = 7, // sRGB (IEC 61966-2-1)
};
enum class Vp9ColorRange {
kStudio, // Studio swing:
// For BitDepth equals 8:
// Y is between 16 and 235 inclusive.
// U and V are between 16 and 240 inclusive.
// For BitDepth equals 10:
// Y is between 64 and 940 inclusive.
// U and V are between 64 and 960 inclusive.
// For BitDepth equals 12:
// Y is between 256 and 3760.
// U and V are between 256 and 3840 inclusive.
kFull // Full swing; no restriction on Y, U, V values.
};
enum class Vp9YuvSubsampling {
k444,
k440,
k422,
k420,
};
enum Vp9ReferenceFrame : int {
kNone = -1,
kIntra = 0,
kLast = 1,
kGolden = 2,
kAltref = 3,
};
enum class Vp9InterpolationFilter : uint8_t {
kEightTap = 0,
kEightTapSmooth = 1,
kEightTapSharp = 2,
kBilinear = 3,
kSwitchable = 4
};
struct Vp9UncompressedHeader {
int profile = 0; // Profiles 0-3 are valid.
absl::optional<uint8_t> show_existing_frame;
bool is_keyframe = false;
bool show_frame = false;
bool error_resilient = false;
Vp9BitDept bit_detph = Vp9BitDept::k8Bit;
absl::optional<Vp9ColorSpace> color_space;
absl::optional<Vp9ColorRange> color_range;
absl::optional<Vp9YuvSubsampling> sub_sampling;
int frame_width = 0;
int frame_height = 0;
int render_width = 0;
int render_height = 0;
// Width/height of the tiles used (in units of 8x8 blocks).
size_t tile_cols_log2 = 0; // tile_cols = 1 << tile_cols_log2
size_t tile_rows_log2 = 0; // tile_rows = 1 << tile_rows_log2
absl::optional<size_t> render_size_offset_bits;
Vp9InterpolationFilter interpolation_filter =
Vp9InterpolationFilter::kEightTap;
bool allow_high_precision_mv = false;
int base_qp = 0;
bool is_lossless = false;
uint8_t frame_context_idx = 0;
bool segmentation_enabled = false;
absl::optional<std::array<uint8_t, 7>> segmentation_tree_probs;
absl::optional<std::array<uint8_t, 3>> segmentation_pred_prob;
bool segmentation_is_delta = false;
std::array<std::array<absl::optional<int>, kVp9SegLvlMax>, kVp9MaxSegments>
segmentation_features;
// Which of the 8 reference buffers may be used as references for this frame.
// -1 indicates not used (e.g. {-1, -1, -1} for intra-only frames).
std::array<int, kVp9RefsPerFrame> reference_buffers = {-1, -1, -1};
// Sign bias corresponding to reference buffers, where the index is a
// ReferenceFrame.
// false/0 indidate backwards reference, true/1 indicate forwards reference).
std::bitset<kVp9MaxRefFrames> reference_buffers_sign_bias = 0;
// Indicates which reference buffer [0,7] to infer the frame size from.
absl::optional<int> infer_size_from_reference;
// Which of the 8 reference buffers are updated by this frame.
std::bitset<kVp9NumRefFrames> updated_buffers = 0;
// Header sizes, in bytes.
uint32_t uncompressed_header_size = 0;
uint32_t compressed_header_size = 0;
bool is_intra_only() const {
return reference_buffers[0] == -1 && reference_buffers[1] == -1 &&
reference_buffers[2] == -1;
}
std::string ToString() const;
};
// Parses the uncompressed header and populates (most) values in a
// UncompressedHeader struct. Returns nullopt on failure.
absl::optional<Vp9UncompressedHeader> ParseUncompressedVp9Header(
rtc::ArrayView<const uint8_t> buf);
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_VP9_UNCOMPRESSED_HEADER_PARSER_H_