In screenshare mode, suppress VP8 bitrate overshoot and increase quality
This change includes several improvements: * VP8 configured with new rate control * Detection of frame dropping, with qp bump for next frame * Increased target and TL0 bitrates * Reworked rate control (TL allocation) in screenshare_layers A note on performance: PSNR and SSIM is expected to get slightly worse with this cl. Frame drops and delays should however improve. BUG=4171 R=pbos@webrtc.org, stefan@webrtc.org Review URL: https://codereview.webrtc.org/1193513006. Cr-Commit-Position: refs/heads/master@{#9495}
This commit is contained in:
@ -411,7 +411,7 @@ void LogSimulcastSubstreams(const webrtc::VideoCodec& codec) {
|
|||||||
|
|
||||||
static const int kScreenshareMinBitrateKbps = 50;
|
static const int kScreenshareMinBitrateKbps = 50;
|
||||||
static const int kScreenshareMaxBitrateKbps = 6000;
|
static const int kScreenshareMaxBitrateKbps = 6000;
|
||||||
static const int kScreenshareDefaultTl0BitrateKbps = 100;
|
static const int kScreenshareDefaultTl0BitrateKbps = 200;
|
||||||
static const int kScreenshareDefaultTl1BitrateKbps = 1000;
|
static const int kScreenshareDefaultTl1BitrateKbps = 1000;
|
||||||
|
|
||||||
static const char* kScreencastLayerFieldTrialName =
|
static const char* kScreencastLayerFieldTrialName =
|
||||||
|
@ -1438,7 +1438,8 @@ TEST_F(WebRtcVideoChannel2Test, UsesCorrectSettingsForScreencast) {
|
|||||||
|
|
||||||
TEST_F(WebRtcVideoChannel2Test,
|
TEST_F(WebRtcVideoChannel2Test,
|
||||||
ConferenceModeScreencastConfiguresTemporalLayer) {
|
ConferenceModeScreencastConfiguresTemporalLayer) {
|
||||||
static const int kConferenceScreencastTemporalBitrateBps = 100000;
|
static const int kConferenceScreencastTemporalBitrateBps =
|
||||||
|
ScreenshareLayerConfig::GetDefault().tl0_bitrate_kbps * 1000;
|
||||||
VideoOptions options;
|
VideoOptions options;
|
||||||
options.conference_mode.Set(true);
|
options.conference_mode.Set(true);
|
||||||
channel_->SetOptions(options);
|
channel_->SetOptions(options);
|
||||||
|
@ -27,5 +27,6 @@
|
|||||||
#define WEBRTC_VIDEO_CODEC_UNINITIALIZED -7
|
#define WEBRTC_VIDEO_CODEC_UNINITIALIZED -7
|
||||||
#define WEBRTC_VIDEO_CODEC_ERR_REQUEST_SLI -12
|
#define WEBRTC_VIDEO_CODEC_ERR_REQUEST_SLI -12
|
||||||
#define WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE -13
|
#define WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE -13
|
||||||
|
#define WEBRTC_VIDEO_CODEC_TARGET_BITRATE_OVERSHOOT -14
|
||||||
|
|
||||||
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_ERROR_CODES_H
|
#endif // WEBRTC_MODULES_VIDEO_CODING_CODECS_INTERFACE_VIDEO_ERROR_CODES_H
|
||||||
|
@ -24,20 +24,22 @@ class DefaultTemporalLayers : public TemporalLayers {
|
|||||||
|
|
||||||
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
||||||
// and/or update the reference buffers.
|
// and/or update the reference buffers.
|
||||||
virtual int EncodeFlags(uint32_t timestamp);
|
int EncodeFlags(uint32_t timestamp) override;
|
||||||
|
|
||||||
virtual bool ConfigureBitrates(int bitrate_kbit,
|
bool ConfigureBitrates(int bitrate_kbit,
|
||||||
int max_bitrate_kbit,
|
int max_bitrate_kbit,
|
||||||
int framerate,
|
int framerate,
|
||||||
vpx_codec_enc_cfg_t* cfg);
|
vpx_codec_enc_cfg_t* cfg) override;
|
||||||
|
|
||||||
virtual void PopulateCodecSpecific(bool base_layer_sync,
|
void PopulateCodecSpecific(bool base_layer_sync,
|
||||||
CodecSpecificInfoVP8* vp8_info,
|
CodecSpecificInfoVP8* vp8_info,
|
||||||
uint32_t timestamp);
|
uint32_t timestamp) override;
|
||||||
|
|
||||||
virtual void FrameEncoded(unsigned int size, uint32_t timestamp) {}
|
void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) override {}
|
||||||
|
|
||||||
virtual int CurrentLayerId() const;
|
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override { return false; }
|
||||||
|
|
||||||
|
int CurrentLayerId() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum TemporalReferences {
|
enum TemporalReferences {
|
||||||
|
@ -239,7 +239,9 @@ class RealTimeTemporalLayers : public TemporalLayers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameEncoded(unsigned int size, uint32_t timestamp) {}
|
void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) override {}
|
||||||
|
|
||||||
|
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override { return false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int temporal_layers_;
|
int temporal_layers_;
|
||||||
|
@ -11,32 +11,52 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "webrtc/base/checks.h"
|
||||||
#include "vpx/vpx_encoder.h"
|
#include "vpx/vpx_encoder.h"
|
||||||
#include "vpx/vp8cx.h"
|
#include "vpx/vp8cx.h"
|
||||||
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
enum { kOneSecond90Khz = 90000 };
|
static const int kOneSecond90Khz = 90000;
|
||||||
|
static const int kMinTimeBetweenSyncs = kOneSecond90Khz * 5;
|
||||||
|
static const int kMaxTimeBetweenSyncs = kOneSecond90Khz * 10;
|
||||||
|
static const int kQpDeltaThresholdForSync = 8;
|
||||||
|
|
||||||
const double ScreenshareLayers::kMaxTL0FpsReduction = 2.5;
|
const double ScreenshareLayers::kMaxTL0FpsReduction = 2.5;
|
||||||
const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0;
|
const double ScreenshareLayers::kAcceptableTargetOvershoot = 2.0;
|
||||||
|
|
||||||
|
// Since this is TL0 we only allow updating and predicting from the LAST
|
||||||
|
// reference frame.
|
||||||
|
const int ScreenshareLayers::kTl0Flags =
|
||||||
|
VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF |
|
||||||
|
VP8_EFLAG_NO_REF_ARF;
|
||||||
|
|
||||||
|
// Allow predicting from both TL0 and TL1.
|
||||||
|
const int ScreenshareLayers::kTl1Flags =
|
||||||
|
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;
|
||||||
|
|
||||||
|
// Allow predicting from only TL0 to allow participants to switch to the high
|
||||||
|
// bitrate stream. This means predicting only from the LAST reference frame, but
|
||||||
|
// only updating GF to not corrupt TL0.
|
||||||
|
const int ScreenshareLayers::kTl1SyncFlags =
|
||||||
|
VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_ARF |
|
||||||
|
VP8_EFLAG_NO_UPD_LAST;
|
||||||
|
|
||||||
ScreenshareLayers::ScreenshareLayers(int num_temporal_layers,
|
ScreenshareLayers::ScreenshareLayers(int num_temporal_layers,
|
||||||
uint8_t initial_tl0_pic_idx,
|
uint8_t initial_tl0_pic_idx)
|
||||||
FrameDropper* tl0_frame_dropper,
|
: number_of_temporal_layers_(num_temporal_layers),
|
||||||
FrameDropper* tl1_frame_dropper)
|
|
||||||
: tl0_frame_dropper_(tl0_frame_dropper),
|
|
||||||
tl1_frame_dropper_(tl1_frame_dropper),
|
|
||||||
number_of_temporal_layers_(num_temporal_layers),
|
|
||||||
last_base_layer_sync_(false),
|
last_base_layer_sync_(false),
|
||||||
tl0_pic_idx_(initial_tl0_pic_idx),
|
tl0_pic_idx_(initial_tl0_pic_idx),
|
||||||
active_layer_(0),
|
active_layer_(-1),
|
||||||
framerate_(5),
|
last_timestamp_(-1),
|
||||||
last_sync_timestamp_(-1) {
|
last_sync_timestamp_(-1),
|
||||||
|
min_qp_(-1),
|
||||||
|
max_qp_(-1),
|
||||||
|
max_debt_bytes_(0),
|
||||||
|
frame_rate_(-1) {
|
||||||
assert(num_temporal_layers > 0);
|
assert(num_temporal_layers > 0);
|
||||||
assert(num_temporal_layers <= 2);
|
assert(num_temporal_layers <= 2);
|
||||||
assert(tl0_frame_dropper && tl1_frame_dropper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenshareLayers::CurrentLayerId() const {
|
int ScreenshareLayers::CurrentLayerId() const {
|
||||||
@ -49,84 +69,125 @@ int ScreenshareLayers::EncodeFlags(uint32_t timestamp) {
|
|||||||
// No flags needed for 1 layer screenshare.
|
// No flags needed for 1 layer screenshare.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
CalculateFramerate(timestamp);
|
|
||||||
|
int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp);
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
// Note that ARF on purpose isn't used in this scheme since it is allocated
|
|
||||||
// for the last key frame to make key frame caching possible.
|
if (active_layer_ == -1 ||
|
||||||
if (tl0_frame_dropper_->DropFrame()) {
|
layers_[active_layer_].state != TemporalLayer::State::kDropped) {
|
||||||
// Must drop TL0, encode TL1 instead.
|
if (layers_[0].debt_bytes_ > max_debt_bytes_) {
|
||||||
if (tl1_frame_dropper_->DropFrame()) {
|
// Must drop TL0, encode TL1 instead.
|
||||||
// Must drop both TL0 and TL1.
|
if (layers_[1].debt_bytes_ > max_debt_bytes_) {
|
||||||
flags = -1;
|
// Must drop both TL0 and TL1.
|
||||||
} else {
|
active_layer_ = -1;
|
||||||
active_layer_ = 1;
|
|
||||||
if (TimeToSync(timestamp)) {
|
|
||||||
last_sync_timestamp_ = timestamp;
|
|
||||||
// Allow predicting from only TL0 to allow participants to switch to the
|
|
||||||
// high bitrate stream. This means predicting only from the LAST
|
|
||||||
// reference frame, but only updating GF to not corrupt TL0.
|
|
||||||
flags = VP8_EFLAG_NO_REF_ARF;
|
|
||||||
flags |= VP8_EFLAG_NO_REF_GF;
|
|
||||||
flags |= VP8_EFLAG_NO_UPD_ARF;
|
|
||||||
flags |= VP8_EFLAG_NO_UPD_LAST;
|
|
||||||
} else {
|
} else {
|
||||||
// Allow predicting from both TL0 and TL1.
|
active_layer_ = 1;
|
||||||
flags = VP8_EFLAG_NO_REF_ARF;
|
|
||||||
flags |= VP8_EFLAG_NO_UPD_ARF;
|
|
||||||
flags |= VP8_EFLAG_NO_UPD_LAST;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
active_layer_ = 0;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
active_layer_ = 0;
|
|
||||||
// Since this is TL0 we only allow updating and predicting from the LAST
|
|
||||||
// reference frame.
|
|
||||||
flags = VP8_EFLAG_NO_UPD_GF;
|
|
||||||
flags |= VP8_EFLAG_NO_UPD_ARF;
|
|
||||||
flags |= VP8_EFLAG_NO_REF_GF;
|
|
||||||
flags |= VP8_EFLAG_NO_REF_ARF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (active_layer_) {
|
||||||
|
case 0:
|
||||||
|
flags = kTl0Flags;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (TimeToSync(unwrapped_timestamp)) {
|
||||||
|
last_sync_timestamp_ = unwrapped_timestamp;
|
||||||
|
flags = kTl1SyncFlags;
|
||||||
|
} else {
|
||||||
|
flags = kTl1Flags;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
flags = -1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
flags = -1;
|
||||||
|
RTC_NOTREACHED();
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure both frame droppers leak out bits.
|
// Make sure both frame droppers leak out bits.
|
||||||
tl0_frame_dropper_->Leak(framerate_);
|
int64_t ts_diff;
|
||||||
tl1_frame_dropper_->Leak(framerate_);
|
if (last_timestamp_ == -1) {
|
||||||
|
ts_diff = kOneSecond90Khz / (frame_rate_ <= 0 ? 5 : frame_rate_);
|
||||||
|
} else {
|
||||||
|
ts_diff = unwrapped_timestamp - last_timestamp_;
|
||||||
|
}
|
||||||
|
|
||||||
|
layers_[0].UpdateDebt(ts_diff / 90);
|
||||||
|
layers_[1].UpdateDebt(ts_diff / 90);
|
||||||
|
last_timestamp_ = timestamp;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScreenshareLayers::ConfigureBitrates(int bitrate_kbit,
|
bool ScreenshareLayers::ConfigureBitrates(int bitrate_kbps,
|
||||||
int max_bitrate_kbit,
|
int max_bitrate_kbps,
|
||||||
int framerate,
|
int framerate,
|
||||||
vpx_codec_enc_cfg_t* cfg) {
|
vpx_codec_enc_cfg_t* cfg) {
|
||||||
if (framerate > 0)
|
layers_[0].target_rate_kbps_ = bitrate_kbps;
|
||||||
framerate_ = framerate;
|
layers_[1].target_rate_kbps_ = max_bitrate_kbps;
|
||||||
|
|
||||||
tl0_frame_dropper_->SetRates(bitrate_kbit, framerate_);
|
int target_bitrate_kbps = bitrate_kbps;
|
||||||
tl1_frame_dropper_->SetRates(max_bitrate_kbit, framerate_);
|
|
||||||
|
|
||||||
if (cfg != nullptr) {
|
if (cfg != nullptr) {
|
||||||
// Calculate a codec target bitrate. This may be higher than TL0, gaining
|
// Calculate a codec target bitrate. This may be higher than TL0, gaining
|
||||||
// quality at the expense of frame rate at TL0. Constraints:
|
// quality at the expense of frame rate at TL0. Constraints:
|
||||||
// - TL0 frame rate should not be less than framerate / kMaxTL0FpsReduction.
|
// - TL0 frame rate should not be less than framerate / kMaxTL0FpsReduction.
|
||||||
// - Target rate * kAcceptableTargetOvershoot should not exceed TL1 rate.
|
// - Target rate * kAcceptableTargetOvershoot should not exceed TL1 rate.
|
||||||
double target_bitrate =
|
target_bitrate_kbps =
|
||||||
std::min(bitrate_kbit * kMaxTL0FpsReduction,
|
std::min(bitrate_kbps * kMaxTL0FpsReduction,
|
||||||
max_bitrate_kbit / kAcceptableTargetOvershoot);
|
max_bitrate_kbps / kAcceptableTargetOvershoot);
|
||||||
cfg->rc_target_bitrate =
|
|
||||||
std::max(static_cast<unsigned int>(bitrate_kbit),
|
// Don't reconfigure qp limits during quality boost frames.
|
||||||
static_cast<unsigned int>(target_bitrate + 0.5));
|
if (layers_[active_layer_].state != TemporalLayer::State::kQualityBoost) {
|
||||||
|
min_qp_ = cfg->rc_min_quantizer;
|
||||||
|
max_qp_ = cfg->rc_max_quantizer;
|
||||||
|
// After a dropped frame, a frame with max qp will be encoded and the
|
||||||
|
// quality will then ramp up from there. To boost the speed of recovery,
|
||||||
|
// encode the next frame with lower max qp. TL0 is the most important to
|
||||||
|
// improve since the errors in this layer will propagate to TL1.
|
||||||
|
// Currently, reduce max qp by 20% for TL0 and 15% for TL1.
|
||||||
|
layers_[0].enhanced_max_qp = min_qp_ + (((max_qp_ - min_qp_) * 80) / 100);
|
||||||
|
layers_[1].enhanced_max_qp = min_qp_ + (((max_qp_ - min_qp_) * 85) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg->rc_target_bitrate = std::max(bitrate_kbps, target_bitrate_kbps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int avg_frame_size = (target_bitrate_kbps * 1000) / (8 * framerate);
|
||||||
|
max_debt_bytes_ = 4 * avg_frame_size;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenshareLayers::FrameEncoded(unsigned int size, uint32_t timestamp) {
|
void ScreenshareLayers::FrameEncoded(unsigned int size,
|
||||||
if (active_layer_ == 0) {
|
uint32_t timestamp,
|
||||||
tl0_frame_dropper_->Fill(size, true);
|
int qp) {
|
||||||
|
if (size == 0) {
|
||||||
|
layers_[active_layer_].state = TemporalLayer::State::kDropped;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (layers_[active_layer_].state == TemporalLayer::State::kDropped) {
|
||||||
|
layers_[active_layer_].state = TemporalLayer::State::kQualityBoost;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp != -1)
|
||||||
|
layers_[active_layer_].last_qp = qp;
|
||||||
|
|
||||||
|
if (active_layer_ == 0) {
|
||||||
|
layers_[0].debt_bytes_ += size;
|
||||||
|
layers_[1].debt_bytes_ += size;
|
||||||
|
} else if (active_layer_ == 1) {
|
||||||
|
layers_[1].debt_bytes_ += size;
|
||||||
}
|
}
|
||||||
tl1_frame_dropper_->Fill(size, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync,
|
void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync,
|
||||||
CodecSpecificInfoVP8 *vp8_info,
|
CodecSpecificInfoVP8 *vp8_info,
|
||||||
uint32_t timestamp) {
|
uint32_t timestamp) {
|
||||||
|
int64_t unwrapped_timestamp = time_wrap_handler_.Unwrap(timestamp);
|
||||||
if (number_of_temporal_layers_ == 1) {
|
if (number_of_temporal_layers_ == 1) {
|
||||||
vp8_info->temporalIdx = kNoTemporalIdx;
|
vp8_info->temporalIdx = kNoTemporalIdx;
|
||||||
vp8_info->layerSync = false;
|
vp8_info->layerSync = false;
|
||||||
@ -135,13 +196,14 @@ void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync,
|
|||||||
vp8_info->temporalIdx = active_layer_;
|
vp8_info->temporalIdx = active_layer_;
|
||||||
if (base_layer_sync) {
|
if (base_layer_sync) {
|
||||||
vp8_info->temporalIdx = 0;
|
vp8_info->temporalIdx = 0;
|
||||||
last_sync_timestamp_ = timestamp;
|
last_sync_timestamp_ = unwrapped_timestamp;
|
||||||
} else if (last_base_layer_sync_ && vp8_info->temporalIdx != 0) {
|
} else if (last_base_layer_sync_ && vp8_info->temporalIdx != 0) {
|
||||||
// Regardless of pattern the frame after a base layer sync will always
|
// Regardless of pattern the frame after a base layer sync will always
|
||||||
// be a layer sync.
|
// be a layer sync.
|
||||||
last_sync_timestamp_ = timestamp;
|
last_sync_timestamp_ = unwrapped_timestamp;
|
||||||
}
|
}
|
||||||
vp8_info->layerSync = (last_sync_timestamp_ == timestamp);
|
vp8_info->layerSync = last_sync_timestamp_ != -1 &&
|
||||||
|
last_sync_timestamp_ == unwrapped_timestamp;
|
||||||
if (vp8_info->temporalIdx == 0) {
|
if (vp8_info->temporalIdx == 0) {
|
||||||
tl0_pic_idx_++;
|
tl0_pic_idx_++;
|
||||||
}
|
}
|
||||||
@ -150,27 +212,65 @@ void ScreenshareLayers::PopulateCodecSpecific(bool base_layer_sync,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScreenshareLayers::TimeToSync(uint32_t timestamp) const {
|
bool ScreenshareLayers::TimeToSync(int64_t timestamp) const {
|
||||||
const uint32_t timestamp_diff = timestamp - last_sync_timestamp_;
|
if (active_layer_ != 1) {
|
||||||
return last_sync_timestamp_ < 0 || timestamp_diff > kOneSecond90Khz;
|
RTC_NOTREACHED();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DCHECK_NE(-1, layers_[0].last_qp);
|
||||||
|
if (layers_[1].last_qp == -1) {
|
||||||
|
// First frame in TL1 should only depend on TL0 since there are no
|
||||||
|
// previous frames in TL1.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_NE(-1, last_sync_timestamp_);
|
||||||
|
int64_t timestamp_diff = timestamp - last_sync_timestamp_;
|
||||||
|
if (timestamp_diff > kMaxTimeBetweenSyncs) {
|
||||||
|
// After a certain time, force a sync frame.
|
||||||
|
return true;
|
||||||
|
} else if (timestamp_diff < kMinTimeBetweenSyncs) {
|
||||||
|
// If too soon from previous sync frame, don't issue a new one.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Issue a sync frame if difference in quality between TL0 and TL1 isn't too
|
||||||
|
// large.
|
||||||
|
if (layers_[0].last_qp - layers_[1].last_qp < kQpDeltaThresholdForSync)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenshareLayers::CalculateFramerate(uint32_t timestamp) {
|
bool ScreenshareLayers::UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) {
|
||||||
timestamp_list_.push_front(timestamp);
|
if (max_qp_ == -1)
|
||||||
// Remove timestamps older than 1 second from the list.
|
return false;
|
||||||
uint32_t timestamp_diff = timestamp - timestamp_list_.back();
|
|
||||||
while (timestamp_diff > kOneSecond90Khz) {
|
// If layer is in the quality boost state (following a dropped frame), update
|
||||||
timestamp_list_.pop_back();
|
// the configuration with the adjusted (lower) qp and set the state back to
|
||||||
timestamp_diff = timestamp - timestamp_list_.back();
|
// normal.
|
||||||
|
unsigned int adjusted_max_qp;
|
||||||
|
if (layers_[active_layer_].state == TemporalLayer::State::kQualityBoost &&
|
||||||
|
layers_[active_layer_].enhanced_max_qp != -1) {
|
||||||
|
adjusted_max_qp = layers_[active_layer_].enhanced_max_qp;
|
||||||
|
layers_[active_layer_].state = TemporalLayer::State::kNormal;
|
||||||
|
} else {
|
||||||
|
if (max_qp_ == -1)
|
||||||
|
return false;
|
||||||
|
adjusted_max_qp = max_qp_; // Set the normal max qp.
|
||||||
}
|
}
|
||||||
// If we have encoded frames within the last second, that number of frames
|
|
||||||
// is a reasonable first estimate of the framerate.
|
if (adjusted_max_qp == cfg->rc_max_quantizer)
|
||||||
framerate_ = timestamp_list_.size();
|
return false;
|
||||||
if (timestamp_diff > 0) {
|
|
||||||
// Estimate the framerate by dividing the number of timestamp diffs with
|
cfg->rc_max_quantizer = adjusted_max_qp;
|
||||||
// the sum of the timestamp diffs (with rounding).
|
return true;
|
||||||
framerate_ = (kOneSecond90Khz * (timestamp_list_.size() - 1) +
|
}
|
||||||
timestamp_diff / 2) / timestamp_diff;
|
|
||||||
|
void ScreenshareLayers::TemporalLayer::UpdateDebt(int64_t delta_ms) {
|
||||||
|
uint32_t debt_reduction_bytes = target_rate_kbps_ * delta_ms / 8;
|
||||||
|
if (debt_reduction_bytes >= debt_bytes_) {
|
||||||
|
debt_bytes_ = 0;
|
||||||
|
} else {
|
||||||
|
debt_bytes_ -= debt_reduction_bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "vpx/vpx_encoder.h"
|
#include "vpx/vpx_encoder.h"
|
||||||
|
|
||||||
|
#include "webrtc/base/timeutils.h"
|
||||||
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
|
||||||
#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
|
#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
|
||||||
#include "webrtc/typedefs.h"
|
#include "webrtc/typedefs.h"
|
||||||
@ -25,43 +26,73 @@ class ScreenshareLayers : public TemporalLayers {
|
|||||||
public:
|
public:
|
||||||
static const double kMaxTL0FpsReduction;
|
static const double kMaxTL0FpsReduction;
|
||||||
static const double kAcceptableTargetOvershoot;
|
static const double kAcceptableTargetOvershoot;
|
||||||
|
static const int kTl0Flags;
|
||||||
|
static const int kTl1Flags;
|
||||||
|
static const int kTl1SyncFlags;
|
||||||
|
|
||||||
ScreenshareLayers(int num_temporal_layers,
|
ScreenshareLayers(int num_temporal_layers, uint8_t initial_tl0_pic_idx);
|
||||||
uint8_t initial_tl0_pic_idx,
|
|
||||||
FrameDropper* tl0_frame_dropper,
|
|
||||||
FrameDropper* tl1_frame_dropper);
|
|
||||||
virtual ~ScreenshareLayers() {}
|
virtual ~ScreenshareLayers() {}
|
||||||
|
|
||||||
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
// Returns the recommended VP8 encode flags needed. May refresh the decoder
|
||||||
// and/or update the reference buffers.
|
// and/or update the reference buffers.
|
||||||
virtual int EncodeFlags(uint32_t timestamp);
|
int EncodeFlags(uint32_t timestamp) override;
|
||||||
|
|
||||||
virtual bool ConfigureBitrates(int bitrate_kbit,
|
bool ConfigureBitrates(int bitrate_kbps,
|
||||||
int max_bitrate_kbit,
|
int max_bitrate_kbps,
|
||||||
int framerate,
|
int framerate,
|
||||||
vpx_codec_enc_cfg_t* cfg);
|
vpx_codec_enc_cfg_t* cfg) override;
|
||||||
|
|
||||||
virtual void PopulateCodecSpecific(bool base_layer_sync,
|
void PopulateCodecSpecific(bool base_layer_sync,
|
||||||
CodecSpecificInfoVP8 *vp8_info,
|
CodecSpecificInfoVP8* vp8_info,
|
||||||
uint32_t timestamp);
|
uint32_t timestamp) override;
|
||||||
|
|
||||||
virtual void FrameEncoded(unsigned int size, uint32_t timestamp);
|
void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) override;
|
||||||
|
|
||||||
virtual int CurrentLayerId() const;
|
int CurrentLayerId() const override;
|
||||||
|
|
||||||
|
// Allows the layers adapter to update the encoder configuration prior to a
|
||||||
|
// frame being encoded. Return true if the configuration should be updated
|
||||||
|
// and false if now change is needed.
|
||||||
|
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CalculateFramerate(uint32_t timestamp);
|
bool TimeToSync(int64_t timestamp) const;
|
||||||
bool TimeToSync(uint32_t timestamp) const;
|
|
||||||
|
|
||||||
FrameDropper* tl0_frame_dropper_;
|
|
||||||
FrameDropper* tl1_frame_dropper_;
|
|
||||||
int number_of_temporal_layers_;
|
int number_of_temporal_layers_;
|
||||||
bool last_base_layer_sync_;
|
bool last_base_layer_sync_;
|
||||||
uint8_t tl0_pic_idx_;
|
uint8_t tl0_pic_idx_;
|
||||||
int active_layer_;
|
int active_layer_;
|
||||||
std::list<uint32_t> timestamp_list_;
|
int64_t last_timestamp_;
|
||||||
int framerate_;
|
|
||||||
int64_t last_sync_timestamp_;
|
int64_t last_sync_timestamp_;
|
||||||
|
rtc::TimestampWrapAroundHandler time_wrap_handler_;
|
||||||
|
int min_qp_;
|
||||||
|
int max_qp_;
|
||||||
|
uint32_t max_debt_bytes_;
|
||||||
|
int frame_rate_;
|
||||||
|
|
||||||
|
static const int kMaxNumTemporalLayers = 2;
|
||||||
|
struct TemporalLayer {
|
||||||
|
TemporalLayer()
|
||||||
|
: state(State::kNormal),
|
||||||
|
enhanced_max_qp(-1),
|
||||||
|
last_qp(-1),
|
||||||
|
debt_bytes_(0),
|
||||||
|
target_rate_kbps_(0) {}
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
kNormal,
|
||||||
|
kDropped,
|
||||||
|
kReencoded,
|
||||||
|
kQualityBoost,
|
||||||
|
} state;
|
||||||
|
|
||||||
|
int enhanced_max_qp;
|
||||||
|
int last_qp;
|
||||||
|
uint32_t debt_bytes_;
|
||||||
|
uint32_t target_rate_kbps_;
|
||||||
|
|
||||||
|
void UpdateDebt(int64_t delta_ms);
|
||||||
|
} layers_[kMaxNumTemporalLayers];
|
||||||
};
|
};
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
||||||
|
@ -22,62 +22,19 @@ using ::testing::Return;
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
enum { kTimestampDelta5Fps = 90000 / 5 }; // 5 frames per second at 90 kHz.
|
// 5 frames per second at 90 kHz.
|
||||||
enum { kTimestampDelta30Fps = 90000 / 30 }; // 30 frames per second at 90 kHz.
|
const uint32_t kTimestampDelta5Fps = 90000 / 5;
|
||||||
enum { kFrameSize = 2500 };
|
const int kDefaultQp = 54;
|
||||||
|
const int kDefaultTl0BitrateKbps = 200;
|
||||||
const int kFlagsTL0 = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
|
const int kDefaultTl1BitrateKbps = 2000;
|
||||||
VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF;
|
const int kFrameRate = 5;
|
||||||
const int kFlagsTL1 = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF |
|
const int kSyncPeriodSeconds = 5;
|
||||||
VP8_EFLAG_NO_UPD_LAST;
|
const int kMaxSyncPeriodSeconds = 10;
|
||||||
const int kFlagsTL1Sync = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF |
|
|
||||||
VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;
|
|
||||||
|
|
||||||
class ScreenshareLayersFT : public ScreenshareLayers {
|
|
||||||
public:
|
|
||||||
ScreenshareLayersFT(int num_temporal_layers,
|
|
||||||
uint8_t initial_tl0_pic_idx,
|
|
||||||
FrameDropper* tl0_frame_dropper,
|
|
||||||
FrameDropper* tl1_frame_dropper)
|
|
||||||
: ScreenshareLayers(num_temporal_layers,
|
|
||||||
initial_tl0_pic_idx,
|
|
||||||
tl0_frame_dropper,
|
|
||||||
tl1_frame_dropper) {}
|
|
||||||
virtual ~ScreenshareLayersFT() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScreenshareLayerTest : public ::testing::Test {
|
class ScreenshareLayerTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
void SetEncodeExpectations(bool drop_tl0, bool drop_tl1, int framerate) {
|
ScreenshareLayerTest() : min_qp_(2), max_qp_(kDefaultQp), frame_size_(-1) {}
|
||||||
EXPECT_CALL(tl0_frame_dropper_, DropFrame())
|
virtual ~ScreenshareLayerTest() {}
|
||||||
.Times(1)
|
|
||||||
.WillRepeatedly(Return(drop_tl0));
|
|
||||||
if (drop_tl0) {
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, DropFrame())
|
|
||||||
.Times(1)
|
|
||||||
.WillRepeatedly(Return(drop_tl1));
|
|
||||||
}
|
|
||||||
EXPECT_CALL(tl0_frame_dropper_, Leak(framerate))
|
|
||||||
.Times(1);
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, Leak(framerate))
|
|
||||||
.Times(1);
|
|
||||||
if (drop_tl0) {
|
|
||||||
EXPECT_CALL(tl0_frame_dropper_, Fill(_, _))
|
|
||||||
.Times(0);
|
|
||||||
if (drop_tl1) {
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, Fill(_, _))
|
|
||||||
.Times(0);
|
|
||||||
} else {
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, Fill(kFrameSize, true))
|
|
||||||
.Times(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EXPECT_CALL(tl0_frame_dropper_, Fill(kFrameSize, true))
|
|
||||||
.Times(1);
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, Fill(kFrameSize, true))
|
|
||||||
.Times(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncodeFrame(uint32_t timestamp,
|
void EncodeFrame(uint32_t timestamp,
|
||||||
bool base_sync,
|
bool base_sync,
|
||||||
@ -85,39 +42,83 @@ class ScreenshareLayerTest : public ::testing::Test {
|
|||||||
int* flags) {
|
int* flags) {
|
||||||
*flags = layers_->EncodeFlags(timestamp);
|
*flags = layers_->EncodeFlags(timestamp);
|
||||||
layers_->PopulateCodecSpecific(base_sync, vp8_info, timestamp);
|
layers_->PopulateCodecSpecific(base_sync, vp8_info, timestamp);
|
||||||
layers_->FrameEncoded(kFrameSize, timestamp);
|
ASSERT_NE(-1, frame_size_);
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
}
|
}
|
||||||
|
|
||||||
NiceMock<MockFrameDropper> tl0_frame_dropper_;
|
void ConfigureBitrates() {
|
||||||
NiceMock<MockFrameDropper> tl1_frame_dropper_;
|
vpx_codec_enc_cfg_t vpx_cfg;
|
||||||
rtc::scoped_ptr<ScreenshareLayersFT> layers_;
|
memset(&vpx_cfg, 0, sizeof(vpx_codec_enc_cfg_t));
|
||||||
|
vpx_cfg.rc_min_quantizer = min_qp_;
|
||||||
|
vpx_cfg.rc_max_quantizer = max_qp_;
|
||||||
|
EXPECT_TRUE(layers_->ConfigureBitrates(
|
||||||
|
kDefaultTl0BitrateKbps, kDefaultTl1BitrateKbps, kFrameRate, &vpx_cfg));
|
||||||
|
frame_size_ = ((vpx_cfg.rc_target_bitrate * 1000) / 8) / kFrameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WithQpLimits(int min_qp, int max_qp) {
|
||||||
|
min_qp_ = min_qp;
|
||||||
|
max_qp_ = max_qp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunGracePeriod() {
|
||||||
|
int flags = 0;
|
||||||
|
uint32_t timestamp = 0;
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
bool got_tl0 = false;
|
||||||
|
bool got_tl1 = false;
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
if (vp8_info.temporalIdx == 0) {
|
||||||
|
got_tl0 = true;
|
||||||
|
} else {
|
||||||
|
got_tl1 = true;
|
||||||
|
}
|
||||||
|
if (got_tl0 && got_tl1)
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
ADD_FAILURE() << "Frames from both layers not received in time.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SkipUntilTl(int layer, int timestamp) {
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
layers_->EncodeFlags(timestamp);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
if (vp8_info.temporalIdx != layer) {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
} else {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ADD_FAILURE() << "Did not get a frame of TL" << layer << " in time.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min_qp_;
|
||||||
|
int max_qp_;
|
||||||
|
int frame_size_;
|
||||||
|
rtc::scoped_ptr<ScreenshareLayers> layers_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, 1Layer) {
|
TEST_F(ScreenshareLayerTest, 1Layer) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(1, 0));
|
||||||
new ScreenshareLayersFT(1, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
ConfigureBitrates();
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
uint32_t timestamp = 0;
|
uint32_t timestamp = 0;
|
||||||
CodecSpecificInfoVP8 vp8_info;
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
// One layer screenshare should not use the frame dropper as all frames will
|
// One layer screenshare should not use the frame dropper as all frames will
|
||||||
// belong to the base layer.
|
// belong to the base layer.
|
||||||
EXPECT_CALL(tl0_frame_dropper_, DropFrame())
|
|
||||||
.Times(0);
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, DropFrame())
|
|
||||||
.Times(0);
|
|
||||||
flags = layers_->EncodeFlags(timestamp);
|
flags = layers_->EncodeFlags(timestamp);
|
||||||
EXPECT_EQ(0, flags);
|
EXPECT_EQ(0, flags);
|
||||||
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
|
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
|
||||||
layers_->FrameEncoded(kFrameSize, timestamp);
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
EXPECT_CALL(tl0_frame_dropper_, DropFrame())
|
|
||||||
.Times(0);
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, DropFrame())
|
|
||||||
.Times(0);
|
|
||||||
flags = layers_->EncodeFlags(timestamp);
|
flags = layers_->EncodeFlags(timestamp);
|
||||||
EXPECT_EQ(0, flags);
|
EXPECT_EQ(0, flags);
|
||||||
timestamp += kTimestampDelta5Fps;
|
timestamp += kTimestampDelta5Fps;
|
||||||
@ -125,139 +126,241 @@ TEST_F(ScreenshareLayerTest, 1Layer) {
|
|||||||
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
|
EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
|
||||||
layers_->FrameEncoded(kFrameSize, timestamp);
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, 2Layer) {
|
TEST_F(ScreenshareLayerTest, 2Layer) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
ConfigureBitrates();
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
uint32_t timestamp = 0;
|
uint32_t timestamp = 0;
|
||||||
uint8_t expected_tl0_idx = 0;
|
uint8_t expected_tl0_idx = 0;
|
||||||
CodecSpecificInfoVP8 vp8_info;
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
SetEncodeExpectations(false, false, 1);
|
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(kFlagsTL0, flags);
|
EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags);
|
||||||
EXPECT_EQ(0, vp8_info.temporalIdx);
|
EXPECT_EQ(0, vp8_info.temporalIdx);
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
++expected_tl0_idx;
|
++expected_tl0_idx;
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
||||||
|
|
||||||
EXPECT_CALL(tl1_frame_dropper_, SetRates(1000, 1))
|
// Insert 5 frames, cover grace period. All should be in TL0.
|
||||||
.Times(1);
|
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, -1, NULL));
|
|
||||||
// Insert 5 frames at 30 fps. All should belong to TL0.
|
|
||||||
for (int i = 0; i < 5; ++i) {
|
for (int i = 0; i < 5; ++i) {
|
||||||
timestamp += kTimestampDelta30Fps;
|
timestamp += kTimestampDelta5Fps;
|
||||||
// First iteration has a framerate based on a single frame, thus 1.
|
|
||||||
SetEncodeExpectations(false, false, 30);
|
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(0, vp8_info.temporalIdx);
|
EXPECT_EQ(0, vp8_info.temporalIdx);
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
++expected_tl0_idx;
|
++expected_tl0_idx;
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
||||||
}
|
}
|
||||||
// Drop two frames from TL0, thus being coded in TL1.
|
|
||||||
timestamp += kTimestampDelta30Fps;
|
// First frame in TL0.
|
||||||
SetEncodeExpectations(true, false, 30);
|
timestamp += kTimestampDelta5Fps;
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(kFlagsTL1Sync, flags);
|
EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags);
|
||||||
|
EXPECT_EQ(0, vp8_info.temporalIdx);
|
||||||
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
|
++expected_tl0_idx;
|
||||||
|
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
||||||
|
|
||||||
|
// Drop two frames from TL0, thus being coded in TL1.
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
|
// First frame is sync frame.
|
||||||
|
EXPECT_EQ(ScreenshareLayers::kTl1SyncFlags, flags);
|
||||||
EXPECT_EQ(1, vp8_info.temporalIdx);
|
EXPECT_EQ(1, vp8_info.temporalIdx);
|
||||||
EXPECT_TRUE(vp8_info.layerSync);
|
EXPECT_TRUE(vp8_info.layerSync);
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
||||||
|
|
||||||
timestamp += kTimestampDelta30Fps;
|
timestamp += kTimestampDelta5Fps;
|
||||||
SetEncodeExpectations(true, false, 30);
|
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(kFlagsTL1, flags);
|
EXPECT_EQ(ScreenshareLayers::kTl1Flags, flags);
|
||||||
EXPECT_EQ(1, vp8_info.temporalIdx);
|
EXPECT_EQ(1, vp8_info.temporalIdx);
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
EXPECT_FALSE(vp8_info.layerSync);
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) {
|
TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
ConfigureBitrates();
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
uint32_t timestamp = 0;
|
uint32_t timestamp = 0;
|
||||||
CodecSpecificInfoVP8 vp8_info;
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
const int kNumFrames = 10;
|
std::vector<int> sync_times;
|
||||||
const bool kDrops[kNumFrames] = {false, true, true, true, true,
|
|
||||||
true, true, true, true, true};
|
const int kNumFrames = kSyncPeriodSeconds * kFrameRate * 2 - 1;
|
||||||
const int kExpectedFramerates[kNumFrames] = {1, 5, 5, 5, 5, 5, 5, 5, 5, 5};
|
|
||||||
const bool kExpectedSyncs[kNumFrames] = {false, true, false, false, false,
|
|
||||||
false, false, true, false, false};
|
|
||||||
const int kExpectedTemporalIdx[kNumFrames] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
|
||||||
for (int i = 0; i < kNumFrames; ++i) {
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
timestamp += kTimestampDelta5Fps;
|
timestamp += kTimestampDelta5Fps;
|
||||||
SetEncodeExpectations(kDrops[i], false, kExpectedFramerates[i]);
|
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(kExpectedTemporalIdx[i], vp8_info.temporalIdx);
|
if (vp8_info.temporalIdx == 1 && vp8_info.layerSync) {
|
||||||
EXPECT_EQ(kExpectedSyncs[i], vp8_info.layerSync) << "Iteration: " << i;
|
sync_times.push_back(timestamp);
|
||||||
EXPECT_EQ(1, vp8_info.tl0PicIdx);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(2u, sync_times.size());
|
||||||
|
EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kSyncPeriodSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ScreenshareLayerTest, 2LayersSyncAfterTimeout) {
|
||||||
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
|
ConfigureBitrates();
|
||||||
|
uint32_t timestamp = 0;
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
std::vector<int> sync_times;
|
||||||
|
|
||||||
|
const int kNumFrames = kMaxSyncPeriodSeconds * kFrameRate * 2 - 1;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
layers_->EncodeFlags(timestamp);
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
|
||||||
|
// Simulate TL1 being at least 8 qp steps better.
|
||||||
|
if (vp8_info.temporalIdx == 0) {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
} else {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vp8_info.temporalIdx == 1 && vp8_info.layerSync)
|
||||||
|
sync_times.push_back(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(2u, sync_times.size());
|
||||||
|
EXPECT_GE(sync_times[1] - sync_times[0], 90000 * kMaxSyncPeriodSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ScreenshareLayerTest, 2LayersSyncAfterSimilarQP) {
|
||||||
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
|
ConfigureBitrates();
|
||||||
|
uint32_t timestamp = 0;
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
std::vector<int> sync_times;
|
||||||
|
|
||||||
|
const int kNumFrames = (kSyncPeriodSeconds +
|
||||||
|
((kMaxSyncPeriodSeconds - kSyncPeriodSeconds) / 2)) *
|
||||||
|
kFrameRate;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
layers_->EncodeFlags(timestamp);
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
|
||||||
|
// Simulate TL1 being at least 8 qp steps better.
|
||||||
|
if (vp8_info.temporalIdx == 0) {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
} else {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vp8_info.temporalIdx == 1 && vp8_info.layerSync)
|
||||||
|
sync_times.push_back(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(1u, sync_times.size());
|
||||||
|
|
||||||
|
bool bumped_tl0_quality = false;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
int flags = layers_->EncodeFlags(timestamp);
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
|
||||||
|
if (vp8_info.temporalIdx == 0) {
|
||||||
|
// Bump TL0 to same quality as TL1.
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8);
|
||||||
|
bumped_tl0_quality = true;
|
||||||
|
} else {
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp - 8);
|
||||||
|
if (bumped_tl0_quality) {
|
||||||
|
EXPECT_TRUE(vp8_info.layerSync);
|
||||||
|
EXPECT_EQ(ScreenshareLayers::kTl1SyncFlags, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ADD_FAILURE() << "No TL1 frame arrived within time limit.";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, 2LayersToggling) {
|
TEST_F(ScreenshareLayerTest, 2LayersToggling) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
ConfigureBitrates();
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
|
int flags = 0;
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
uint32_t timestamp = RunGracePeriod();
|
||||||
|
|
||||||
|
// Insert 50 frames. 2/5 should be TL0.
|
||||||
|
int tl0_frames = 0;
|
||||||
|
int tl1_frames = 0;
|
||||||
|
for (int i = 0; i < 50; ++i) {
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
|
switch (vp8_info.temporalIdx) {
|
||||||
|
case 0:
|
||||||
|
++tl0_frames;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
++tl1_frames;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_EQ(20, tl0_frames);
|
||||||
|
EXPECT_EQ(30, tl1_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ScreenshareLayerTest, AllFitsLayer0) {
|
||||||
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
|
ConfigureBitrates();
|
||||||
|
frame_size_ = ((kDefaultTl0BitrateKbps * 1000) / 8) / kFrameRate;
|
||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
uint32_t timestamp = 0;
|
uint32_t timestamp = 0;
|
||||||
CodecSpecificInfoVP8 vp8_info;
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
const int kNumFrames = 10;
|
// Insert 50 frames, small enough that all fits in TL0.
|
||||||
const bool kDrops[kNumFrames] = {false, true, false, true, false,
|
for (int i = 0; i < 50; ++i) {
|
||||||
true, false, true, false, true};
|
|
||||||
const int kExpectedFramerates[kNumFrames] = {1, 5, 5, 5, 5, 5, 5, 5, 5, 5};
|
|
||||||
const bool kExpectedSyncs[kNumFrames] = {false, true, false, false, false,
|
|
||||||
false, false, true, false, false};
|
|
||||||
const int kExpectedTemporalIdx[kNumFrames] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
|
|
||||||
const int kExpectedTl0Idx[kNumFrames] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
|
|
||||||
for (int i = 0; i < kNumFrames; ++i) {
|
|
||||||
timestamp += kTimestampDelta5Fps;
|
|
||||||
SetEncodeExpectations(kDrops[i], false, kExpectedFramerates[i]);
|
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
EXPECT_EQ(kExpectedTemporalIdx[i], vp8_info.temporalIdx);
|
timestamp += kTimestampDelta5Fps;
|
||||||
EXPECT_EQ(kExpectedSyncs[i], vp8_info.layerSync) << "Iteration: " << i;
|
EXPECT_EQ(ScreenshareLayers::kTl0Flags, flags);
|
||||||
EXPECT_EQ(kExpectedTl0Idx[i], vp8_info.tl0PicIdx);
|
EXPECT_EQ(0, vp8_info.temporalIdx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, 2LayersBothDrops) {
|
TEST_F(ScreenshareLayerTest, TooHighBitrate) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
ConfigureBitrates();
|
||||||
EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
|
frame_size_ = 2 * ((kDefaultTl1BitrateKbps * 1000) / 8) / kFrameRate;
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
uint32_t timestamp = 0;
|
|
||||||
uint8_t expected_tl0_idx = 0;
|
|
||||||
CodecSpecificInfoVP8 vp8_info;
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
SetEncodeExpectations(false, false, 1);
|
uint32_t timestamp = RunGracePeriod();
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
|
||||||
EXPECT_EQ(kFlagsTL0, flags);
|
|
||||||
EXPECT_EQ(0, vp8_info.temporalIdx);
|
|
||||||
EXPECT_FALSE(vp8_info.layerSync);
|
|
||||||
++expected_tl0_idx;
|
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
|
||||||
|
|
||||||
timestamp += kTimestampDelta5Fps;
|
// Insert 100 frames. Half should be dropped.
|
||||||
SetEncodeExpectations(true, false, 5);
|
int tl0_frames = 0;
|
||||||
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
int tl1_frames = 0;
|
||||||
EXPECT_EQ(kFlagsTL1Sync, flags);
|
int dropped_frames = 0;
|
||||||
EXPECT_EQ(1, vp8_info.temporalIdx);
|
for (int i = 0; i < 100; ++i) {
|
||||||
EXPECT_TRUE(vp8_info.layerSync);
|
timestamp += kTimestampDelta5Fps;
|
||||||
EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
|
EncodeFrame(timestamp, false, &vp8_info, &flags);
|
||||||
|
if (flags == -1) {
|
||||||
|
++dropped_frames;
|
||||||
|
} else {
|
||||||
|
switch (vp8_info.temporalIdx) {
|
||||||
|
case 0:
|
||||||
|
++tl0_frames;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
++tl1_frames;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timestamp += kTimestampDelta5Fps;
|
EXPECT_EQ(5, tl0_frames);
|
||||||
SetEncodeExpectations(true, true, 5);
|
EXPECT_EQ(45, tl1_frames);
|
||||||
flags = layers_->EncodeFlags(timestamp);
|
EXPECT_EQ(50, dropped_frames);
|
||||||
EXPECT_EQ(-1, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) {
|
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
|
||||||
|
|
||||||
vpx_codec_enc_cfg_t cfg;
|
vpx_codec_enc_cfg_t cfg;
|
||||||
layers_->ConfigureBitrates(100, 1000, 5, &cfg);
|
layers_->ConfigureBitrates(100, 1000, 5, &cfg);
|
||||||
@ -268,8 +371,7 @@ TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) {
|
TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
|
||||||
vpx_codec_enc_cfg_t cfg;
|
vpx_codec_enc_cfg_t cfg;
|
||||||
layers_->ConfigureBitrates(100, 450, 5, &cfg);
|
layers_->ConfigureBitrates(100, 450, 5, &cfg);
|
||||||
|
|
||||||
@ -279,12 +381,64 @@ TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) {
|
TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) {
|
||||||
layers_.reset(
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
|
|
||||||
vpx_codec_enc_cfg_t cfg;
|
vpx_codec_enc_cfg_t cfg;
|
||||||
layers_->ConfigureBitrates(100, 100, 5, &cfg);
|
layers_->ConfigureBitrates(100, 100, 5, &cfg);
|
||||||
|
|
||||||
EXPECT_EQ(100U, cfg.rc_target_bitrate);
|
EXPECT_EQ(100U, cfg.rc_target_bitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ScreenshareLayerTest, EncoderDrop) {
|
||||||
|
layers_.reset(new ScreenshareLayers(2, 0));
|
||||||
|
ConfigureBitrates();
|
||||||
|
CodecSpecificInfoVP8 vp8_info;
|
||||||
|
vpx_codec_enc_cfg_t cfg;
|
||||||
|
cfg.rc_max_quantizer = kDefaultQp;
|
||||||
|
|
||||||
|
uint32_t timestamp = RunGracePeriod();
|
||||||
|
timestamp = SkipUntilTl(0, timestamp);
|
||||||
|
|
||||||
|
// Size 0 indicates dropped frame.
|
||||||
|
layers_->FrameEncoded(0, timestamp, kDefaultQp);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EXPECT_FALSE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
EXPECT_EQ(ScreenshareLayers::kTl0Flags, layers_->EncodeFlags(timestamp));
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
|
timestamp = SkipUntilTl(0, timestamp);
|
||||||
|
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
EXPECT_LT(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
|
layers_->EncodeFlags(timestamp);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
EXPECT_EQ(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
|
// Next drop in TL1.
|
||||||
|
|
||||||
|
timestamp = SkipUntilTl(1, timestamp);
|
||||||
|
layers_->FrameEncoded(0, timestamp, kDefaultQp);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EXPECT_FALSE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
EXPECT_EQ(ScreenshareLayers::kTl1Flags, layers_->EncodeFlags(timestamp));
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
|
timestamp = SkipUntilTl(1, timestamp);
|
||||||
|
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
EXPECT_LT(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
|
||||||
|
layers_->EncodeFlags(timestamp);
|
||||||
|
timestamp += kTimestampDelta5Fps;
|
||||||
|
EXPECT_TRUE(layers_->UpdateConfiguration(&cfg));
|
||||||
|
layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
|
||||||
|
EXPECT_EQ(cfg.rc_max_quantizer, static_cast<unsigned int>(kDefaultQp));
|
||||||
|
layers_->FrameEncoded(frame_size_, timestamp, kDefaultQp);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -102,10 +102,7 @@ struct ScreenshareTemporalLayersFactory : webrtc::TemporalLayers::Factory {
|
|||||||
|
|
||||||
virtual webrtc::TemporalLayers* Create(int num_temporal_layers,
|
virtual webrtc::TemporalLayers* Create(int num_temporal_layers,
|
||||||
uint8_t initial_tl0_pic_idx) const {
|
uint8_t initial_tl0_pic_idx) const {
|
||||||
return new webrtc::ScreenshareLayers(num_temporal_layers,
|
return new webrtc::ScreenshareLayers(num_temporal_layers, rand());
|
||||||
rand(),
|
|
||||||
&tl0_frame_dropper_,
|
|
||||||
&tl1_frame_dropper_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutable webrtc::FrameDropper tl0_frame_dropper_;
|
mutable webrtc::FrameDropper tl0_frame_dropper_;
|
||||||
|
@ -196,12 +196,16 @@ class SkipEncodingUnusedStreamsTest {
|
|||||||
layers_->PopulateCodecSpecific(base_layer_sync, vp8_info, timestamp);
|
layers_->PopulateCodecSpecific(base_layer_sync, vp8_info, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameEncoded(unsigned int size, uint32_t timestamp) override {
|
void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) override {
|
||||||
layers_->FrameEncoded(size, timestamp);
|
layers_->FrameEncoded(size, timestamp, qp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CurrentLayerId() const override { return layers_->CurrentLayerId(); }
|
int CurrentLayerId() const override { return layers_->CurrentLayerId(); }
|
||||||
|
|
||||||
|
bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int configured_bitrate_;
|
int configured_bitrate_;
|
||||||
TemporalLayers* layers_;
|
TemporalLayers* layers_;
|
||||||
};
|
};
|
||||||
|
@ -47,9 +47,11 @@ class TemporalLayers {
|
|||||||
CodecSpecificInfoVP8* vp8_info,
|
CodecSpecificInfoVP8* vp8_info,
|
||||||
uint32_t timestamp) = 0;
|
uint32_t timestamp) = 0;
|
||||||
|
|
||||||
virtual void FrameEncoded(unsigned int size, uint32_t timestamp) = 0;
|
virtual void FrameEncoded(unsigned int size, uint32_t timestamp, int qp) = 0;
|
||||||
|
|
||||||
virtual int CurrentLayerId() const = 0;
|
virtual int CurrentLayerId() const = 0;
|
||||||
|
|
||||||
|
virtual bool UpdateConfiguration(vpx_codec_enc_cfg_t* cfg) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Factory for a temporal layers strategy that adaptively changes the number of
|
// Factory for a temporal layers strategy that adaptively changes the number of
|
||||||
|
@ -278,7 +278,6 @@ int VP8EncoderImpl::SetRates(uint32_t new_bitrate_kbit,
|
|||||||
int tl0_bitrate = std::min(codec_.targetBitrate, target_bitrate);
|
int tl0_bitrate = std::min(codec_.targetBitrate, target_bitrate);
|
||||||
max_bitrate = std::min(codec_.maxBitrate, target_bitrate);
|
max_bitrate = std::min(codec_.maxBitrate, target_bitrate);
|
||||||
target_bitrate = tl0_bitrate;
|
target_bitrate = tl0_bitrate;
|
||||||
framerate = -1;
|
|
||||||
}
|
}
|
||||||
configurations_[i].rc_target_bitrate = target_bitrate;
|
configurations_[i].rc_target_bitrate = target_bitrate;
|
||||||
temporal_layers_[stream_idx]->ConfigureBitrates(target_bitrate,
|
temporal_layers_[stream_idx]->ConfigureBitrates(target_bitrate,
|
||||||
@ -312,10 +311,8 @@ void VP8EncoderImpl::SetupTemporalLayers(int num_streams,
|
|||||||
if (num_streams == 1) {
|
if (num_streams == 1) {
|
||||||
if (codec.mode == kScreensharing) {
|
if (codec.mode == kScreensharing) {
|
||||||
// Special mode when screensharing on a single stream.
|
// Special mode when screensharing on a single stream.
|
||||||
temporal_layers_.push_back(new ScreenshareLayers(num_temporal_layers,
|
temporal_layers_.push_back(
|
||||||
rand(),
|
new ScreenshareLayers(num_temporal_layers, rand()));
|
||||||
&tl0_frame_dropper_,
|
|
||||||
&tl1_frame_dropper_));
|
|
||||||
} else {
|
} else {
|
||||||
temporal_layers_.push_back(
|
temporal_layers_.push_back(
|
||||||
tl_factory.Create(num_temporal_layers, rand()));
|
tl_factory.Create(num_temporal_layers, rand()));
|
||||||
@ -670,8 +667,10 @@ int VP8EncoderImpl::InitAndSetControlSettings() {
|
|||||||
static_cast<vp8e_token_partitions>(token_partitions_));
|
static_cast<vp8e_token_partitions>(token_partitions_));
|
||||||
vpx_codec_control(&(encoders_[i]), VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
vpx_codec_control(&(encoders_[i]), VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
||||||
rc_max_intra_target_);
|
rc_max_intra_target_);
|
||||||
|
// VP8E_SET_SCREEN_CONTENT_MODE 2 = screen content with more aggressive
|
||||||
|
// rate control (drop frames on large target bitrate overshoot)
|
||||||
vpx_codec_control(&(encoders_[i]), VP8E_SET_SCREEN_CONTENT_MODE,
|
vpx_codec_control(&(encoders_[i]), VP8E_SET_SCREEN_CONTENT_MODE,
|
||||||
codec_.mode == kScreensharing);
|
codec_.mode == kScreensharing ? 2 : 0);
|
||||||
}
|
}
|
||||||
inited_ = true;
|
inited_ = true;
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
@ -698,15 +697,12 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
|
|||||||
const std::vector<VideoFrameType>* frame_types) {
|
const std::vector<VideoFrameType>* frame_types) {
|
||||||
TRACE_EVENT1("webrtc", "VP8::Encode", "timestamp", frame.timestamp());
|
TRACE_EVENT1("webrtc", "VP8::Encode", "timestamp", frame.timestamp());
|
||||||
|
|
||||||
if (!inited_) {
|
if (!inited_)
|
||||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||||
}
|
if (frame.IsZeroSize())
|
||||||
if (frame.IsZeroSize()) {
|
|
||||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||||
}
|
if (encoded_complete_callback_ == NULL)
|
||||||
if (encoded_complete_callback_ == NULL) {
|
|
||||||
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
||||||
}
|
|
||||||
|
|
||||||
// Only apply scaling to improve for single-layer streams. The scaling metrics
|
// Only apply scaling to improve for single-layer streams. The scaling metrics
|
||||||
// use frame drops as a signal and is only applicable when we drop frames.
|
// use frame drops as a signal and is only applicable when we drop frames.
|
||||||
@ -851,6 +847,16 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
|
|||||||
// whereas |encoder_| is from highest to lowest resolution.
|
// whereas |encoder_| is from highest to lowest resolution.
|
||||||
size_t stream_idx = encoders_.size() - 1;
|
size_t stream_idx = encoders_.size() - 1;
|
||||||
for (size_t i = 0; i < encoders_.size(); ++i, --stream_idx) {
|
for (size_t i = 0; i < encoders_.size(); ++i, --stream_idx) {
|
||||||
|
// Allow the layers adapter to temporarily modify the configuration. This
|
||||||
|
// change isn't stored in configurations_ so change will be discarded at
|
||||||
|
// the next update.
|
||||||
|
vpx_codec_enc_cfg_t temp_config;
|
||||||
|
memcpy(&temp_config, &configurations_[i], sizeof(vpx_codec_enc_cfg_t));
|
||||||
|
if (temporal_layers_[stream_idx]->UpdateConfiguration(&temp_config)) {
|
||||||
|
if (vpx_codec_enc_config_set(&encoders_[i], &temp_config))
|
||||||
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
vpx_codec_control(&encoders_[i], VP8E_SET_FRAME_FLAGS, flags[stream_idx]);
|
vpx_codec_control(&encoders_[i], VP8E_SET_FRAME_FLAGS, flags[stream_idx]);
|
||||||
vpx_codec_control(&encoders_[i],
|
vpx_codec_control(&encoders_[i],
|
||||||
VP8E_SET_TEMPORAL_LAYER_ID,
|
VP8E_SET_TEMPORAL_LAYER_ID,
|
||||||
@ -873,9 +879,8 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
|
|||||||
vpx_codec_control(&(encoders_[0]), VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
vpx_codec_control(&(encoders_[0]), VP8E_SET_MAX_INTRA_BITRATE_PCT,
|
||||||
rc_max_intra_target_);
|
rc_max_intra_target_);
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error)
|
||||||
return WEBRTC_VIDEO_CODEC_ERROR;
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||||
}
|
|
||||||
timestamp_ += duration;
|
timestamp_ += duration;
|
||||||
return GetEncodedPartitions(input_image, only_predict_from_key_frame);
|
return GetEncodedPartitions(input_image, only_predict_from_key_frame);
|
||||||
}
|
}
|
||||||
@ -933,6 +938,7 @@ void VP8EncoderImpl::PopulateCodecSpecific(
|
|||||||
int VP8EncoderImpl::GetEncodedPartitions(const VideoFrame& input_image,
|
int VP8EncoderImpl::GetEncodedPartitions(const VideoFrame& input_image,
|
||||||
bool only_predicting_from_key_frame) {
|
bool only_predicting_from_key_frame) {
|
||||||
int stream_idx = static_cast<int>(encoders_.size()) - 1;
|
int stream_idx = static_cast<int>(encoders_.size()) - 1;
|
||||||
|
int result = WEBRTC_VIDEO_CODEC_OK;
|
||||||
for (size_t encoder_idx = 0; encoder_idx < encoders_.size();
|
for (size_t encoder_idx = 0; encoder_idx < encoders_.size();
|
||||||
++encoder_idx, --stream_idx) {
|
++encoder_idx, --stream_idx) {
|
||||||
vpx_codec_iter_t iter = NULL;
|
vpx_codec_iter_t iter = NULL;
|
||||||
@ -981,9 +987,12 @@ int VP8EncoderImpl::GetEncodedPartitions(const VideoFrame& input_image,
|
|||||||
encoded_images_[encoder_idx]._timeStamp = input_image.timestamp();
|
encoded_images_[encoder_idx]._timeStamp = input_image.timestamp();
|
||||||
encoded_images_[encoder_idx].capture_time_ms_ =
|
encoded_images_[encoder_idx].capture_time_ms_ =
|
||||||
input_image.render_time_ms();
|
input_image.render_time_ms();
|
||||||
|
|
||||||
|
int qp = -1;
|
||||||
|
vpx_codec_control(&encoders_[encoder_idx], VP8E_GET_LAST_QUANTIZER_64, &qp);
|
||||||
temporal_layers_[stream_idx]->FrameEncoded(
|
temporal_layers_[stream_idx]->FrameEncoded(
|
||||||
encoded_images_[encoder_idx]._length,
|
encoded_images_[encoder_idx]._length,
|
||||||
encoded_images_[encoder_idx]._timeStamp);
|
encoded_images_[encoder_idx]._timeStamp, qp);
|
||||||
if (send_stream_[stream_idx]) {
|
if (send_stream_[stream_idx]) {
|
||||||
if (encoded_images_[encoder_idx]._length > 0) {
|
if (encoded_images_[encoder_idx]._length > 0) {
|
||||||
TRACE_COUNTER_ID1("webrtc", "EncodedFrameSize", encoder_idx,
|
TRACE_COUNTER_ID1("webrtc", "EncodedFrameSize", encoder_idx,
|
||||||
@ -994,6 +1003,8 @@ int VP8EncoderImpl::GetEncodedPartitions(const VideoFrame& input_image,
|
|||||||
codec_.simulcastStream[stream_idx].width;
|
codec_.simulcastStream[stream_idx].width;
|
||||||
encoded_complete_callback_->Encoded(encoded_images_[encoder_idx],
|
encoded_complete_callback_->Encoded(encoded_images_[encoder_idx],
|
||||||
&codec_specific, &frag_info);
|
&codec_specific, &frag_info);
|
||||||
|
} else if (codec_.mode == kScreensharing) {
|
||||||
|
result = WEBRTC_VIDEO_CODEC_TARGET_BITRATE_OVERSHOOT;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Required in case padding is applied to dropped frames.
|
// Required in case padding is applied to dropped frames.
|
||||||
@ -1017,7 +1028,7 @@ int VP8EncoderImpl::GetEncodedPartitions(const VideoFrame& input_image,
|
|||||||
quality_scaler_.ReportDroppedFrame();
|
quality_scaler_.ReportDroppedFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VP8EncoderImpl::SetChannelParameters(uint32_t packetLoss, int64_t rtt) {
|
int VP8EncoderImpl::SetChannelParameters(uint32_t packetLoss, int64_t rtt) {
|
||||||
|
@ -60,7 +60,8 @@ VCMGenericEncoder::VCMGenericEncoder(VideoEncoder* encoder,
|
|||||||
bit_rate_(0),
|
bit_rate_(0),
|
||||||
frame_rate_(0),
|
frame_rate_(0),
|
||||||
internal_source_(internalSource),
|
internal_source_(internalSource),
|
||||||
rotation_(kVideoRotation_0) {
|
rotation_(kVideoRotation_0),
|
||||||
|
is_screenshare_(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
VCMGenericEncoder::~VCMGenericEncoder()
|
VCMGenericEncoder::~VCMGenericEncoder()
|
||||||
@ -90,6 +91,7 @@ VCMGenericEncoder::InitEncode(const VideoCodec* settings,
|
|||||||
frame_rate_ = settings->maxFramerate;
|
frame_rate_ = settings->maxFramerate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_screenshare_ = settings->mode == VideoCodecMode::kScreensharing;
|
||||||
if (encoder_->InitEncode(settings, numberOfCores, maxPayloadSize) != 0) {
|
if (encoder_->InitEncode(settings, numberOfCores, maxPayloadSize) != 0) {
|
||||||
LOG(LS_ERROR) << "Failed to initialize the encoder associated with "
|
LOG(LS_ERROR) << "Failed to initialize the encoder associated with "
|
||||||
"payload name: " << settings->plName;
|
"payload name: " << settings->plName;
|
||||||
@ -114,7 +116,15 @@ int32_t VCMGenericEncoder::Encode(const VideoFrame& inputFrame,
|
|||||||
vcm_encoded_frame_callback_->SetRotation(rotation_);
|
vcm_encoded_frame_callback_->SetRotation(rotation_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return encoder_->Encode(inputFrame, codecSpecificInfo, &video_frame_types);
|
int32_t result =
|
||||||
|
encoder_->Encode(inputFrame, codecSpecificInfo, &video_frame_types);
|
||||||
|
if (is_screenshare_ &&
|
||||||
|
result == WEBRTC_VIDEO_CODEC_TARGET_BITRATE_OVERSHOOT) {
|
||||||
|
// Target bitrate exceeded, encoder state has been reset - try again.
|
||||||
|
return encoder_->Encode(inputFrame, codecSpecificInfo, &video_frame_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t
|
int32_t
|
||||||
|
@ -149,6 +149,7 @@ private:
|
|||||||
const bool internal_source_;
|
const bool internal_source_;
|
||||||
mutable rtc::CriticalSection rates_lock_;
|
mutable rtc::CriticalSection rates_lock_;
|
||||||
VideoRotation rotation_;
|
VideoRotation rotation_;
|
||||||
|
bool is_screenshare_;
|
||||||
}; // end of VCMGenericEncoder class
|
}; // end of VCMGenericEncoder class
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -649,8 +649,8 @@ TEST_F(FullStackTest, ScreenshareSlides) {
|
|||||||
{"screenshare_slides", 1850, 1110, 5},
|
{"screenshare_slides", 1850, 1110, 5},
|
||||||
true,
|
true,
|
||||||
50000,
|
50000,
|
||||||
100000,
|
200000,
|
||||||
1000000,
|
2000000,
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
kFullStackTestDurationSecs};
|
kFullStackTestDurationSecs};
|
||||||
|
@ -45,12 +45,12 @@ size_t MinBitrate() {
|
|||||||
return static_cast<size_t>(FLAGS_min_bitrate);
|
return static_cast<size_t>(FLAGS_min_bitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_int32(tl0_bitrate, 100, "Temporal layer 0 target bitrate.");
|
DEFINE_int32(tl0_bitrate, 200, "Temporal layer 0 target bitrate.");
|
||||||
size_t StartBitrate() {
|
size_t StartBitrate() {
|
||||||
return static_cast<size_t>(FLAGS_tl0_bitrate);
|
return static_cast<size_t>(FLAGS_tl0_bitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_int32(tl1_bitrate, 1000, "Temporal layer 1 target bitrate.");
|
DEFINE_int32(tl1_bitrate, 2000, "Temporal layer 1 target bitrate.");
|
||||||
size_t MaxBitrate() {
|
size_t MaxBitrate() {
|
||||||
return static_cast<size_t>(FLAGS_tl1_bitrate);
|
return static_cast<size_t>(FLAGS_tl1_bitrate);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user