Skip to content

Commit

Permalink
media/base: Store Bitrate in VideoBitrateAllocation
Browse files Browse the repository at this point in the history
Prior to this CL, the VideoBitrateAllocation and Bitrate classes were
fully separate representations of similar data. After this CL,
VideoBitrateAllocation (VBA) stores a Bitrate representing the sum of
the bitrates across the layers in the VBA.

A design doc for this change is available to Googlers at
https://docs.google.com/document/d/1mZxe9jjdbryiPAloVbaIqSLFKE3s-4969sqmhNFKRGc/edit?usp=sharing

Bug: b:197905631
Change-Id: I66becc6b467dd69151f3a3e42a745d9dae6eec85
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3440380
Reviewed-by: Joe Mason <joenotcharles@google.com>
Reviewed-by: Eugene Zemtsov <eugene@chromium.org>
Commit-Queue: Clarissa Garvey <clarissagarvey@chromium.org>
Cr-Commit-Position: refs/heads/main@{#987092}
  • Loading branch information
Clarissa Garvey authored and Chromium LUCI CQ committed Mar 30, 2022
1 parent d4e68c7 commit b5e8c6b
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 21 deletions.
72 changes: 66 additions & 6 deletions media/base/video_bitrate_allocation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,74 @@

#include "base/check_op.h"
#include "base/numerics/checked_math.h"
#include "media/base/bitrate.h"

namespace {

static media::Bitrate MakeReplacementBitrate(const media::Bitrate& old,
uint32_t target_bps,
uint32_t peak_bps) {
switch (old.mode()) {
case media::Bitrate::Mode::kConstant:
return media::Bitrate::ConstantBitrate(target_bps);
case media::Bitrate::Mode::kVariable:
return media::Bitrate::VariableBitrate(target_bps, peak_bps);
}
}

} // namespace

namespace media {

constexpr size_t VideoBitrateAllocation::kMaxSpatialLayers;
constexpr size_t VideoBitrateAllocation::kMaxTemporalLayers;

VideoBitrateAllocation::VideoBitrateAllocation(Bitrate::Mode mode) {
switch (mode) {
case Bitrate::Mode::kConstant:
sum_bitrate_ = Bitrate::ConstantBitrate(0u);
break;
case Bitrate::Mode::kVariable:
// For variable bitrates, the peak must not be zero as enforced by
// Bitrate.
sum_bitrate_ = Bitrate::VariableBitrate(0u, 1u);
break;
}
}

bool VideoBitrateAllocation::SetPeakBps(uint32_t peak_bps) {
if (sum_bitrate_.mode() != Bitrate::Mode::kVariable)
return false;

if (peak_bps == 0u)
return false;

if (sum_bitrate_.target_bps() > peak_bps)
return false;

Bitrate old = sum_bitrate_;
sum_bitrate_ = MakeReplacementBitrate(old, old.target_bps(), peak_bps);
return true;
}

bool VideoBitrateAllocation::SetBitrate(size_t spatial_index,
size_t temporal_index,
uint32_t bitrate_bps) {
CHECK_LT(spatial_index, kMaxSpatialLayers);
CHECK_LT(temporal_index, kMaxTemporalLayers);

base::CheckedNumeric<uint32_t> checked_sum = sum_;
checked_sum -= bitrates_[spatial_index][temporal_index];
base::CheckedNumeric<uint32_t> checked_sum = sum_bitrate_.target_bps();
uint32_t old_bitrate_bps = bitrates_[spatial_index][temporal_index];
checked_sum -= old_bitrate_bps;
checked_sum += bitrate_bps;
if (!checked_sum.IsValid()) {
return false; // Would cause overflow of the sum.
}

sum_ = checked_sum.ValueOrDie();
const uint32_t new_sum_bps = checked_sum.ValueOrDefault(0u);
const uint32_t new_peak_bps = std::max(sum_bitrate_.peak_bps(), new_sum_bps);
sum_bitrate_ =
MakeReplacementBitrate(sum_bitrate_, new_sum_bps, new_peak_bps);
bitrates_[spatial_index][temporal_index] = bitrate_bps;
return true;
}
Expand All @@ -43,7 +91,11 @@ uint32_t VideoBitrateAllocation::GetBitrateBps(size_t spatial_index,
}

uint32_t VideoBitrateAllocation::GetSumBps() const {
return sum_;
return sum_bitrate_.target_bps();
}

const Bitrate VideoBitrateAllocation::GetSumBitrate() const {
return sum_bitrate_;
}

std::string VideoBitrateAllocation::ToString() const {
Expand Down Expand Up @@ -83,13 +135,21 @@ std::string VideoBitrateAllocation::ToString() const {
}
ss << "}";
}
ss << "}";
ss << "}, mode ";
switch (sum_bitrate_.mode()) {
case Bitrate::Mode::kConstant:
ss << "CBR";
break;
case Bitrate::Mode::kVariable:
ss << "VBR with peak bps " << sum_bitrate_.peak_bps();
break;
}
return ss.str();
}

bool VideoBitrateAllocation::operator==(
const VideoBitrateAllocation& other) const {
if (sum_ != other.sum_) {
if (sum_bitrate_ != other.sum_bitrate_) {
return false;
}
return memcmp(bitrates_, other.bitrates_, sizeof(bitrates_)) == 0;
Expand Down
29 changes: 21 additions & 8 deletions media/base/video_bitrate_allocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <stdint.h>
#include <string>

#include "media/base/bitrate.h"
#include "media/base/media_export.h"

namespace media {
Expand All @@ -21,14 +22,16 @@ class MEDIA_EXPORT VideoBitrateAllocation {
static constexpr size_t kMaxSpatialLayers = 5;
static constexpr size_t kMaxTemporalLayers = 4;

VideoBitrateAllocation() = default;
explicit VideoBitrateAllocation(
Bitrate::Mode mode = Bitrate::Mode::kConstant);
~VideoBitrateAllocation() = default;

// Returns if this bitrate can't be set (sum exceeds uint32_t max value). Do
// not use an integer or uint64_t version of this. If you have a signed or
// 64-bit value you want to use as input, you must explicitly convert to
// uint32_t before calling. This is intended to prevent implicit and unsafe
// type conversion.
// Returns true iff. the bitrate was set (sum within uint32_t max value). If
// a variable bitrate is used and the previous peak bitrate was below the new
// sum of bitrates across layers, this will automatically set the new peak to
// equal the new sum. If you have a signed or 64-bit value you want to use as
// input, you must explicitly convert to uint32_t before calling. This is
// intended to prevent implicit and unsafe type conversion.
bool SetBitrate(size_t spatial_index,
size_t temporal_index,
uint32_t bitrate_bps);
Expand All @@ -46,12 +49,21 @@ class MEDIA_EXPORT VideoBitrateAllocation {
size_t temporal_index,
uint64_t bitrate_bps) = delete;

// True iff. this bitrate allocation can have its peak set to |peak_bps| (the
// peak must be greater than the sum of the layers' bitrates, and the bitrate
// mode must be variable bitrate).
bool SetPeakBps(uint32_t peak_bps);

// Returns the bitrate for specified spatial/temporal index, or 0 if not set.
uint32_t GetBitrateBps(size_t spatial_index, size_t temporal_index) const;

// Sum of all bitrates.
uint32_t GetSumBps() const;

// Non-layered bitrate allocation. If there are layers, this bitrate's target
// bps equals the sum of the layers' bitrates.
const Bitrate GetSumBitrate() const;

std::string ToString() const;

bool operator==(const VideoBitrateAllocation& other) const;
Expand All @@ -60,8 +72,9 @@ class MEDIA_EXPORT VideoBitrateAllocation {
}

private:
// Cached sum of all elements of |bitrates_|, for performance.
uint32_t sum_ = 0u;
// A bitrate representing a cached sum of the elements of |bitrates_|, for
// performance.
Bitrate sum_bitrate_;
uint32_t bitrates_[kMaxSpatialLayers][kMaxTemporalLayers] = {};
};

Expand Down
130 changes: 123 additions & 7 deletions media/base/video_bitrate_allocation_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@

namespace media {

TEST(VideoBitrateAllocationTest, Constructor_DefaultsModeConstant) {
VideoBitrateAllocation allocation;

ASSERT_EQ(allocation.GetSumBitrate().mode(), Bitrate::Mode::kConstant);
}

TEST(VideoBitrateAllocationTest, Constructor_ConstantBitrate_CorrectMode) {
VideoBitrateAllocation allocation(Bitrate::Mode::kConstant);

ASSERT_EQ(allocation.GetSumBitrate().mode(), Bitrate::Mode::kConstant);
}

TEST(VideoBitrateAllocationTest, Constructor_VariableBitrate_CorrectMode) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);

ASSERT_EQ(allocation.GetSumBitrate().mode(), Bitrate::Mode::kVariable);
}

TEST(VideoBitrateAllocationTest,
Constructor_ConstantBitrate_InitializesTargetZero) {
VideoBitrateAllocation allocation;

ASSERT_EQ(allocation.GetSumBitrate().target_bps(), 0u);
}

TEST(VideoBitrateAllocationTest,
Constructor_VariableBitrate_InitializesTargetZero) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);

ASSERT_EQ(allocation.GetSumBitrate().target_bps(), 0u);
}

TEST(VideoBitrateAllocationTest, SetAndGet) {
uint32_t sum = 0u;
uint32_t layer_rate = 0u;
Expand Down Expand Up @@ -39,6 +71,66 @@ TEST(VideoBitrateAllocationTest, SetAndGet) {
}
}

TEST(VideoBitrateAllocationTest, SetBitrate_VariableBitrate_CorrectSum) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 1u);
allocation.SetBitrate(1, 0, 2u);
allocation.SetBitrate(0, 1, 3u);
allocation.SetBitrate(2, 2, 4u);
allocation.SetBitrate(1, 2, 5u);

ASSERT_EQ(15u, allocation.GetSumBps());
}

TEST(VideoBitrateAllocationTest, SetBitrate_PeakTooLow_IncreasesPeak) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetPeakBps(50u);

ASSERT_TRUE(allocation.SetBitrate(0, 0, 1000u));
ASSERT_EQ(allocation.GetSumBitrate().peak_bps(), 1000u);
}

TEST(VideoBitrateAllocationTest, SetPeakBps_GreaterThanSum_Succeeds) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 500u);
allocation.SetBitrate(0, 1, 500u);
allocation.SetBitrate(1, 0, 500u);

ASSERT_TRUE(allocation.SetPeakBps(2000u));
ASSERT_EQ(allocation.GetSumBitrate().peak_bps(), 2000u);
}

TEST(VideoBitrateAllocationTest, SetPeakBps_EqualToSum_Succeeds) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 400u);
allocation.SetBitrate(0, 1, 300u);
allocation.SetBitrate(1, 0, 300u);

EXPECT_EQ(allocation.GetSumBps(), 1000u);

ASSERT_TRUE(allocation.SetPeakBps(1000u));
ASSERT_EQ(allocation.GetSumBitrate().peak_bps(), 1000u);
}

TEST(VideoBitrateAllocationTest, SetPeakBps_ImplicitConstantBitrate_Fails) {
VideoBitrateAllocation allocation;

ASSERT_FALSE(allocation.SetPeakBps(1u));
}

TEST(VideoBitrateAllocationTest, SetPeakBps_ConstantBitrate_Fails) {
VideoBitrateAllocation allocation(Bitrate::Mode::kConstant);

ASSERT_FALSE(allocation.SetPeakBps(1u));
}

TEST(VideoBitrateAllocationTest, SetPeakBps_PeakLessThanSum_Fails) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 1000u);

ASSERT_FALSE(allocation.SetPeakBps(999u));
}

TEST(VideoBitrateAllocationTest, CanSetMaxValue) {
VideoBitrateAllocation allocation;
// Single cell containing max value.
Expand Down Expand Up @@ -87,26 +179,27 @@ TEST(VideoBitrateAllocationTest, ToString) {
EXPECT_TRUE(allocation.SetBitrate(0, 1, 456u));
EXPECT_TRUE(allocation.SetBitrate(0, 2, 789u));
EXPECT_EQ(allocation.ToString(),
"active spatial layers: 1, {SL#0: {123, 456, 789}}");
"active spatial layers: 1, {SL#0: {123, 456, 789}}, mode CBR");

// Add spatial layer.
EXPECT_TRUE(allocation.SetBitrate(1, 0, 789u));
EXPECT_TRUE(allocation.SetBitrate(1, 1, 456u));
EXPECT_EQ(
allocation.ToString(),
"active spatial layers: 2, {SL#0: {123, 456, 789}, SL#1: {789, 456}}");
EXPECT_EQ(allocation.ToString(),
"active spatial layers: 2, {SL#0: {123, 456, 789}, SL#1: {789, "
"456}}, mode CBR");

// Reset the bottom spatial layer.
EXPECT_TRUE(allocation.SetBitrate(0, 0, 0u));
EXPECT_TRUE(allocation.SetBitrate(0, 1, 0u));
EXPECT_TRUE(allocation.SetBitrate(0, 2, 0u));
EXPECT_EQ(allocation.ToString(),
"active spatial layers: 1, {SL#1: {789, 456}}");
"active spatial layers: 1, {SL#1: {789, 456}}, mode CBR");

// Add one more spatial layer.
EXPECT_TRUE(allocation.SetBitrate(2, 0, 123u));
EXPECT_EQ(allocation.ToString(),
"active spatial layers: 2, {SL#1: {789, 456}, SL#2: {123}}");
EXPECT_EQ(
allocation.ToString(),
"active spatial layers: 2, {SL#1: {789, 456}, SL#2: {123}}, mode CBR");

// Reset all the spatial layers.
EXPECT_TRUE(allocation.SetBitrate(1, 0, 0u));
Expand All @@ -115,4 +208,27 @@ TEST(VideoBitrateAllocationTest, ToString) {
EXPECT_EQ(allocation.ToString(), "Empty VideoBitrateAllocation");
}

TEST(VideoBitrateAllocationTest, ToString_VariableBitrateAndSingleLayer) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 1u);

EXPECT_EQ(allocation.ToString(),
"active spatial layers: 1, {SL#0: {1}}, mode VBR with peak bps 1");
}

TEST(VideoBitrateAllocationTest, ToString_VariableBitrateAndMultiLayer) {
VideoBitrateAllocation allocation(Bitrate::Mode::kVariable);
allocation.SetBitrate(0, 0, 1u);
allocation.SetBitrate(0, 1, 2u);
allocation.SetBitrate(0, 2, 3u);
allocation.SetBitrate(1, 0, 4u);
allocation.SetBitrate(1, 1, 5u);
allocation.SetBitrate(1, 2, 6u);
allocation.SetPeakBps(100u);

EXPECT_EQ(allocation.ToString(),
"active spatial layers: 2, {SL#0: {1, 2, 3}, SL#1: {4, 5, 6}}, "
"mode VBR with peak bps 100");
}

} // namespace media
7 changes: 7 additions & 0 deletions media/mojo/mojom/video_encode_accelerator.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,17 @@ interface VideoEncodeAcceleratorProvider {
=> (array<VideoEncodeAcceleratorSupportedProfile> profiles);
};

// This defines a mojo transport format used in the
// mojo::VideoBitrateAllocation that corresponds to media::Bitrate::peak_bps_
struct VariableBitratePeak {
uint32 bps;
};

// Class that describes how video bitrate, in bps, is allocated across temporal
// and spatial layers. See media::VideoBitrateAllocation for more details.
struct VideoBitrateAllocation {
array<uint32> bitrates;
VariableBitratePeak? variable_bitrate_peak;
};

// This defines a mojo transport format for
Expand Down

0 comments on commit b5e8c6b

Please sign in to comment.