Dynamic resolution change for VP8 HW encode.
Off by default for now. BUG= R=glaznev@webrtc.org, stefan@webrtc.org TBR=mflodman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/45849004 Cr-Commit-Position: refs/heads/master@{#9045}
This commit is contained in:
@ -34,6 +34,7 @@
|
|||||||
#include "webrtc/base/logging.h"
|
#include "webrtc/base/logging.h"
|
||||||
#include "webrtc/base/thread.h"
|
#include "webrtc/base/thread.h"
|
||||||
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
||||||
|
#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
|
||||||
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
|
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
|
||||||
#include "third_party/libyuv/include/libyuv/convert.h"
|
#include "third_party/libyuv/include/libyuv/convert.h"
|
||||||
#include "third_party/libyuv/include/libyuv/convert_from.h"
|
#include "third_party/libyuv/include/libyuv/convert_from.h"
|
||||||
@ -96,6 +97,8 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder,
|
|||||||
// rtc::MessageHandler implementation.
|
// rtc::MessageHandler implementation.
|
||||||
void OnMessage(rtc::Message* msg) override;
|
void OnMessage(rtc::Message* msg) override;
|
||||||
|
|
||||||
|
void OnDroppedFrame() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// CHECK-fail if not running on |codec_thread_|.
|
// CHECK-fail if not running on |codec_thread_|.
|
||||||
void CheckOnCodecThread();
|
void CheckOnCodecThread();
|
||||||
@ -171,7 +174,6 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder,
|
|||||||
int frames_received_; // Number of frames received by encoder.
|
int frames_received_; // Number of frames received by encoder.
|
||||||
int frames_encoded_; // Number of frames encoded by encoder.
|
int frames_encoded_; // Number of frames encoded by encoder.
|
||||||
int frames_dropped_; // Number of frames dropped by encoder.
|
int frames_dropped_; // Number of frames dropped by encoder.
|
||||||
int frames_resolution_update_; // Number of frames with new codec resolution.
|
|
||||||
int frames_in_queue_; // Number of frames in encoder queue.
|
int frames_in_queue_; // Number of frames in encoder queue.
|
||||||
int64_t start_time_ms_; // Start time for statistics.
|
int64_t start_time_ms_; // Start time for statistics.
|
||||||
int current_frames_; // Number of frames in the current statistics interval.
|
int current_frames_; // Number of frames in the current statistics interval.
|
||||||
@ -193,6 +195,11 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder,
|
|||||||
bool drop_next_input_frame_;
|
bool drop_next_input_frame_;
|
||||||
// Global references; must be deleted in Release().
|
// Global references; must be deleted in Release().
|
||||||
std::vector<jobject> input_buffers_;
|
std::vector<jobject> input_buffers_;
|
||||||
|
scoped_ptr<webrtc::QualityScaler> quality_scaler_;
|
||||||
|
// Target frame size in bytes.
|
||||||
|
int target_framesize_;
|
||||||
|
// Dynamic resolution change, off by default.
|
||||||
|
bool scale_;
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaCodecVideoEncoder::~MediaCodecVideoEncoder() {
|
MediaCodecVideoEncoder::~MediaCodecVideoEncoder() {
|
||||||
@ -207,6 +214,7 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(
|
|||||||
inited_(false),
|
inited_(false),
|
||||||
picture_id_(0),
|
picture_id_(0),
|
||||||
codec_thread_(new Thread()),
|
codec_thread_(new Thread()),
|
||||||
|
quality_scaler_(new webrtc::QualityScaler()),
|
||||||
j_media_codec_video_encoder_class_(
|
j_media_codec_video_encoder_class_(
|
||||||
jni,
|
jni,
|
||||||
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")),
|
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")),
|
||||||
@ -270,6 +278,8 @@ int32_t MediaCodecVideoEncoder::InitEncode(
|
|||||||
const webrtc::VideoCodec* codec_settings,
|
const webrtc::VideoCodec* codec_settings,
|
||||||
int32_t /* number_of_cores */,
|
int32_t /* number_of_cores */,
|
||||||
size_t /* max_payload_size */) {
|
size_t /* max_payload_size */) {
|
||||||
|
const int kMinWidth = 320;
|
||||||
|
const int kMinHeight = 180;
|
||||||
if (codec_settings == NULL) {
|
if (codec_settings == NULL) {
|
||||||
ALOGE("NULL VideoCodec instance");
|
ALOGE("NULL VideoCodec instance");
|
||||||
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
||||||
@ -279,6 +289,16 @@ int32_t MediaCodecVideoEncoder::InitEncode(
|
|||||||
codec_settings->codecType << " for " << codecType_;
|
codec_settings->codecType << " for " << codecType_;
|
||||||
|
|
||||||
ALOGD("InitEncode request");
|
ALOGD("InitEncode request");
|
||||||
|
scale_ = false;
|
||||||
|
quality_scaler_->Init(0);
|
||||||
|
quality_scaler_->SetMinResolution(kMinWidth, kMinHeight);
|
||||||
|
quality_scaler_->ReportFramerate(codec_settings->maxFramerate);
|
||||||
|
if (codec_settings->maxFramerate > 0) {
|
||||||
|
target_framesize_ = codec_settings->startBitrate * 1000 /
|
||||||
|
codec_settings->maxFramerate / 8;
|
||||||
|
} else {
|
||||||
|
target_framesize_ = 0;
|
||||||
|
}
|
||||||
return codec_thread_->Invoke<int32_t>(
|
return codec_thread_->Invoke<int32_t>(
|
||||||
Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread,
|
Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread,
|
||||||
this,
|
this,
|
||||||
@ -317,6 +337,12 @@ int32_t MediaCodecVideoEncoder::SetChannelParameters(uint32_t /* packet_loss */,
|
|||||||
|
|
||||||
int32_t MediaCodecVideoEncoder::SetRates(uint32_t new_bit_rate,
|
int32_t MediaCodecVideoEncoder::SetRates(uint32_t new_bit_rate,
|
||||||
uint32_t frame_rate) {
|
uint32_t frame_rate) {
|
||||||
|
quality_scaler_->ReportFramerate(frame_rate);
|
||||||
|
if (frame_rate > 0) {
|
||||||
|
target_framesize_ = new_bit_rate * 1000 / frame_rate / 8;
|
||||||
|
} else {
|
||||||
|
target_framesize_ = 0;
|
||||||
|
}
|
||||||
return codec_thread_->Invoke<int32_t>(
|
return codec_thread_->Invoke<int32_t>(
|
||||||
Bind(&MediaCodecVideoEncoder::SetRatesOnCodecThread,
|
Bind(&MediaCodecVideoEncoder::SetRatesOnCodecThread,
|
||||||
this,
|
this,
|
||||||
@ -384,7 +410,6 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread(
|
|||||||
frames_received_ = 0;
|
frames_received_ = 0;
|
||||||
frames_encoded_ = 0;
|
frames_encoded_ = 0;
|
||||||
frames_dropped_ = 0;
|
frames_dropped_ = 0;
|
||||||
frames_resolution_update_ = 0;
|
|
||||||
frames_in_queue_ = 0;
|
frames_in_queue_ = 0;
|
||||||
current_timestamp_us_ = 0;
|
current_timestamp_us_ = 0;
|
||||||
start_time_ms_ = GetCurrentTimeMs();
|
start_time_ms_ = GetCurrentTimeMs();
|
||||||
@ -472,20 +497,18 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|||||||
}
|
}
|
||||||
|
|
||||||
CHECK(frame_types->size() == 1) << "Unexpected stream count";
|
CHECK(frame_types->size() == 1) << "Unexpected stream count";
|
||||||
if (frame.width() != width_ || frame.height() != height_) {
|
const I420VideoFrame& input_frame =
|
||||||
frames_resolution_update_++;
|
(scale_ && codecType_ == kVideoCodecVP8) ?
|
||||||
ALOGD("Unexpected frame resolution change from %d x %d to %d x %d",
|
quality_scaler_->GetScaledFrame(frame) : frame;
|
||||||
width_, height_, frame.width(), frame.height());
|
|
||||||
if (frames_resolution_update_ > 3) {
|
if (input_frame.width() != width_ || input_frame.height() != height_) {
|
||||||
// Reset codec if we received more than 3 frames with new resolution.
|
ALOGD("Frame resolution change from %d x %d to %d x %d",
|
||||||
width_ = frame.width();
|
width_, height_, input_frame.width(), input_frame.height());
|
||||||
height_ = frame.height();
|
width_ = input_frame.width();
|
||||||
frames_resolution_update_ = 0;
|
height_ = input_frame.height();
|
||||||
ResetCodec();
|
ResetCodec();
|
||||||
}
|
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
}
|
}
|
||||||
frames_resolution_update_ = 0;
|
|
||||||
|
|
||||||
bool key_frame = frame_types->front() != webrtc::kDeltaFrame;
|
bool key_frame = frame_types->front() != webrtc::kDeltaFrame;
|
||||||
|
|
||||||
@ -498,6 +521,8 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|||||||
ALOGD("Drop frame - encoder is behind by %d ms. Q size: %d",
|
ALOGD("Drop frame - encoder is behind by %d ms. Q size: %d",
|
||||||
encoder_latency_ms, frames_in_queue_);
|
encoder_latency_ms, frames_in_queue_);
|
||||||
frames_dropped_++;
|
frames_dropped_++;
|
||||||
|
// Report dropped frame to quality_scaler_.
|
||||||
|
OnDroppedFrame();
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,6 +534,8 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|||||||
// Video codec falls behind - no input buffer available.
|
// Video codec falls behind - no input buffer available.
|
||||||
ALOGV("Encoder drop frame - no input buffers available");
|
ALOGV("Encoder drop frame - no input buffers available");
|
||||||
frames_dropped_++;
|
frames_dropped_++;
|
||||||
|
// Report dropped frame to quality_scaler_.
|
||||||
|
OnDroppedFrame();
|
||||||
return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887.
|
return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887.
|
||||||
}
|
}
|
||||||
if (j_input_buffer_index == -2) {
|
if (j_input_buffer_index == -2) {
|
||||||
@ -525,9 +552,12 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|||||||
CHECK_EXCEPTION(jni);
|
CHECK_EXCEPTION(jni);
|
||||||
CHECK(yuv_buffer) << "Indirect buffer??";
|
CHECK(yuv_buffer) << "Indirect buffer??";
|
||||||
CHECK(!libyuv::ConvertFromI420(
|
CHECK(!libyuv::ConvertFromI420(
|
||||||
frame.buffer(webrtc::kYPlane), frame.stride(webrtc::kYPlane),
|
input_frame.buffer(webrtc::kYPlane),
|
||||||
frame.buffer(webrtc::kUPlane), frame.stride(webrtc::kUPlane),
|
input_frame.stride(webrtc::kYPlane),
|
||||||
frame.buffer(webrtc::kVPlane), frame.stride(webrtc::kVPlane),
|
input_frame.buffer(webrtc::kUPlane),
|
||||||
|
input_frame.stride(webrtc::kUPlane),
|
||||||
|
input_frame.buffer(webrtc::kVPlane),
|
||||||
|
input_frame.stride(webrtc::kVPlane),
|
||||||
yuv_buffer, width_,
|
yuv_buffer, width_,
|
||||||
width_, height_,
|
width_, height_,
|
||||||
encoder_fourcc_))
|
encoder_fourcc_))
|
||||||
@ -536,8 +566,8 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|||||||
frames_in_queue_++;
|
frames_in_queue_++;
|
||||||
|
|
||||||
// Save input image timestamps for later output
|
// Save input image timestamps for later output
|
||||||
timestamps_.push_back(frame.timestamp());
|
timestamps_.push_back(input_frame.timestamp());
|
||||||
render_times_ms_.push_back(frame.render_time_ms());
|
render_times_ms_.push_back(input_frame.render_time_ms());
|
||||||
frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
|
frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
|
||||||
|
|
||||||
bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
|
bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
|
||||||
@ -686,6 +716,17 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) {
|
|||||||
last_input_timestamp_ms_ - last_output_timestamp_ms_,
|
last_input_timestamp_ms_ - last_output_timestamp_ms_,
|
||||||
frame_encoding_time_ms);
|
frame_encoding_time_ms);
|
||||||
|
|
||||||
|
if (payload_size) {
|
||||||
|
double framesize_deviation = 0.0;
|
||||||
|
if (target_framesize_ > 0) {
|
||||||
|
framesize_deviation =
|
||||||
|
(double)abs((int)payload_size - target_framesize_) /
|
||||||
|
target_framesize_;
|
||||||
|
}
|
||||||
|
quality_scaler_->ReportNormalizedFrameSizeFluctuation(
|
||||||
|
framesize_deviation);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate and print encoding statistics - every 3 seconds.
|
// Calculate and print encoding statistics - every 3 seconds.
|
||||||
frames_encoded_++;
|
frames_encoded_++;
|
||||||
current_frames_++;
|
current_frames_++;
|
||||||
@ -830,6 +871,9 @@ int32_t MediaCodecVideoEncoder::NextNaluPosition(
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaCodecVideoEncoder::OnDroppedFrame() {
|
||||||
|
quality_scaler_->ReportDroppedFrame();
|
||||||
|
}
|
||||||
|
|
||||||
MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() {
|
MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() {
|
||||||
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
||||||
|
@ -94,8 +94,9 @@ source_set("video_coding_utility") {
|
|||||||
sources = [
|
sources = [
|
||||||
"utility/frame_dropper.cc",
|
"utility/frame_dropper.cc",
|
||||||
"utility/include/frame_dropper.h",
|
"utility/include/frame_dropper.h",
|
||||||
|
"utility/include/moving_average.h",
|
||||||
|
"utility/include/quality_scaler.h",
|
||||||
"utility/quality_scaler.cc",
|
"utility/quality_scaler.cc",
|
||||||
"utility/quality_scaler.h",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
configs += [ "../..:common_config" ]
|
configs += [ "../..:common_config" ]
|
||||||
|
@ -77,6 +77,8 @@ class I420Encoder : public VideoEncoder {
|
|||||||
return WEBRTC_VIDEO_CODEC_OK;
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnDroppedFrame() override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uint8_t* InsertHeader(uint8_t* buffer, uint16_t width,
|
static uint8_t* InsertHeader(uint8_t* buffer, uint16_t width,
|
||||||
uint16_t height);
|
uint16_t height);
|
||||||
|
@ -501,4 +501,8 @@ bool SimulcastEncoderAdapter::Initialized() const {
|
|||||||
return !streaminfos_.empty();
|
return !streaminfos_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimulcastEncoderAdapter::OnDroppedFrame() {
|
||||||
|
streaminfos_[0].encoder->OnDroppedFrame();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -55,6 +55,8 @@ class SimulcastEncoderAdapter : public VP8Encoder {
|
|||||||
const CodecSpecificInfo* codecSpecificInfo = NULL,
|
const CodecSpecificInfo* codecSpecificInfo = NULL,
|
||||||
const RTPFragmentationHeader* fragmentation = NULL);
|
const RTPFragmentationHeader* fragmentation = NULL);
|
||||||
|
|
||||||
|
void OnDroppedFrame() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct StreamInfo {
|
struct StreamInfo {
|
||||||
StreamInfo()
|
StreamInfo()
|
||||||
|
@ -1035,7 +1035,7 @@ int VP8EncoderImpl::GetEncodedPartitions(
|
|||||||
if (encoded_images_[0]._length > 0) {
|
if (encoded_images_[0]._length > 0) {
|
||||||
int qp;
|
int qp;
|
||||||
vpx_codec_control(&encoders_[0], VP8E_GET_LAST_QUANTIZER_64, &qp);
|
vpx_codec_control(&encoders_[0], VP8E_GET_LAST_QUANTIZER_64, &qp);
|
||||||
quality_scaler_.ReportEncodedFrame(qp);
|
quality_scaler_.ReportNormalizedQP(qp);
|
||||||
} else {
|
} else {
|
||||||
quality_scaler_.ReportDroppedFrame();
|
quality_scaler_.ReportDroppedFrame();
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
|
||||||
#include "webrtc/modules/video_coding/codecs/vp8/reference_picture_selection.h"
|
#include "webrtc/modules/video_coding/codecs/vp8/reference_picture_selection.h"
|
||||||
#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
|
#include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
|
||||||
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
|
#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
|
||||||
#include "webrtc/video_frame.h"
|
#include "webrtc/video_frame.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
@ -56,6 +56,8 @@ class VP8EncoderImpl : public VP8Encoder {
|
|||||||
|
|
||||||
virtual int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate);
|
virtual int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate);
|
||||||
|
|
||||||
|
void OnDroppedFrame() override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetupTemporalLayers(int num_streams, int num_temporal_layers,
|
void SetupTemporalLayers(int num_streams, int num_temporal_layers,
|
||||||
const VideoCodec& codec);
|
const VideoCodec& codec);
|
||||||
|
@ -41,6 +41,8 @@ class VP9EncoderImpl : public VP9Encoder {
|
|||||||
|
|
||||||
int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate) override;
|
int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate) override;
|
||||||
|
|
||||||
|
void OnDroppedFrame() override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Determine number of encoder threads to use.
|
// Determine number of encoder threads to use.
|
||||||
int NumberOfThreads(int width, int height, int number_of_cores);
|
int NumberOfThreads(int width, int height, int number_of_cores);
|
||||||
|
@ -197,6 +197,10 @@ VCMGenericEncoder::InternalSource() const
|
|||||||
return internal_source_;
|
return internal_source_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VCMGenericEncoder::OnDroppedFrame() {
|
||||||
|
encoder_->OnDroppedFrame();
|
||||||
|
}
|
||||||
|
|
||||||
/***************************
|
/***************************
|
||||||
* Callback Implementation
|
* Callback Implementation
|
||||||
***************************/
|
***************************/
|
||||||
|
@ -136,6 +136,8 @@ public:
|
|||||||
|
|
||||||
bool InternalSource() const;
|
bool InternalSource() const;
|
||||||
|
|
||||||
|
void OnDroppedFrame();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VideoEncoder* const encoder_;
|
VideoEncoder* const encoder_;
|
||||||
VideoEncoderRateObserver* const rate_observer_;
|
VideoEncoderRateObserver* const rate_observer_;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
||||||
#include "webrtc/modules/video_coding/main/source/encoded_frame.h"
|
#include "webrtc/modules/video_coding/main/source/encoded_frame.h"
|
||||||
#include "webrtc/modules/video_coding/main/source/video_coding_impl.h"
|
#include "webrtc/modules/video_coding/main/source/video_coding_impl.h"
|
||||||
|
#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
|
||||||
#include "webrtc/system_wrappers/interface/clock.h"
|
#include "webrtc/system_wrappers/interface/clock.h"
|
||||||
#include "webrtc/system_wrappers/interface/logging.h"
|
#include "webrtc/system_wrappers/interface/logging.h"
|
||||||
|
|
||||||
@ -346,6 +347,7 @@ int32_t VideoSender::AddVideoFrame(const I420VideoFrame& videoFrame,
|
|||||||
return VCM_OK;
|
return VCM_OK;
|
||||||
}
|
}
|
||||||
if (_mediaOpt.DropFrame()) {
|
if (_mediaOpt.DropFrame()) {
|
||||||
|
_encoder->OnDroppedFrame();
|
||||||
return VCM_OK;
|
return VCM_OK;
|
||||||
}
|
}
|
||||||
_mediaOpt.UpdateContentData(contentMetrics);
|
_mediaOpt.UpdateContentData(contentMetrics);
|
||||||
|
71
webrtc/modules/video_coding/utility/include/moving_average.h
Normal file
71
webrtc/modules/video_coding/utility/include/moving_average.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
|
||||||
|
#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
#include "webrtc/typedefs.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
template<class T>
|
||||||
|
class MovingAverage {
|
||||||
|
public:
|
||||||
|
MovingAverage();
|
||||||
|
void AddSample(T sample);
|
||||||
|
bool GetAverage(size_t num_samples, T* average);
|
||||||
|
void Reset();
|
||||||
|
int size();
|
||||||
|
|
||||||
|
private:
|
||||||
|
T sum_;
|
||||||
|
std::list<T> samples_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
MovingAverage<T>::MovingAverage() : sum_(static_cast<T>(0)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void MovingAverage<T>::AddSample(T sample) {
|
||||||
|
samples_.push_back(sample);
|
||||||
|
sum_ += sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
bool MovingAverage<T>::GetAverage(size_t num_samples, T* avg) {
|
||||||
|
if (num_samples > samples_.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Remove old samples.
|
||||||
|
while (num_samples < samples_.size()) {
|
||||||
|
sum_ -= samples_.front();
|
||||||
|
samples_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
*avg = sum_ / static_cast<T>(num_samples);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void MovingAverage<T>::Reset() {
|
||||||
|
sum_ = static_cast<T>(0);
|
||||||
|
samples_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int MovingAverage<T>::size() {
|
||||||
|
return samples_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // WEBRTC_MODULES_VIDEO_CODING_MOVING_AVERAGE_SCALER_H_
|
@ -11,9 +11,8 @@
|
|||||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
|
#ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
|
||||||
#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
|
#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
|
||||||
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
#include "webrtc/common_video/libyuv/include/scaler.h"
|
#include "webrtc/common_video/libyuv/include/scaler.h"
|
||||||
|
#include "webrtc/modules/video_coding/utility/include/moving_average.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
class QualityScaler {
|
class QualityScaler {
|
||||||
@ -25,27 +24,20 @@ class QualityScaler {
|
|||||||
|
|
||||||
QualityScaler();
|
QualityScaler();
|
||||||
void Init(int max_qp);
|
void Init(int max_qp);
|
||||||
|
void SetMinResolution(int min_width, int min_height);
|
||||||
void ReportFramerate(int framerate);
|
void ReportFramerate(int framerate);
|
||||||
void ReportEncodedFrame(int qp);
|
|
||||||
void ReportDroppedFrame();
|
|
||||||
|
|
||||||
|
// Report QP for SW encoder, report framesize fluctuation for HW encoder,
|
||||||
|
// only one of these two functions should be called, framesize fluctuation
|
||||||
|
// is to be used only if qp isn't available.
|
||||||
|
void ReportNormalizedQP(int qp);
|
||||||
|
void ReportNormalizedFrameSizeFluctuation(double framesize_deviation);
|
||||||
|
void ReportDroppedFrame();
|
||||||
|
void Reset(int framerate, int bitrate, int width, int height);
|
||||||
Resolution GetScaledResolution(const I420VideoFrame& frame);
|
Resolution GetScaledResolution(const I420VideoFrame& frame);
|
||||||
const I420VideoFrame& GetScaledFrame(const I420VideoFrame& frame);
|
const I420VideoFrame& GetScaledFrame(const I420VideoFrame& frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class MovingAverage {
|
|
||||||
public:
|
|
||||||
MovingAverage();
|
|
||||||
void AddSample(int sample);
|
|
||||||
bool GetAverage(size_t num_samples, int* average);
|
|
||||||
void Reset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
int sum_;
|
|
||||||
std::list<int> samples_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AdjustScale(bool up);
|
void AdjustScale(bool up);
|
||||||
void ClearSamples();
|
void ClearSamples();
|
||||||
|
|
||||||
@ -53,11 +45,14 @@ class QualityScaler {
|
|||||||
I420VideoFrame scaled_frame_;
|
I420VideoFrame scaled_frame_;
|
||||||
|
|
||||||
size_t num_samples_;
|
size_t num_samples_;
|
||||||
|
int target_framesize_;
|
||||||
int low_qp_threshold_;
|
int low_qp_threshold_;
|
||||||
MovingAverage average_qp_;
|
MovingAverage<int> framedrop_percent_;
|
||||||
MovingAverage framedrop_percent_;
|
MovingAverage<double> frame_quality_;
|
||||||
|
|
||||||
int downscale_shift_;
|
int downscale_shift_;
|
||||||
|
int min_width_;
|
||||||
|
int min_height_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
@ -7,8 +7,7 @@
|
|||||||
* in the file PATENTS. All contributing project authors may
|
* in the file PATENTS. All contributing project authors may
|
||||||
* be found in the AUTHORS file in the root of the source tree.
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
|
||||||
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
|
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
@ -16,25 +15,41 @@ static const int kMinFps = 10;
|
|||||||
static const int kMeasureSeconds = 5;
|
static const int kMeasureSeconds = 5;
|
||||||
static const int kFramedropPercentThreshold = 60;
|
static const int kFramedropPercentThreshold = 60;
|
||||||
static const int kLowQpThresholdDenominator = 3;
|
static const int kLowQpThresholdDenominator = 3;
|
||||||
|
static const double kFramesizeFlucThreshold = 0.11;
|
||||||
|
|
||||||
QualityScaler::QualityScaler()
|
QualityScaler::QualityScaler()
|
||||||
: num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0) {
|
: num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0),
|
||||||
|
min_width_(0), min_height_(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void QualityScaler::Init(int max_qp) {
|
void QualityScaler::Init(int max_qp) {
|
||||||
ClearSamples();
|
ClearSamples();
|
||||||
downscale_shift_ = 0;
|
low_qp_threshold_ = max_qp / kLowQpThresholdDenominator;
|
||||||
low_qp_threshold_ = max_qp / kLowQpThresholdDenominator ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QualityScaler::SetMinResolution(int min_width, int min_height) {
|
||||||
|
min_width_ = min_width;
|
||||||
|
min_height_ = min_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jackychen): target_framesize should be calculated from average bitrate
|
||||||
|
// in the measured period of time.
|
||||||
|
// Report framerate(fps) and target_bitrate(kbit/s) to estimate # of samples
|
||||||
|
// and get target_framesize_.
|
||||||
void QualityScaler::ReportFramerate(int framerate) {
|
void QualityScaler::ReportFramerate(int framerate) {
|
||||||
num_samples_ = static_cast<size_t>(
|
num_samples_ = static_cast<size_t>(
|
||||||
kMeasureSeconds * (framerate < kMinFps ? kMinFps : framerate));
|
kMeasureSeconds * (framerate < kMinFps ? kMinFps : framerate));
|
||||||
}
|
}
|
||||||
|
|
||||||
void QualityScaler::ReportEncodedFrame(int qp) {
|
void QualityScaler::ReportNormalizedQP(int qp) {
|
||||||
average_qp_.AddSample(qp);
|
|
||||||
framedrop_percent_.AddSample(0);
|
framedrop_percent_.AddSample(0);
|
||||||
|
frame_quality_.AddSample(static_cast<double>(qp) / low_qp_threshold_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QualityScaler::ReportNormalizedFrameSizeFluctuation(
|
||||||
|
double framesize_deviation) {
|
||||||
|
framedrop_percent_.AddSample(0);
|
||||||
|
frame_quality_.AddSample(framesize_deviation / kFramesizeFlucThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QualityScaler::ReportDroppedFrame() {
|
void QualityScaler::ReportDroppedFrame() {
|
||||||
@ -43,23 +58,25 @@ void QualityScaler::ReportDroppedFrame() {
|
|||||||
|
|
||||||
QualityScaler::Resolution QualityScaler::GetScaledResolution(
|
QualityScaler::Resolution QualityScaler::GetScaledResolution(
|
||||||
const I420VideoFrame& frame) {
|
const I420VideoFrame& frame) {
|
||||||
// Both of these should be set through InitEncode -> Should be set by now.
|
// Should be set through InitEncode -> Should be set by now.
|
||||||
assert(low_qp_threshold_ >= 0);
|
assert(low_qp_threshold_ >= 0);
|
||||||
assert(num_samples_ > 0);
|
assert(num_samples_ > 0);
|
||||||
// Update scale factor.
|
|
||||||
int avg;
|
|
||||||
if (framedrop_percent_.GetAverage(num_samples_, &avg) &&
|
|
||||||
avg >= kFramedropPercentThreshold) {
|
|
||||||
AdjustScale(false);
|
|
||||||
} else if (average_qp_.GetAverage(num_samples_, &avg) &&
|
|
||||||
avg <= low_qp_threshold_) {
|
|
||||||
AdjustScale(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Resolution res;
|
Resolution res;
|
||||||
res.width = frame.width();
|
res.width = frame.width();
|
||||||
res.height = frame.height();
|
res.height = frame.height();
|
||||||
|
|
||||||
|
// Update scale factor.
|
||||||
|
int avg_drop;
|
||||||
|
double avg_quality;
|
||||||
|
if (framedrop_percent_.GetAverage(num_samples_, &avg_drop) &&
|
||||||
|
avg_drop >= kFramedropPercentThreshold) {
|
||||||
|
AdjustScale(false);
|
||||||
|
} else if (frame_quality_.GetAverage(num_samples_, &avg_quality) &&
|
||||||
|
avg_quality <= 1.0) {
|
||||||
|
AdjustScale(true);
|
||||||
|
}
|
||||||
|
|
||||||
assert(downscale_shift_ >= 0);
|
assert(downscale_shift_ >= 0);
|
||||||
for (int shift = downscale_shift_;
|
for (int shift = downscale_shift_;
|
||||||
shift > 0 && res.width > 1 && res.height > 1;
|
shift > 0 && res.width > 1 && res.height > 1;
|
||||||
@ -68,6 +85,12 @@ QualityScaler::Resolution QualityScaler::GetScaledResolution(
|
|||||||
res.height >>= 1;
|
res.height >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set this limitation for VP8 HW encoder to avoid crash.
|
||||||
|
if (min_width_ > 0 && res.width * res.height < min_width_ * min_height_) {
|
||||||
|
res.width = min_width_;
|
||||||
|
res.height = min_height_;
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,37 +117,9 @@ const I420VideoFrame& QualityScaler::GetScaledFrame(
|
|||||||
return scaled_frame_;
|
return scaled_frame_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QualityScaler::MovingAverage::MovingAverage() : sum_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void QualityScaler::MovingAverage::AddSample(int sample) {
|
|
||||||
samples_.push_back(sample);
|
|
||||||
sum_ += sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QualityScaler::MovingAverage::GetAverage(size_t num_samples, int* avg) {
|
|
||||||
assert(num_samples > 0);
|
|
||||||
if (num_samples > samples_.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Remove old samples.
|
|
||||||
while (num_samples < samples_.size()) {
|
|
||||||
sum_ -= samples_.front();
|
|
||||||
samples_.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
*avg = sum_ / static_cast<int>(num_samples);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QualityScaler::MovingAverage::Reset() {
|
|
||||||
sum_ = 0;
|
|
||||||
samples_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QualityScaler::ClearSamples() {
|
void QualityScaler::ClearSamples() {
|
||||||
average_qp_.Reset();
|
|
||||||
framedrop_percent_.Reset();
|
framedrop_percent_.Reset();
|
||||||
|
frame_quality_.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QualityScaler::AdjustScale(bool up) {
|
void QualityScaler::AdjustScale(bool up) {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
* be found in the AUTHORS file in the root of the source tree.
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
|
#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
|
||||||
|
|
||||||
#include "testing/gtest/include/gtest/gtest.h"
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ class QualityScalerTest : public ::testing::Test {
|
|||||||
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
||||||
switch (scale_direction) {
|
switch (scale_direction) {
|
||||||
case kScaleUp:
|
case kScaleUp:
|
||||||
qs_.ReportEncodedFrame(kLowQp);
|
qs_.ReportNormalizedQP(kLowQp);
|
||||||
break;
|
break;
|
||||||
case kScaleDown:
|
case kScaleDown:
|
||||||
qs_.ReportDroppedFrame();
|
qs_.ReportDroppedFrame();
|
||||||
@ -93,7 +93,7 @@ TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
|
|||||||
|
|
||||||
TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
|
TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
|
||||||
for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
|
for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
|
||||||
qs_.ReportEncodedFrame(kNormalQp);
|
qs_.ReportNormalizedQP(kNormalQp);
|
||||||
qs_.ReportDroppedFrame();
|
qs_.ReportDroppedFrame();
|
||||||
qs_.ReportDroppedFrame();
|
qs_.ReportDroppedFrame();
|
||||||
if (qs_.GetScaledResolution(input_frame_).width < input_frame_.width())
|
if (qs_.GetScaledResolution(input_frame_).width < input_frame_.width())
|
||||||
@ -105,7 +105,7 @@ TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
|
|||||||
|
|
||||||
TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
|
TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
|
||||||
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
||||||
qs_.ReportEncodedFrame(kNormalQp);
|
qs_.ReportNormalizedQP(kNormalQp);
|
||||||
ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
|
ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
|
||||||
<< "Unexpected scale on half framedrop.";
|
<< "Unexpected scale on half framedrop.";
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
|
|||||||
|
|
||||||
TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
|
TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
|
||||||
for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
|
for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
|
||||||
qs_.ReportEncodedFrame(kNormalQp);
|
qs_.ReportNormalizedQP(kNormalQp);
|
||||||
ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
|
ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
|
||||||
<< "Unexpected scale on half framedrop.";
|
<< "Unexpected scale on half framedrop.";
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() {
|
|||||||
|
|
||||||
// Verify we don't start upscaling after further low use.
|
// Verify we don't start upscaling after further low use.
|
||||||
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
|
||||||
qs_.ReportEncodedFrame(kLowQp);
|
qs_.ReportNormalizedQP(kLowQp);
|
||||||
ExpectOriginalFrame();
|
ExpectOriginalFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,9 @@
|
|||||||
'sources': [
|
'sources': [
|
||||||
'frame_dropper.cc',
|
'frame_dropper.cc',
|
||||||
'include/frame_dropper.h',
|
'include/frame_dropper.h',
|
||||||
|
'include/moving_average.h',
|
||||||
'quality_scaler.cc',
|
'quality_scaler.cc',
|
||||||
'quality_scaler.h',
|
'include/quality_scaler.h',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
], # targets
|
], # targets
|
||||||
|
@ -122,6 +122,7 @@ class VideoEncoder {
|
|||||||
virtual int32_t CodecConfigParameters(uint8_t* /*buffer*/, int32_t /*size*/) {
|
virtual int32_t CodecConfigParameters(uint8_t* /*buffer*/, int32_t /*size*/) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
virtual void OnDroppedFrame() {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
Reference in New Issue
Block a user