blob: c87dfc7ba5d8ac2c0f2e4f982e8f938974676896 [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 "ash/services/recording/recording_encoder_muxer.h"
#include "ash/services/recording/recording_service_constants.h"
#include "base/bind.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "media/base/audio_codecs.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
namespace recording {
namespace {
// The video encoder is initialized asynchronously, and until that happens, all
// received video frames are added to |pending_video_frames_|. However, in order
// to avoid an OOM situation if the encoder takes too long to initialize or it
// never does, we impose an upper-bound to the number of pending frames. The
// below value is equal to the maximum number of in-flight frames that the
// capturer uses (See |viz::FrameSinkVideoCapturerImpl::kDesignLimitMaxFrames|)
// before it stops sending frames. Once we hit that limit in
// |pending_video_frames_|, we will start dropping frames to let the capturer
// proceed, with an upper limit of how many frames we can drop that is
// equivalent to 4 seconds, after which we'll declare an encoder initialization
// failure.
constexpr size_t kMaxPendingFrames = 10;
constexpr size_t kMaxDroppedFrames = 4 * kMaxFrameRate;
} // namespace
// static
base::SequenceBound<RecordingEncoderMuxer> RecordingEncoderMuxer::Create(
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
const media::VideoEncoder::Options& video_encoder_options,
const media::AudioParameters* audio_input_params,
media::WebmMuxer::WriteDataCB muxer_output_callback,
FailureCallback on_failure_callback) {
return base::SequenceBound<RecordingEncoderMuxer>(
std::move(blocking_task_runner), video_encoder_options,
audio_input_params, std::move(muxer_output_callback),
std::move(on_failure_callback));
}
void RecordingEncoderMuxer::InitializeVideoEncoder(
const media::VideoEncoder::Options& video_encoder_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Note: The VpxVideoEncoder supports changing the encoding options
// dynamically, but it won't work for all frame size changes and may cause
// encoding failures. Therefore, it's better to recreate and reinitialize a
// new encoder. See media::VpxVideoEncoder::ChangeOptions() for more details.
if (video_encoder_ && is_video_encoder_initialized_) {
auto* encoder_ptr = video_encoder_.get();
encoder_ptr->Flush(base::BindOnce(
// Holds on to the old encoder until it flushes its buffers, then
// destroys it.
[](std::unique_ptr<media::VpxVideoEncoder> old_encoder,
media::Status status) {},
std::move(video_encoder_)));
}
is_video_encoder_initialized_ = false;
video_encoder_ = std::make_unique<media::VpxVideoEncoder>();
video_encoder_->Initialize(
media::VP8PROFILE_ANY, video_encoder_options,
base::BindRepeating(&RecordingEncoderMuxer::OnVideoEncoderOutput,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&RecordingEncoderMuxer::OnVideoEncoderInitialized,
weak_ptr_factory_.GetWeakPtr(), video_encoder_.get()));
}
void RecordingEncoderMuxer::EncodeVideo(
scoped_refptr<media::VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_video_encoder_initialized_) {
EncodeVideoImpl(std::move(frame));
return;
}
pending_video_frames_.push_back(std::move(frame));
if (pending_video_frames_.size() == kMaxPendingFrames) {
pending_video_frames_.pop_front();
DCHECK_LT(pending_video_frames_.size(), kMaxPendingFrames);
if (++num_dropped_frames_ >= kMaxDroppedFrames) {
LOG(ERROR) << "Video encoder took too long to initialize.";
NotifyFailure(FailureType::kEncoderInitialization, /*for_video=*/true);
}
}
}
void RecordingEncoderMuxer::EncodeAudio(
std::unique_ptr<media::AudioBus> audio_bus,
base::TimeTicks capture_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(audio_encoder_);
if (!did_failure_occur())
audio_encoder_->EncodeAudio(*audio_bus, capture_time);
}
void RecordingEncoderMuxer::FlushAndFinalize(base::OnceClosure on_done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Note that flushing the audio encoder is synchronous, so calling Flush() on
// it will result in OnAudioEncoded() being called directly (if any audio
// frames were still buffered and not processed). The video encoder responds
// asynchronously.
if (audio_encoder_)
audio_encoder_->Flush();
video_encoder_->Flush(
base::BindOnce(&RecordingEncoderMuxer::OnVideoEncoderFlushed,
weak_ptr_factory_.GetWeakPtr(), std::move(on_done)));
}
RecordingEncoderMuxer::RecordingEncoderMuxer(
const media::VideoEncoder::Options& video_encoder_options,
const media::AudioParameters* audio_input_params,
media::WebmMuxer::WriteDataCB muxer_output_callback,
FailureCallback on_failure_callback)
: webm_muxer_(media::kCodecOpus,
/*has_video_=*/true,
/*has_audio_=*/!!audio_input_params,
muxer_output_callback),
on_failure_callback_(std::move(on_failure_callback)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (audio_input_params) {
audio_encoder_ = std::make_unique<media::AudioOpusEncoder>(
*audio_input_params,
base::BindRepeating(&RecordingEncoderMuxer::OnAudioEncoded,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&RecordingEncoderMuxer::OnEncoderStatus,
weak_ptr_factory_.GetWeakPtr(),
/*for_video=*/false),
// 0 means the encoder picks bitrate automatically.
/*bits_per_second=*/0);
}
InitializeVideoEncoder(video_encoder_options);
}
RecordingEncoderMuxer::~RecordingEncoderMuxer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void RecordingEncoderMuxer::OnVideoEncoderInitialized(
media::VpxVideoEncoder* encoder,
media::Status status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Ignore initialization of encoders that were removed as part of
// reinitialization.
if (video_encoder_.get() != encoder)
return;
if (!status.is_ok()) {
LOG(ERROR) << "Could not initialize the video encoder: "
<< status.message();
NotifyFailure(FailureType::kEncoderInitialization,
/*for_video=*/true);
return;
}
is_video_encoder_initialized_ = true;
for (auto& frame : pending_video_frames_)
EncodeVideoImpl(frame);
pending_video_frames_.clear();
}
void RecordingEncoderMuxer::EncodeVideoImpl(
scoped_refptr<media::VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_video_encoder_initialized_);
if (did_failure_occur())
return;
video_visible_rect_sizes_.push(frame->visible_rect().size());
video_encoder_->Encode(
frame, /*key_frame=*/false,
base::BindOnce(&RecordingEncoderMuxer::OnEncoderStatus,
weak_ptr_factory_.GetWeakPtr(), /*for_video=*/true));
}
void RecordingEncoderMuxer::OnVideoEncoderOutput(
media::VideoEncoderOutput output,
base::Optional<media::VideoEncoder::CodecDescription> codec_description) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
media::WebmMuxer::VideoParameters params(video_visible_rect_sizes_.front(),
kMaxFrameRate, media::kCodecVP8,
kColorSpace);
video_visible_rect_sizes_.pop();
// TODO(crbug.com/1143798): Explore changing the WebmMuxer so it doesn't work
// with strings, to avoid copying the encoded data.
std::string data{reinterpret_cast<const char*>(output.data.get()),
output.size};
webm_muxer_.OnEncodedVideo(params, std::move(data), std::string(),
base::TimeTicks() + output.timestamp,
output.key_frame);
}
void RecordingEncoderMuxer::OnAudioEncoded(
media::EncodedAudioBuffer encoded_audio) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(audio_encoder_);
// TODO(crbug.com/1143798): Explore changing the WebmMuxer so it doesn't work
// with strings, to avoid copying the encoded data.
std::string encoded_data{
reinterpret_cast<const char*>(encoded_audio.encoded_data.get()),
encoded_audio.encoded_data_size};
webm_muxer_.OnEncodedAudio(encoded_audio.params, std::move(encoded_data),
encoded_audio.timestamp);
}
void RecordingEncoderMuxer::OnVideoEncoderFlushed(base::OnceClosure on_done,
media::Status status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!status.is_ok()) {
LOG(ERROR) << "Could not flush remaining video frames: "
<< status.message();
}
webm_muxer_.Flush();
std::move(on_done).Run();
}
void RecordingEncoderMuxer::OnEncoderStatus(bool for_video,
media::Status status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (status.is_ok())
return;
LOG(ERROR) << "Failed to encode " << (for_video ? "video" : "audio")
<< " frame: " << status.message();
NotifyFailure(FailureType::kEncoding, for_video);
}
void RecordingEncoderMuxer::NotifyFailure(FailureType type, bool for_video) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (on_failure_callback_)
std::move(on_failure_callback_).Run(type, for_video);
}
} // namespace recording