blob: 696afed6ee5670ab93df5d45801760fa90e9108e [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/vpx_video_encoder.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
namespace media {
namespace {
Status SetUpVpxConfig(const VideoEncoder::Options& opts,
vpx_codec_enc_cfg_t* config) {
if (opts.width <= 0 || opts.height <= 0)
return Status(StatusCode::kEncoderUnsupportedConfig,
"Negative width or height values");
config->g_pass = VPX_RC_ONE_PASS;
config->g_lag_in_frames = 0;
config->rc_resize_allowed = 0;
config->rc_dropframe_thresh = 0; // Don't drop frames
config->g_timebase.num = 1;
config->g_timebase.den = base::Time::kMicrosecondsPerSecond;
if (opts.threads.has_value())
config->g_threads = opts.threads.value();
config->g_w = opts.width;
config->g_h = opts.height;
// Insert keyframes at will with a given max interval
if (opts.keyframe_interval.has_value()) {
config->kf_mode = VPX_KF_AUTO;
config->kf_min_dist = 0;
config->kf_max_dist = opts.keyframe_interval.value();
}
if (opts.bitrate.has_value()) {
config->rc_end_usage = VPX_CBR;
config->rc_target_bitrate = opts.bitrate.value() / 1000;
} else {
config->rc_end_usage = VPX_VBR;
}
return Status();
}
} // namespace
VpxVideoEncoder::VpxVideoEncoder() = default;
void VpxVideoEncoder::Initialize(VideoCodecProfile profile,
const Options& options,
OutputCB output_cb,
StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
return;
}
vpx_codec_iface_t* iface = nullptr;
if (profile == media::VP8PROFILE_ANY) {
iface = vpx_codec_vp8_cx();
} else if (profile >= media::VP9PROFILE_PROFILE0 &&
profile <= media::VP9PROFILE_PROFILE3) {
iface = vpx_codec_vp9_cx();
} else {
auto status = Status(StatusCode::kEncoderUnsupportedProfile)
.WithData("profile", profile);
std::move(done_cb).Run(status);
return;
}
auto vpx_error = vpx_codec_enc_config_default(iface, &codec_config_, 0);
if (vpx_error != VPX_CODEC_OK) {
auto status = Status(StatusCode::kEncoderInitializationError,
"Failed to get default VPX config.")
.WithData("vpx_error", vpx_error);
std::move(done_cb).Run(status);
return;
}
switch (profile) {
case media::VP9PROFILE_PROFILE1:
codec_config_.g_profile = 1;
break;
case media::VP9PROFILE_PROFILE2:
codec_config_.g_profile = 2;
break;
case media::VP9PROFILE_PROFILE3:
codec_config_.g_profile = 3;
break;
default:
codec_config_.g_profile = 0;
break;
}
auto status = SetUpVpxConfig(options, &codec_config_);
if (!status.is_ok()) {
std::move(done_cb).Run(status);
return;
}
codec_ = new vpx_codec_ctx_t;
vpx_error = vpx_codec_enc_init(codec_, iface, &codec_config_, 0 /* flags */);
if (vpx_error != VPX_CODEC_OK) {
std::string msg = base::StringPrintf("VPX encoder initialization error: %s",
vpx_codec_err_to_string(vpx_error));
status = Status(StatusCode::kEncoderInitializationError, msg);
std::move(done_cb).Run(status);
return;
}
// Due to https://bugs.chromium.org/p/webm/issues/detail?id=1684
// values less than 5 crash VP9 encoder.
vpx_error = vpx_codec_control(codec_, VP8E_SET_CPUUSED, 5);
if (vpx_error != VPX_CODEC_OK) {
std::string msg =
base::StringPrintf("VPX encoder VP8E_SET_CPUUSED error: %s",
vpx_codec_err_to_string(vpx_error));
status = Status(StatusCode::kEncoderInitializationError, msg);
std::move(done_cb).Run(status);
return;
}
options_ = options;
output_cb_ = media::BindToCurrentLoop(std::move(output_cb));
std::move(done_cb).Run(Status());
}
void VpxVideoEncoder::Encode(scoped_refptr<const VideoFrame> frame,
bool key_frame,
StatusCB done_cb) {
Status status;
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
if (!frame) {
std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
"No frame provided for encoding."));
return;
}
if (!frame->IsMappable() || frame->format() != media::PIXEL_FORMAT_I420) {
status =
Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
.WithData("IsMappable", frame->IsMappable())
.WithData("format", frame->format());
std::move(done_cb).Run(std::move(status));
return;
}
auto size = frame->visible_rect().size();
auto* img_data =
const_cast<unsigned char*>(frame->data(media::VideoFrame::kYPlane));
vpx_image_t vpx_image;
if (&vpx_image != vpx_img_wrap(&vpx_image, VPX_IMG_FMT_I420, size.width(),
size.height(), 1 /* align */, img_data)) {
std::move(done_cb).Run(StatusCode::kEncoderFailedEncode);
return;
}
vpx_image.planes[VPX_PLANE_Y] = const_cast<unsigned char*>(
frame->visible_data(media::VideoFrame::kYPlane));
vpx_image.planes[VPX_PLANE_U] = const_cast<unsigned char*>(
frame->visible_data(media::VideoFrame::kUPlane));
vpx_image.planes[VPX_PLANE_V] = const_cast<unsigned char*>(
frame->visible_data(media::VideoFrame::kVPlane));
vpx_image.stride[VPX_PLANE_Y] = frame->stride(media::VideoFrame::kYPlane);
vpx_image.stride[VPX_PLANE_U] = frame->stride(media::VideoFrame::kUPlane);
vpx_image.stride[VPX_PLANE_V] = frame->stride(media::VideoFrame::kVPlane);
auto timestamp = frame->timestamp().InMicroseconds();
auto duration = GetFrameDuration(*frame);
auto deadline = VPX_DL_REALTIME;
vpx_codec_flags_t flags = key_frame ? VPX_EFLAG_FORCE_KF : 0;
auto vpx_error = vpx_codec_encode(codec_, &vpx_image, timestamp, duration,
flags, deadline);
if (vpx_error != VPX_CODEC_OK) {
std::string msg = base::StringPrintf("VPX encoding error: %s (%s)",
vpx_codec_err_to_string(vpx_error),
vpx_codec_error_detail(codec_));
status = Status(StatusCode::kEncoderFailedEncode, msg)
.WithData("vpx_error", vpx_error);
std::move(done_cb).Run(std::move(status));
return;
}
DrainOutputs();
std::move(done_cb).Run(Status());
}
void VpxVideoEncoder::ChangeOptions(const Options& options, StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
vpx_codec_enc_cfg_t new_config = codec_config_;
auto status = SetUpVpxConfig(options, &new_config);
if (status.is_ok()) {
auto vpx_error = vpx_codec_enc_config_set(codec_, &new_config);
if (vpx_error == VPX_CODEC_OK) {
codec_config_ = new_config;
options_ = options;
} else {
status = Status(StatusCode::kEncoderUnsupportedConfig,
"Failed to set new VPX config")
.WithData("vpx_error", vpx_error);
}
}
std::move(done_cb).Run(std::move(status));
return;
}
uint64_t VpxVideoEncoder::GetFrameDuration(const VideoFrame& frame) {
base::TimeDelta default_duration =
base::TimeDelta::FromSecondsD(1.0 / options_.framerate);
return frame.metadata()
->frame_duration.value_or(default_duration)
.InMicroseconds();
}
VpxVideoEncoder::~VpxVideoEncoder() {
if (!codec_)
return;
auto error = vpx_codec_destroy(codec_);
DCHECK_EQ(error, VPX_CODEC_OK);
delete codec_;
}
void VpxVideoEncoder::Flush(StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
auto vpx_error = vpx_codec_encode(codec_, nullptr, -1, 0, 0, 0);
if (vpx_error != VPX_CODEC_OK) {
std::string msg = base::StringPrintf("VPX flushing error: %s (%s)",
vpx_codec_err_to_string(vpx_error),
vpx_codec_error_detail(codec_));
Status status = Status(StatusCode::kEncoderFailedEncode, msg)
.WithData("vpx_error", vpx_error);
std::move(done_cb).Run(std::move(status));
return;
}
DrainOutputs();
std::move(done_cb).Run(Status());
}
void VpxVideoEncoder::DrainOutputs() {
vpx_codec_iter_t iter = nullptr;
const vpx_codec_cx_pkt_t* pkt = nullptr;
while ((pkt = vpx_codec_get_cx_data(codec_, &iter))) {
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
VideoEncoderOutput result;
result.key_frame = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
result.timestamp = base::TimeDelta::FromMicroseconds(pkt->data.frame.pts);
result.size = pkt->data.frame.sz;
result.data.reset(new uint8_t[result.size]);
memcpy(result.data.get(), pkt->data.frame.buf, result.size);
output_cb_.Run(std::move(result));
}
}
}
} // namespace media