blob: 552b55f1834e3979f39a935596c1c2daab4e74b2 [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/alpha_video_encoder_wrapper.h"
#include <algorithm>
#include <cmath>
#include "base/logging.h"
#include "base/numerics/checked_math.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/svc_scalability_mode.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_color_space.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/video/video_encoder_info.h"
namespace media {
AlphaVideoEncoderWrapper::AlphaVideoEncoderWrapper(
std::unique_ptr<VideoEncoder> yuv_encoder,
std::unique_ptr<VideoEncoder> alpha_encoder)
: yuv_encoder_(std::move(yuv_encoder)),
alpha_encoder_(std::move(alpha_encoder)) {
CHECK(yuv_encoder_);
CHECK(alpha_encoder_);
}
void AlphaVideoEncoderWrapper::Initialize(VideoCodecProfile profile,
const Options& options,
EncoderInfoCB info_cb,
OutputCB output_cb,
EncoderStatusCB done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
output_cb_ = BindCallbackToCurrentLoopIfNeeded(std::move(output_cb));
if (options.scalability_mode.has_value()) {
std::move(done_cb).Run(EncoderStatus::Codes::kEncoderUnsupportedConfig);
return;
}
Options yuv_options = options;
Options alpha_options = options;
// Split bitrate 3 quarters to color planes and 1 quarter to alpha.
if (options.bitrate.has_value()) {
auto& bitrate = options.bitrate.value();
switch (bitrate.mode()) {
case Bitrate::Mode::kConstant:
yuv_options.bitrate =
Bitrate::ConstantBitrate(bitrate.target_bps() / 4 * 3);
alpha_options.bitrate =
Bitrate::ConstantBitrate(bitrate.target_bps() / 4);
break;
case Bitrate::Mode::kVariable:
yuv_options.bitrate = Bitrate::VariableBitrate(
bitrate.target_bps() / 4 * 3, bitrate.peak_bps() / 4 * 3);
alpha_options.bitrate = Bitrate::VariableBitrate(
bitrate.target_bps() / 4, bitrate.peak_bps() / 4);
break;
case Bitrate::Mode::kExternal:
break;
}
}
auto done_callback = [](base::WeakPtr<AlphaVideoEncoderWrapper> self,
EncoderStatus status) {
if (!self) {
NOTREACHED_IN_MIGRATION() << "Underlying encoder must be synchronous";
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
if (!self->init_status_.has_value() || self->init_status_->is_ok()) {
self->init_status_ = std::move(status);
}
};
yuv_encoder_->DisablePostedCallbacks();
alpha_encoder_->DisablePostedCallbacks();
init_status_.reset();
yuv_encoder_->Initialize(
profile, yuv_options, std::move(info_cb),
base::BindRepeating(&AlphaVideoEncoderWrapper::YuvOutputCallback,
weak_factory_.GetWeakPtr()),
base::BindOnce(done_callback, weak_factory_.GetWeakPtr()));
alpha_encoder_->Initialize(
profile, alpha_options, EncoderInfoCB(),
base::BindRepeating(&AlphaVideoEncoderWrapper::AlphaOutputCallback,
weak_factory_.GetWeakPtr()),
base::BindOnce(done_callback, weak_factory_.GetWeakPtr()));
CHECK(init_status_.has_value());
std::move(done_cb).Run(std::move(init_status_).value());
}
void AlphaVideoEncoderWrapper::Encode(scoped_refptr<VideoFrame> frame,
const EncodeOptions& encode_options,
EncoderStatusCB done_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
if (frame->format() != PIXEL_FORMAT_I420A) {
std::move(done_cb).Run(EncoderStatus::Codes::kUnsupportedFrameFormat);
return;
}
const gfx::Size frame_size = frame->coded_size();
auto dummy_plane_size =
VideoFrame::PlaneSize(frame->format(), VideoFrame::Plane::kV, frame_size)
.Area64();
if (dummy_plane_size != dummy_uv_planes_.size()) {
// It is more expensive to encode 0x00, so use 0x80 instead.
dummy_uv_planes_.resize(dummy_plane_size, 0x80);
}
yuv_output_.reset();
alpha_output_.reset();
encode_status_.reset();
auto uv_stride = VideoFrame::RowBytes(VideoFrame::Plane::kU, frame->format(),
frame_size.width());
auto yuv_frame = WrapAsI420VideoFrame(frame);
auto alpha_frame = VideoFrame::WrapExternalYuvData(
PIXEL_FORMAT_I420, frame->visible_rect().size(), frame->visible_rect(),
frame->natural_size(), frame->stride(VideoFrame::Plane::kA), uv_stride,
uv_stride, frame->visible_data(VideoFrame::Plane::kA),
dummy_uv_planes_.data(), dummy_uv_planes_.data(), frame->timestamp());
alpha_frame->metadata().MergeMetadataFrom(frame->metadata());
auto done_callback = [](base::WeakPtr<AlphaVideoEncoderWrapper> self,
EncoderStatus status) {
if (!self) {
NOTREACHED_IN_MIGRATION() << "Underlying encoder must be synchronous";
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
if (!self->encode_status_.has_value() || self->encode_status_->is_ok()) {
self->encode_status_ = std::move(status);
}
};
yuv_encoder_->Encode(
yuv_frame, encode_options,
base::BindOnce(done_callback, weak_factory_.GetWeakPtr()));
alpha_encoder_->Encode(
alpha_frame, encode_options,
base::BindOnce(done_callback, weak_factory_.GetWeakPtr()));
if (!yuv_output_.has_value() || !alpha_output_.has_value()) {
// This wrapper can only work with synchronous encoders that are completely
// done encoding by the time Encode() completed.
// So if we don't have the status and outputs it's time to give up.
CHECK(encode_status_.has_value());
std::move(done_cb).Run(*encode_status_);
return;
}
if (encode_status_->is_ok()) {
if (yuv_output_->key_frame && !alpha_output_->key_frame) {
// Alpha keyframe must always go with YUV keyframe.
std::move(done_cb).Run(EncoderStatus::Codes::kEncoderIllegalState);
return;
}
VideoEncoderOutput output = std::move(yuv_output_).value();
output.alpha_data = std::move(alpha_output_->data);
output_cb_.Run(std::move(output), {});
}
std::move(done_cb).Run(std::move(encode_status_).value());
}
void AlphaVideoEncoderWrapper::ChangeOptions(const Options& options,
OutputCB output_cb,
EncoderStatusCB done_cb) {
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
NOTREACHED_IN_MIGRATION() << "Not implemented. Implement when needed.";
std::move(done_cb).Run(EncoderStatus::Codes::kEncoderUnsupportedConfig);
}
AlphaVideoEncoderWrapper::~AlphaVideoEncoderWrapper() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void AlphaVideoEncoderWrapper::Flush(EncoderStatusCB done_cb) {
done_cb = BindCallbackToCurrentLoopIfNeeded(std::move(done_cb));
std::move(done_cb).Run(EncoderStatus::Codes::kOk);
}
void AlphaVideoEncoderWrapper::YuvOutputCallback(
VideoEncoderOutput output,
std::optional<CodecDescription> desc) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (desc.has_value()) {
NOTREACHED_IN_MIGRATION()
<< "AlphaVideoEncoderWrapper doesn't support codecs with extra data";
return;
}
yuv_output_.emplace(std::move(output));
}
void AlphaVideoEncoderWrapper::AlphaOutputCallback(
VideoEncoderOutput output,
std::optional<CodecDescription> desc) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (desc.has_value()) {
NOTREACHED_IN_MIGRATION()
<< "AlphaVideoEncoderWrapper doesn't support codecs with extra data";
return;
}
alpha_output_.emplace(std::move(output));
}
} // namespace media