Extract bitrate allocation of spatial/temporal layers out of codec impl.
This CL makes a number of intervowen changes: * Add BitrateAllocation struct, that contains a codec independent view of how the target bitrate is distributed over spatial and temporal layers. * Adds the BitrateAllocator interface, which takes a bitrate and frame rate and produces a BitrateAllocation. * A default (non layered) implementation is added, and SimulcastRateAllocator is extended to fully handle VP8 allocation. This includes capturing TemporalLayer instances created by the encoder. * ViEEncoder now owns both the bitrate allocator and the temporal layer factories for VP8. This allows allocation to happen fully outside of the encoder implementation. This refactoring will make it possible for ViEEncoder to signal the full picture of target bitrates to the RTCP module. BUG=webrtc:6301 Review-Url: https://codereview.webrtc.org/2434073003 Cr-Commit-Position: refs/heads/master@{#14998}
This commit is contained in:
@ -12,6 +12,7 @@
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_SIMULCAST_UNITTEST_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@ -20,6 +21,7 @@
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||
#include "webrtc/modules/video_coding/include/mock/mock_video_codec_interface.h"
|
||||
#include "webrtc/modules/video_coding/utility/simulcast_rate_allocator.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/video_frame.h"
|
||||
|
||||
@ -147,83 +149,6 @@ class Vp8TestDecodedImageCallback : public DecodedImageCallback {
|
||||
int decoded_frames_;
|
||||
};
|
||||
|
||||
class SkipEncodingUnusedStreamsTest {
|
||||
public:
|
||||
std::vector<unsigned int> RunTest(VP8Encoder* encoder,
|
||||
VideoCodec* settings,
|
||||
uint32_t target_bitrate) {
|
||||
SpyingTemporalLayersFactory spy_factory;
|
||||
settings->VP8()->tl_factory = &spy_factory;
|
||||
EXPECT_EQ(0, encoder->InitEncode(settings, 1, 1200));
|
||||
|
||||
encoder->SetRates(target_bitrate, 30);
|
||||
|
||||
std::vector<unsigned int> configured_bitrates;
|
||||
for (std::vector<TemporalLayers*>::const_iterator it =
|
||||
spy_factory.spying_layers_.begin();
|
||||
it != spy_factory.spying_layers_.end(); ++it) {
|
||||
configured_bitrates.push_back(
|
||||
static_cast<SpyingTemporalLayers*>(*it)->configured_bitrate_);
|
||||
}
|
||||
return configured_bitrates;
|
||||
}
|
||||
|
||||
class SpyingTemporalLayers : public TemporalLayers {
|
||||
public:
|
||||
explicit SpyingTemporalLayers(TemporalLayers* layers)
|
||||
: configured_bitrate_(0), layers_(layers) {}
|
||||
|
||||
virtual ~SpyingTemporalLayers() { delete layers_; }
|
||||
|
||||
int EncodeFlags(uint32_t timestamp) override {
|
||||
return layers_->EncodeFlags(timestamp);
|
||||
}
|
||||
|
||||
bool ConfigureBitrates(int bitrate_kbit,
|
||||
int max_bitrate_kbit,
|
||||
int framerate,
|
||||
vpx_codec_enc_cfg_t* cfg) override {
|
||||
configured_bitrate_ = bitrate_kbit;
|
||||
return layers_->ConfigureBitrates(bitrate_kbit, max_bitrate_kbit,
|
||||
framerate, cfg);
|
||||
}
|
||||
|
||||
void PopulateCodecSpecific(bool base_layer_sync,
|
||||
CodecSpecificInfoVP8* vp8_info,
|
||||
uint32_t timestamp) override {
|
||||
layers_->PopulateCodecSpecific(base_layer_sync, vp8_info, timestamp);
|
||||
}
|
||||
|
||||
void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) override {
|
||||
layers_->FrameEncoded(size, timestamp, qp);
|
||||
}
|
||||
|
||||
int CurrentLayerId() const override { return layers_->CurrentLayerId(); }
|
||||
|
||||
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
int configured_bitrate_;
|
||||
TemporalLayers* layers_;
|
||||
};
|
||||
|
||||
class SpyingTemporalLayersFactory : public TemporalLayersFactory {
|
||||
public:
|
||||
virtual ~SpyingTemporalLayersFactory() {}
|
||||
TemporalLayers* Create(int temporal_layers,
|
||||
uint8_t initial_tl0_pic_idx) const override {
|
||||
SpyingTemporalLayers* layers =
|
||||
new SpyingTemporalLayers(TemporalLayersFactory::Create(
|
||||
temporal_layers, initial_tl0_pic_idx));
|
||||
spying_layers_.push_back(layers);
|
||||
return layers;
|
||||
}
|
||||
|
||||
mutable std::vector<TemporalLayers*> spying_layers_;
|
||||
};
|
||||
};
|
||||
|
||||
class TestVp8Simulcast : public ::testing::Test {
|
||||
public:
|
||||
TestVp8Simulcast(VP8Encoder* encoder, VP8Decoder* decoder)
|
||||
@ -265,7 +190,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
static void DefaultSettings(VideoCodec* settings,
|
||||
const int* temporal_layer_profile) {
|
||||
assert(settings);
|
||||
RTC_CHECK(settings);
|
||||
memset(settings, 0, sizeof(VideoCodec));
|
||||
strncpy(settings->plName, "VP8", 4);
|
||||
settings->codecType = kVideoCodecVP8;
|
||||
@ -315,12 +240,18 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() { SetUpCodec(kDefaultTemporalLayerProfile); }
|
||||
void SetUp() override { SetUpCodec(kDefaultTemporalLayerProfile); }
|
||||
|
||||
virtual void SetUpCodec(const int* temporal_layer_profile) {
|
||||
void TearDown() override {
|
||||
encoder_->Release();
|
||||
decoder_->Release();
|
||||
}
|
||||
|
||||
void SetUpCodec(const int* temporal_layer_profile) {
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback_);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoder_callback_);
|
||||
DefaultSettings(&settings_, temporal_layer_profile);
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
EXPECT_EQ(0, decoder_->InitDecode(&settings_, 1));
|
||||
int half_width = (kDefaultWidth + 1) / 2;
|
||||
@ -331,9 +262,16 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
new VideoFrame(input_buffer_, 0, 0, webrtc::kVideoRotation_0));
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
encoder_->Release();
|
||||
decoder_->Release();
|
||||
void SetUpRateAllocator() {
|
||||
TemporalLayersFactory* tl_factory = new TemporalLayersFactory();
|
||||
rate_allocator_.reset(new SimulcastRateAllocator(
|
||||
settings_, std::unique_ptr<TemporalLayersFactory>(tl_factory)));
|
||||
settings_.codecSpecific.VP8.tl_factory = tl_factory;
|
||||
}
|
||||
|
||||
void SetRates(uint32_t bitrate_kbps, uint32_t fps) {
|
||||
encoder_->SetRateAllocation(
|
||||
rate_allocator_->GetAllocation(bitrate_kbps * 1000, fps), fps);
|
||||
}
|
||||
|
||||
void ExpectStreams(FrameType frame_type, int expected_video_streams) {
|
||||
@ -396,7 +334,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
// We currently expect all active streams to generate a key frame even though
|
||||
// a key frame was only requested for some of them.
|
||||
void TestKeyFrameRequestsOnAllStreams() {
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, kNumberOfSimulcastStreams);
|
||||
@ -431,7 +369,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
void TestPaddingAllStreams() {
|
||||
// We should always encode the base layer.
|
||||
encoder_->SetRates(kMinBitrates[0] - 1, 30);
|
||||
SetRates(kMinBitrates[0] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
@ -444,7 +382,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
void TestPaddingTwoStreams() {
|
||||
// We have just enough to get only the first stream and padding for two.
|
||||
encoder_->SetRates(kMinBitrates[0], 30);
|
||||
SetRates(kMinBitrates[0], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
@ -458,7 +396,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
void 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.
|
||||
encoder_->SetRates(kTargetBitrates[0] + kMinBitrates[1] - 1, 30);
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
@ -471,7 +409,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
void TestPaddingOneStream() {
|
||||
// We have just enough to send two streams, so padding for one stream.
|
||||
encoder_->SetRates(kTargetBitrates[0] + kMinBitrates[1], 30);
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
@ -485,8 +423,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
void 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|.
|
||||
encoder_->SetRates(
|
||||
kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] - 1, 30);
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] - 1, 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
@ -499,8 +436,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
void TestSendAllStreams() {
|
||||
// We have just enough to send all streams.
|
||||
encoder_->SetRates(
|
||||
kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2], 30);
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
@ -513,7 +449,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
|
||||
void TestDisablingStreams() {
|
||||
// We should get three media streams.
|
||||
encoder_->SetRates(kMaxBitrates[0] + kMaxBitrates[1] + kMaxBitrates[2], 30);
|
||||
SetRates(kMaxBitrates[0] + kMaxBitrates[1] + kMaxBitrates[2], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
@ -524,36 +460,33 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get two streams and padding for one.
|
||||
encoder_->SetRates(
|
||||
kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get the first stream and padding for two.
|
||||
encoder_->SetRates(kTargetBitrates[0] + kMinBitrates[1] / 2, 30);
|
||||
SetRates(kTargetBitrates[0] + kMinBitrates[1] / 2, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We don't have enough bitrate for the thumbnail stream, but we should get
|
||||
// it anyway with current configuration.
|
||||
encoder_->SetRates(kTargetBitrates[0] - 1, 30);
|
||||
SetRates(kTargetBitrates[0] - 1, 30);
|
||||
ExpectStreams(kVideoFrameDelta, 1);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should only get two streams and padding for one.
|
||||
encoder_->SetRates(
|
||||
kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kMinBitrates[2] / 2, 30);
|
||||
// We get a key frame because a new stream is being enabled.
|
||||
ExpectStreams(kVideoFrameKey, 2);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, &frame_types));
|
||||
|
||||
// We should get all three streams.
|
||||
encoder_->SetRates(
|
||||
kTargetBitrates[0] + kTargetBitrates[1] + kTargetBitrates[2], 30);
|
||||
SetRates(kTargetBitrates[0] + kTargetBitrates[1] + kTargetBitrates[2], 30);
|
||||
// We get a key frame because a new stream is being enabled.
|
||||
ExpectStreams(kVideoFrameKey, 3);
|
||||
input_frame_->set_timestamp(input_frame_->timestamp() + 3000);
|
||||
@ -590,10 +523,11 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
settings_.width;
|
||||
settings_.simulcastStream[settings_.numberOfSimulcastStreams - 1].height =
|
||||
settings_.height;
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
|
||||
// Encode one frame and verify.
|
||||
encoder_->SetRates(kMaxBitrates[0] + kMaxBitrates[1], 30);
|
||||
SetRates(kMaxBitrates[0] + kMaxBitrates[1], 30);
|
||||
std::vector<FrameType> frame_types(kNumberOfSimulcastStreams,
|
||||
kVideoFrameDelta);
|
||||
EXPECT_CALL(
|
||||
@ -611,8 +545,9 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
DefaultSettings(&settings_, kDefaultTemporalLayerProfile);
|
||||
// Start at the lowest bitrate for enabling base stream.
|
||||
settings_.startBitrate = kMinBitrates[0];
|
||||
SetUpRateAllocator();
|
||||
EXPECT_EQ(0, encoder_->InitEncode(&settings_, 1, 1200));
|
||||
encoder_->SetRates(settings_.startBitrate, 30);
|
||||
SetRates(settings_.startBitrate, 30);
|
||||
ExpectStreams(kVideoFrameKey, 1);
|
||||
// Resize |input_frame_| to the new resolution.
|
||||
half_width = (settings_.width + 1) / 2;
|
||||
@ -634,7 +569,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
|
||||
EXPECT_EQ(0, encoder_->Encode(*input_frame_, NULL, NULL));
|
||||
int picture_id = -1;
|
||||
@ -703,7 +638,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoder_callback);
|
||||
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
|
||||
// Set color.
|
||||
int plane_offset[kNumOfPlanes];
|
||||
@ -777,7 +712,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
void TestSaptioTemporalLayers333PatternEncoder() {
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
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};
|
||||
@ -846,7 +781,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
SetUpCodec(temporal_layer_profile);
|
||||
Vp8TestEncodedImageCallback encoder_callback;
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
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};
|
||||
@ -905,7 +840,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
encoder_->RegisterEncodeCompleteCallback(&encoder_callback);
|
||||
decoder_->RegisterDecodeCompleteCallback(&decoder_callback);
|
||||
|
||||
encoder_->SetRates(kMaxBitrates[2], 30); // To get all three streams.
|
||||
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;
|
||||
@ -941,30 +876,6 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
EXPECT_EQ(2, decoder_callback.DecodedFrames());
|
||||
}
|
||||
|
||||
void TestSkipEncodingUnusedStreams() {
|
||||
SkipEncodingUnusedStreamsTest test;
|
||||
std::vector<unsigned int> configured_bitrate =
|
||||
test.RunTest(encoder_.get(), &settings_,
|
||||
1); // Target bit rate 1, to force all streams but the
|
||||
// base one to be exceeding bandwidth constraints.
|
||||
EXPECT_EQ(static_cast<size_t>(kNumberOfSimulcastStreams),
|
||||
configured_bitrate.size());
|
||||
|
||||
unsigned int min_bitrate =
|
||||
std::max(settings_.simulcastStream[0].minBitrate, settings_.minBitrate);
|
||||
int stream = 0;
|
||||
for (std::vector<unsigned int>::const_iterator it =
|
||||
configured_bitrate.begin();
|
||||
it != configured_bitrate.end(); ++it) {
|
||||
if (stream == 0) {
|
||||
EXPECT_EQ(min_bitrate, *it);
|
||||
} else {
|
||||
EXPECT_EQ(0u, *it);
|
||||
}
|
||||
++stream;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<VP8Encoder> encoder_;
|
||||
MockEncodedImageCallback encoder_callback_;
|
||||
std::unique_ptr<VP8Decoder> decoder_;
|
||||
@ -972,6 +883,7 @@ class TestVp8Simulcast : public ::testing::Test {
|
||||
VideoCodec settings_;
|
||||
rtc::scoped_refptr<I420Buffer> input_buffer_;
|
||||
std::unique_ptr<VideoFrame> input_frame_;
|
||||
std::unique_ptr<SimulcastRateAllocator> rate_allocator_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
||||
Reference in New Issue
Block a user