blob: 53de94db12ba7377159eb80e4555a7d5d74a741a [file] [log] [blame]
// 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 "third_party/blink/renderer/modules/mediarecorder/media_recorder_encoder_wrapper.h"
#include "base/containers/contains.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/video_encoder_metrics_provider.h"
#include "media/base/video_frame.h"
#include "media/media_buildflags.h"
#include "media/video/alpha_video_encoder_wrapper.h"
#include "media/video/gpu_video_accelerator_factories.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
namespace blink {
MediaRecorderEncoderWrapper::EncodeTask::EncodeTask(
scoped_refptr<media::VideoFrame> frame,
base::TimeTicks capture_timestamp,
bool request_keyframe)
: frame(std::move(frame)),
capture_timestamp(capture_timestamp),
request_keyframe(request_keyframe) {}
MediaRecorderEncoderWrapper::EncodeTask::~EncodeTask() = default;
MediaRecorderEncoderWrapper::VideoParamsAndTimestamp::VideoParamsAndTimestamp(
const media::Muxer::VideoParameters& params,
base::TimeTicks timestamp)
: params(params), timestamp(timestamp) {}
MediaRecorderEncoderWrapper::VideoParamsAndTimestamp::
~VideoParamsAndTimestamp() = default;
MediaRecorderEncoderWrapper::MediaRecorderEncoderWrapper(
scoped_refptr<base::SequencedTaskRunner> encoding_task_runner,
media::VideoCodecProfile profile,
uint32_t bits_per_second,
bool is_screencast,
media::GpuVideoAcceleratorFactories* gpu_factories,
CreateEncoderCB create_encoder_cb,
VideoTrackRecorder::OnEncodedVideoCB on_encoded_video_cb,
OnErrorCB on_error_cb)
: Encoder(std::move(encoding_task_runner),
on_encoded_video_cb,
bits_per_second),
gpu_factories_(gpu_factories),
profile_(profile),
codec_(media::VideoCodecProfileToVideoCodec(profile_)),
create_encoder_cb_(create_encoder_cb),
on_error_cb_(std::move(on_error_cb)) {
DETACH_FROM_SEQUENCE(sequence_checker_);
CHECK(create_encoder_cb_);
CHECK(on_error_cb_);
constexpr media::VideoCodec kSupportedCodecs[] = {
media::VideoCodec::kH264,
media::VideoCodec::kVP8,
media::VideoCodec::kVP9,
media::VideoCodec::kAV1,
};
CHECK(base::Contains(kSupportedCodecs, codec_));
options_.latency_mode = media::VideoEncoder::LatencyMode::Quality;
options_.bitrate = media::Bitrate::VariableBitrate(
bits_per_second, base::ClampMul(bits_per_second, 2u).RawValue());
options_.content_hint = is_screencast
? media::VideoEncoder::ContentHint::Screen
: media::VideoEncoder::ContentHint::Camera;
if (codec_ == media::VideoCodec::kH264) {
options_.avc.produce_annexb = true;
}
}
MediaRecorderEncoderWrapper::~MediaRecorderEncoderWrapper() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool MediaRecorderEncoderWrapper::CanEncodeAlphaChannel() const {
// Alpha encoding is supported only with VP8 and VP9 software encoders.
return !gpu_factories_ && (codec_ == media::VideoCodec::kVP8 ||
codec_ == media::VideoCodec::kVP9);
}
bool MediaRecorderEncoderWrapper::IsScreenContentEncodingForTesting() const {
return options_.content_hint.has_value() &&
*options_.content_hint == media::VideoEncoder::ContentHint::Screen;
}
void MediaRecorderEncoderWrapper::EnterErrorState(
const media::EncoderStatus& status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ == State::kInError) {
CHECK(!on_error_cb_);
return;
}
metrics_provider_->SetError(status);
state_ = State::kInError;
pending_encode_tasks_ = {};
params_in_encode_ = {};
CHECK(on_error_cb_);
std::move(on_error_cb_).Run();
}
void MediaRecorderEncoderWrapper::Reconfigure(const gfx::Size& frame_size,
bool encode_alpha) {
TRACE_EVENT2(
"media", "MediaRecorderEncoderWrapper::ReconfigureForNewResolution",
"frame_size", frame_size.ToString(), "encode_alpha", encode_alpha);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(encoder_);
CHECK_NE(state_, State::kInError);
state_ = State::kInitializing;
encoder_->Flush(
WTF::BindOnce(&MediaRecorderEncoderWrapper::CreateAndInitialize,
weak_factory_.GetWeakPtr(), frame_size, encode_alpha));
}
void MediaRecorderEncoderWrapper::CreateAndInitialize(
const gfx::Size& frame_size,
bool encode_alpha,
media::EncoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT1("media", "MediaRecorderEncoderWrapper::CreateAndInitialize",
"frame_size", frame_size.ToString());
if (!status.is_ok()) {
// CreateAndInitialize() is called (1) in VideoFrameBuffer() for the first
// encoder creation with status=kOk and as Flush done callback. The status
// can be non kOk only if it is invoked as flush callback.
DLOG(ERROR) << "Flush() failed: " << status.message();
EnterErrorState(status);
return;
}
CHECK_NE(state_, State::kInError);
CHECK(!encoder_ || state_ == State::kInitializing)
<< ", unexpected status: " << static_cast<int>(state_);
state_ = State::kInitializing;
options_.frame_size = frame_size;
encode_alpha_ = encode_alpha;
if (encode_alpha_) {
CHECK(CanEncodeAlphaChannel());
auto yuv_encoder = create_encoder_cb_.Run(gpu_factories_);
auto alpha_encoder = create_encoder_cb_.Run(gpu_factories_);
CHECK(yuv_encoder && alpha_encoder);
encoder_ = std::make_unique<media::AlphaVideoEncoderWrapper>(
std::move(yuv_encoder), std::move(alpha_encoder));
} else {
encoder_ = create_encoder_cb_.Run(gpu_factories_);
}
CHECK(encoder_);
// MediaRecorderEncoderWrapper doesn't require an encoder to post a callback
// because a given |on_encoded_video_cb_| already hops a thread.
encoder_->DisablePostedCallbacks();
metrics_provider_->Initialize(profile_, options_.frame_size,
/*is_hardware_encoder=*/gpu_factories_);
encoder_->Initialize(
profile_, options_,
/*info_cb=*/base::DoNothing(),
WTF::BindRepeating(&MediaRecorderEncoderWrapper::OutputEncodeData,
weak_factory_.GetWeakPtr()),
WTF::BindOnce(&MediaRecorderEncoderWrapper::InitializeDone,
weak_factory_.GetWeakPtr()));
}
void MediaRecorderEncoderWrapper::InitializeDone(media::EncoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("media", "MediaRecorderEncoderWrapper::InitizalizeDone");
if (!status.is_ok()) {
DLOG(ERROR) << "Initialize() failed: " << status.message();
EnterErrorState(status);
return;
}
CHECK_NE(state_, State::kInError);
state_ = State::kEncoding;
EncodePendingTasks();
}
void MediaRecorderEncoderWrapper::EncodeFrame(
scoped_refptr<media::VideoFrame> frame,
base::TimeTicks capture_timestamp,
bool request_keyframe) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("media", "MediaRecorderEncoderWrapper::EncodeFrame");
if (state_ == State::kInError) {
CHECK(!on_error_cb_);
return;
}
pending_encode_tasks_.emplace_back(std::move(frame), capture_timestamp,
request_keyframe);
if (state_ == State::kEncoding) {
EncodePendingTasks();
}
}
void MediaRecorderEncoderWrapper::EncodePendingTasks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
while (state_ == State::kEncoding && !pending_encode_tasks_.empty()) {
auto& task = pending_encode_tasks_.front();
const gfx::Size& frame_size = task.frame->visible_rect().size();
CHECK(media::IsOpaque(task.frame->format()) ||
task.frame->format() == media::PIXEL_FORMAT_I420A);
const bool need_alpha_encode =
task.frame->format() == media::PIXEL_FORMAT_I420A;
// When a frame size is different from the current frame size (or first
// Encode() call), encoder needs to be re-created because
// media::VideoEncoder don't support all resolution change cases.
// If |encoder_| exists, we first Flush() to not drop frames being encoded.
if (frame_size != options_.frame_size ||
encode_alpha_ != need_alpha_encode) {
if (encoder_) {
Reconfigure(frame_size, need_alpha_encode);
} else {
// Only first Encode() call.
CreateAndInitialize(frame_size, need_alpha_encode,
media::EncoderStatus::Codes::kOk);
}
return;
}
params_in_encode_.emplace_back(media::Muxer::VideoParameters(*task.frame),
task.capture_timestamp);
bool request_keyframe = task.request_keyframe;
auto frame = std::move(task.frame);
pending_encode_tasks_.pop_front();
// Encode() calls EncodeDone() and OutputEncodeData() within a call because
// we DisablePostedCallbacks(). Therefore, |params_in_encode_| and
// |pending_encode_tasks_| must be changed before calling Encode().
encoder_->Encode(std::move(frame),
media::VideoEncoder::EncodeOptions(request_keyframe),
WTF::BindOnce(&MediaRecorderEncoderWrapper::EncodeDone,
weak_factory_.GetWeakPtr()));
}
}
void MediaRecorderEncoderWrapper::EncodeDone(media::EncoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!status.is_ok()) {
DLOG(ERROR) << "EncodeDone() failed: " << status.message();
EnterErrorState(status);
return;
}
}
void MediaRecorderEncoderWrapper::OutputEncodeData(
media::VideoEncoderOutput output,
std::optional<media::VideoEncoder::CodecDescription> description) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("media", "MediaRecorderEncoderWrapper::OutputEncodeData");
if (state_ == State::kInError) {
CHECK(!on_error_cb_);
return;
}
metrics_provider_->IncrementEncodedFrameCount();
// TODO(crbug.com/1330919): Check OutputEncodeData() in the same order as
// Encode().
CHECK(!params_in_encode_.empty());
auto [video_params, capture_timestamp] = std::move(params_in_encode_.front());
params_in_encode_.pop_front();
video_params.codec = codec_;
on_encoded_video_cb_.Run(
video_params,
std::string(reinterpret_cast<const char*>(output.data.get()),
output.size),
encode_alpha_
? std::string(reinterpret_cast<const char*>(output.alpha_data.get()),
output.alpha_size)
: std::string(),
std::move(description), capture_timestamp, output.key_frame);
}
} // namespace blink