#include "media/cast/encoding/vpx_encoder.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "media/base/video_encoder_metrics_provider.h"
#include "media/base/video_frame.h"
#include "media/cast/common/openscreen_conversion_helpers.h"
#include "media/cast/common/sender_encoded_frame.h"
#include "media/cast/constants.h"
#include "media/cast/encoding/encoding_util.h"
#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
#include "third_party/openscreen/src/cast/streaming/public/encoded_frame.h"
namespace media {
namespace cast {
namespace {
const int kRestartFramePeriods = 3;
const int kEncodingSpeedAccHalfLife = 120000;
const double kHiTargetEncoderUtilization = 0.7;
const double kMidTargetEncoderUtilization = 0.6;
const double kLoTargetEncoderUtilization = 0.5;
const double kEquivalentEncodingSpeedStepPerQpStep = 1 / 20.0;
const int kHighestEncodingSpeed = 12;
const int kLowestEncodingSpeed = 6;
bool HasSufficientFeedback(
const FeedbackSignalAccumulator<base::TimeDelta>& accumulator) {
const base::TimeDelta amount_of_history =
accumulator.update_time() - accumulator.reset_time();
return amount_of_history.InMicroseconds() >= 250000;
}
}
VpxEncoder::VpxEncoder(
const FrameSenderConfig& video_config,
std::unique_ptr<VideoEncoderMetricsProvider> metrics_provider)
: cast_config_(video_config),
codec_params_(cast_config_.video_codec_params.value()),
target_encoder_utilization_(
codec_params_->number_of_encode_threads > 2
? kHiTargetEncoderUtilization
: (codec_params_->number_of_encode_threads > 1
? kMidTargetEncoderUtilization
: kLoTargetEncoderUtilization)),
metrics_provider_(std::move(metrics_provider)),
key_frame_requested_(true),
bitrate_kbit_(cast_config_.start_bitrate / 1000),
next_frame_id_(FrameId::first()),
encoding_speed_acc_(base::Microseconds(kEncodingSpeedAccHalfLife)),
encoding_speed_(kHighestEncodingSpeed) {
config_.g_timebase.den = 0;
DCHECK_LE(codec_params_->min_qp, codec_params_->max_cpu_saver_qp);
DCHECK_LE(codec_params_->max_cpu_saver_qp, codec_params_->max_qp);
DETACH_FROM_THREAD(thread_checker_);
}
VpxEncoder::~VpxEncoder() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (is_initialized()) {
vpx_codec_destroy(&encoder_);
}
}
void VpxEncoder::Initialize() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!is_initialized());
}
void VpxEncoder::ConfigureForNewFrameSize(const gfx::Size& frame_size) {
if (is_initialized()) {
if (frame_size.GetArea() <= gfx::Size(config_.g_w, config_.g_h).GetArea()) {
DVLOG(1) << "Continuing to use existing encoder at smaller frame size: "
<< gfx::Size(config_.g_w, config_.g_h).ToString() << " --> "
<< frame_size.ToString();
config_.g_w = frame_size.width();
config_.g_h = frame_size.height();
config_.rc_min_quantizer = codec_params_->min_qp;
if (vpx_codec_enc_config_set(&encoder_, &config_) == VPX_CODEC_OK) {
return;
}
DVLOG(1) << "libvpx rejected the attempt to use a smaller frame size in "
"the current instance.";
}
DVLOG(1) << "Destroying/Re-Creating encoder for larger frame size: "
<< gfx::Size(config_.g_w, config_.g_h).ToString() << " --> "
<< frame_size.ToString();
vpx_codec_destroy(&encoder_);
} else {
DVLOG(1) << "Creating encoder for the first frame; size: "
<< frame_size.ToString();
}
vpx_codec_iface_t* ctx;
if (codec_params_->codec == VideoCodec::kVP9) {
ctx = vpx_codec_vp9_cx();
} else {
DCHECK(codec_params_->codec == VideoCodec::kVP8);
ctx = vpx_codec_vp8_cx();
}
CHECK_EQ(vpx_codec_enc_config_default(ctx, &config_, 0), VPX_CODEC_OK);
config_.g_threads = codec_params_->number_of_encode_threads;
config_.g_w = frame_size.width();
config_.g_h = frame_size.height();
config_.g_timebase.num = 1;
config_.g_timebase.den = base::Time::kMicrosecondsPerSecond;
config_.g_pass = VPX_RC_ONE_PASS;
config_.g_lag_in_frames = 0;
config_.rc_dropframe_thresh = GetEncoderDropFrameThreshold();
config_.rc_resize_allowed = 0;
config_.rc_end_usage = VPX_CBR;
config_.rc_target_bitrate = bitrate_kbit_;
config_.rc_min_quantizer = codec_params_->min_qp;
config_.rc_max_quantizer = codec_params_->max_qp;
config_.rc_undershoot_pct = 100;
config_.rc_overshoot_pct = 15;
config_.rc_buf_initial_sz = 500;
config_.rc_buf_optimal_sz = 600;
config_.rc_buf_sz = 1000;
config_.kf_mode = VPX_KF_DISABLED;
vpx_codec_flags_t flags = 0;
metrics_provider_->Initialize(codec_params_->codec == VideoCodec::kVP9
? media::VP9PROFILE_MIN
: media::VP8PROFILE_ANY,
frame_size, false);
if (vpx_codec_err_t ret = vpx_codec_enc_init(&encoder_, ctx, &config_, flags);
ret != VPX_CODEC_OK) {
metrics_provider_->SetError(
{media::EncoderStatus::Codes::kEncoderInitializationError,
base::StrCat(
{"libvpx failed to initialize: ", vpx_codec_err_to_string(ret)})});
}
CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_STATIC_THRESHOLD, 100),
VPX_CODEC_OK);
if (codec_params_->codec == VideoCodec::kVP9) {
CHECK_EQ(vpx_codec_control(&encoder_, VP9E_SET_TUNE_CONTENT,
VP9E_CONTENT_SCREEN),
VPX_CODEC_OK);
} else {
const unsigned int screen_content_mode =
base::FeatureList::IsEnabled(kCastVideoEncoderFrameDrop) ? 2 : 1;
CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_SCREEN_CONTENT_MODE,
screen_content_mode),
VPX_CODEC_OK);
}
encoding_speed_ = kHighestEncodingSpeed;
CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_CPUUSED, -encoding_speed_),
VPX_CODEC_OK);
}
void VpxEncoder::Encode(scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks reference_time,
SenderEncodedFrame* encoded_frame) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(encoded_frame);
const base::TimeTicks start_time = base::TimeTicks::Now();
const gfx::Size frame_size = video_frame->visible_rect().size();
if (!is_initialized() || gfx::Size(config_.g_w, config_.g_h) != frame_size) {
ConfigureForNewFrameSize(frame_size);
}
vpx_img_fmt_t vpx_format = video_frame->format() == PIXEL_FORMAT_NV12
? VPX_IMG_FMT_NV12
: VPX_IMG_FMT_I420;
vpx_image_t vpx_image;
vpx_image_t* const result = vpx_img_wrap(
&vpx_image, vpx_format, frame_size.width(), frame_size.height(), 1,
const_cast<uint8_t*>(video_frame->visible_data(VideoFrame::Plane::kY)));
DCHECK_EQ(result, &vpx_image);
switch (vpx_format) {
case VPX_IMG_FMT_I420:
vpx_image.planes[VPX_PLANE_Y] = const_cast<uint8_t*>(
video_frame->visible_data(VideoFrame::Plane::kY));
vpx_image.planes[VPX_PLANE_U] = const_cast<uint8_t*>(
video_frame->visible_data(VideoFrame::Plane::kU));
vpx_image.planes[VPX_PLANE_V] = const_cast<uint8_t*>(
video_frame->visible_data(VideoFrame::Plane::kV));
vpx_image.stride[VPX_PLANE_Y] =
video_frame->stride(VideoFrame::Plane::kY);
vpx_image.stride[VPX_PLANE_U] =
video_frame->stride(VideoFrame::Plane::kU);
vpx_image.stride[VPX_PLANE_V] =
video_frame->stride(VideoFrame::Plane::kV);
break;
case VPX_IMG_FMT_NV12:
vpx_image.planes[VPX_PLANE_Y] = const_cast<uint8_t*>(
video_frame->visible_data(VideoFrame::Plane::kY));
vpx_image.planes[VPX_PLANE_U] = const_cast<uint8_t*>(
video_frame->visible_data(VideoFrame::Plane::kUV));
vpx_image.planes[VPX_PLANE_V] =
UNSAFE_TODO(vpx_image.planes[VPX_PLANE_U] + 1);
vpx_image.stride[VPX_PLANE_Y] =
video_frame->stride(VideoFrame::Plane::kY);
vpx_image.stride[VPX_PLANE_U] =
video_frame->stride(VideoFrame::Plane::kUV);
vpx_image.stride[VPX_PLANE_V] =
video_frame->stride(VideoFrame::Plane::kUV);
break;
default:
NOTREACHED();
}
const base::TimeDelta minimum_frame_duration =
base::Seconds(1.0 / cast_config_.max_frame_rate);
const base::TimeDelta maximum_frame_duration = base::Seconds(
static_cast<double>(kRestartFramePeriods) / cast_config_.max_frame_rate);
base::TimeDelta predicted_frame_duration =
video_frame->metadata().frame_duration.value_or(base::TimeDelta());
if (predicted_frame_duration <= base::TimeDelta()) {
predicted_frame_duration = video_frame->timestamp() - last_frame_timestamp_;
}
predicted_frame_duration =
std::max(minimum_frame_duration,
std::min(maximum_frame_duration, predicted_frame_duration));
last_frame_timestamp_ = video_frame->timestamp();
if (vpx_codec_err_t ret = vpx_codec_encode(
&encoder_, &vpx_image, 0, predicted_frame_duration.InMicroseconds(),
key_frame_requested_ ? VPX_EFLAG_FORCE_KF : 0, VPX_DL_REALTIME);
ret != VPX_CODEC_OK) {
metrics_provider_->SetError(
{media::EncoderStatus::Codes::kEncoderFailedEncode,
base::StrCat(
{"libvpx failed to encode: ", vpx_codec_err_to_string(ret), " - ",
vpx_codec_error_detail(&encoder_)})});
LOG(FATAL) << "BUG: Invalid arguments passed to vpx_codec_encode().";
}
encoded_frame->frame_id = next_frame_id_;
const vpx_codec_cx_pkt_t* pkt = nullptr;
vpx_codec_iter_t iter = nullptr;
while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != nullptr) {
if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
continue;
}
encoded_frame->is_key_frame = pkt->data.frame.flags & VPX_FRAME_IS_KEY;
if (encoded_frame->is_key_frame) {
encoded_frame->referenced_frame_id = encoded_frame->frame_id;
} else {
encoded_frame->referenced_frame_id = encoded_frame->frame_id - 1;
}
encoded_frame->rtp_timestamp =
ToRtpTimeTicks(video_frame->timestamp(), kVideoFrequency);
encoded_frame->reference_time = reference_time;
encoded_frame->data = base::HeapArray<uint8_t>::CopiedFrom(
UNSAFE_TODO(base::span(static_cast<const uint8_t*>(pkt->data.frame.buf),
pkt->data.frame.sz)));
break;
}
if (encoded_frame->data.empty()) {
return;
}
next_frame_id_++;
metrics_provider_->IncrementEncodedFrameCount();
const base::TimeDelta processing_time = base::TimeTicks::Now() - start_time;
encoded_frame->encoder_utilization =
processing_time / predicted_frame_duration;
const double actual_bitrate =
encoded_frame->data.size() * 8.0 / predicted_frame_duration.InSecondsF();
const double target_bitrate = 1000.0 * config_.rc_target_bitrate;
DCHECK_GT(target_bitrate, 0.0);
const double bitrate_utilization = actual_bitrate / target_bitrate;
int quantizer = -1;
CHECK_EQ(vpx_codec_control(&encoder_, VP8E_GET_LAST_QUANTIZER_64, &quantizer),
VPX_CODEC_OK);
const double perfect_quantizer = bitrate_utilization * std::max(0, quantizer);
encoded_frame->lossiness = perfect_quantizer / 63.0;
DVLOG(2) << "VPX encoded frame_id " << encoded_frame->frame_id
<< ", sized: " << encoded_frame->data.size()
<< ", encoder_utilization: " << encoded_frame->encoder_utilization
<< ", lossiness: " << encoded_frame->lossiness
<< " (quantizer chosen by the encoder was " << quantizer << ')';
if (encoded_frame->is_key_frame) {
key_frame_requested_ = false;
encoding_speed_acc_.Reset(kHighestEncodingSpeed, video_frame->timestamp());
} else {
double actual_encoding_speed =
encoding_speed_ + kEquivalentEncodingSpeedStepPerQpStep *
std::max(0, quantizer - codec_params_->min_qp);
double adjusted_encoding_speed = actual_encoding_speed *
encoded_frame->encoder_utilization /
target_encoder_utilization_;
encoding_speed_acc_.Update(adjusted_encoding_speed,
video_frame->timestamp());
}
if (HasSufficientFeedback(encoding_speed_acc_)) {
double next_encoding_speed = encoding_speed_acc_.current();
int next_min_qp;
if (next_encoding_speed > kHighestEncodingSpeed) {
double remainder = next_encoding_speed - kHighestEncodingSpeed;
next_encoding_speed = kHighestEncodingSpeed;
next_min_qp =
static_cast<int>(remainder / kEquivalentEncodingSpeedStepPerQpStep +
codec_params_->min_qp + 0.5);
next_min_qp = std::min(next_min_qp, codec_params_->max_cpu_saver_qp);
} else {
next_encoding_speed =
std::max<double>(kLowestEncodingSpeed, next_encoding_speed) + 0.5;
next_min_qp = codec_params_->min_qp;
}
if (encoding_speed_ != static_cast<int>(next_encoding_speed)) {
encoding_speed_ = static_cast<int>(next_encoding_speed);
CHECK_EQ(vpx_codec_control(&encoder_, VP8E_SET_CPUUSED, -encoding_speed_),
VPX_CODEC_OK);
}
if (config_.rc_min_quantizer != static_cast<unsigned int>(next_min_qp)) {
config_.rc_min_quantizer = static_cast<unsigned int>(next_min_qp);
CHECK_EQ(vpx_codec_enc_config_set(&encoder_, &config_), VPX_CODEC_OK);
}
}
}
void VpxEncoder::UpdateRates(uint32_t new_bitrate) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!is_initialized()) {
return;
}
uint32_t new_bitrate_kbit = new_bitrate / 1000;
if (config_.rc_target_bitrate == new_bitrate_kbit) {
return;
}
config_.rc_target_bitrate = bitrate_kbit_ = new_bitrate_kbit;
if (vpx_codec_enc_config_set(&encoder_, &config_)) {
NOTREACHED() << "Invalid return value";
}
VLOG(1) << "VPX new rc_target_bitrate: " << new_bitrate_kbit << " kbps";
}
void VpxEncoder::GenerateKeyFrame() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
key_frame_requested_ = true;
}
}
}