blob: 6f44e6b483302b767ab4d19dc297bedd284fbe62 [file] [log] [blame]
// Copyright 2022 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/gpu/chromeos/oop_video_decoder.h"
#include "base/memory/ptr_util.h"
#include "media/base/bind_to_current_loop.h"
#include "media/gpu/macros.h"
#include "media/mojo/common/mojo_decoder_buffer_converter.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
// Throughout this file, we have sprinkled many CHECK()s to assert invariants
// that should hold regardless of the behavior of the remote decoder or
// untrusted client. We use CHECK()s instead of DCHECK()s because
// OOPVideoDecoder and associated classes are very stateful so:
//
// a) They're hard to reason about.
// b) They're hard to fully exercise with tests.
// c) It's hard to reason if the violation of an invariant can have security
// implications because once we enter into a bad state, everything is fair
// game.
//
// Hence it's safer to crash and surface those crashes.
//
// More specifically:
//
// - It's illegal to call many methods if OOPVideoDecoder enters into an error
// state (tracked by |has_error_|).
//
// - The media::VideoDecoder interface demands that its users don't call certain
// methods while in specific states. An OOPVideoDecoder is used by an
// in-process class (the VideoDecoderPipeline) to communicate with an
// out-of-process video decoder. Therefore, we trust that the in-process user
// of this class abides by the requirements of the media::VideoDecoder
// interface and thus, we don't handle violations gracefully. In particular:
//
// - No media::VideoDecoder methods should be called before the |init_cb|
// passed to Initialize() is called. We track this interim state with
// |init_cb_|.
//
// - Initialize() should not be called while there are pending decodes (i.e.,
// while !pending_decodes_.empty()).
//
// - No media::VideoDecoder methods should be called before the |closure|
// passed to Reset() is called. We track this interim state with
// |reset_cb_|.
// TODO(b/220915557): OOPVideoDecoder cannot trust |remote_decoder_| (but
// |remote_decoder_| might trust us). We need to audit this class to make sure:
//
// - That OOPVideoDecoder validates everything coming from
// |remote_video_decoder_|.
//
// - That OOPVideoDecoder meets the requirements of the media::VideoDecoder and
// the media::VideoDecoderMixin interfaces. For example, we need to make sure
// we guarantee statements like "all pending Decode() requests will be
// finished or aborted before |closure| is called" (for
// VideoDecoder::Reset()).
//
// - That OOPVideoDecoder asserts it's not being misused (which might cause us
// to violate the requirements of the StableVideoDecoder interface). For
// example, the StableVideoDecoder interface says for Decode(): "this must not
// be called while there are pending Initialize(), Reset(), or Decode(EOS)
// requests."
namespace media {
// static
std::unique_ptr<VideoDecoderMixin> OOPVideoDecoder::Create(
mojo::PendingRemote<stable::mojom::StableVideoDecoder>
pending_remote_decoder,
std::unique_ptr<media::MediaLog> media_log,
scoped_refptr<base::SequencedTaskRunner> decoder_task_runner,
base::WeakPtr<VideoDecoderMixin::Client> client) {
// TODO(b/171813538): make the destructor of this class (as well as the
// destructor of sister class VaapiVideoDecoder) public so the explicit
// argument can be removed from this call to base::WrapUnique().
return base::WrapUnique<VideoDecoderMixin>(new OOPVideoDecoder(
std::move(media_log), std::move(decoder_task_runner), std::move(client),
std::move(pending_remote_decoder)));
}
OOPVideoDecoder::OOPVideoDecoder(
std::unique_ptr<media::MediaLog> media_log,
scoped_refptr<base::SequencedTaskRunner> decoder_task_runner,
base::WeakPtr<VideoDecoderMixin::Client> client,
mojo::PendingRemote<stable::mojom::StableVideoDecoder>
pending_remote_decoder)
: VideoDecoderMixin(std::move(media_log),
std::move(decoder_task_runner),
std::move(client)),
remote_decoder_(std::move(pending_remote_decoder)),
weak_this_factory_(this) {
VLOGF(2);
DCHECK(decoder_task_runner_->RunsTasksInCurrentSequence());
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Set a connection error handler in case the remote decoder gets
// disconnected, for instance, if the remote decoder process crashes.
// The remote decoder lives in a utility process (for lacros-chrome,
// this utility process is in ash-chrome).
// base::Unretained() is safe because `this` owns the `mojo::Remote`.
remote_decoder_.set_disconnect_handler(
base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this)));
// TODO(b/195769334): |remote_consumer_handle| corresponds to the data pipe
// that allows us to send data to the out-of-process video decoder. This data
// pipe is separate from the one set up by renderers to send data to the GPU
// process. Therefore, we're introducing the need for copying the encoded data
// from one pipe to the other. Ideally, we would just forward the pipe
// endpoint directly to the out-of-process video decoder and avoid the extra
// copy. This would require us to plumb the mojo::ScopedDataPipeConsumerHandle
// from the MojoVideoDecoderService all the way here.
mojo::ScopedDataPipeConsumerHandle remote_consumer_handle;
mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create(
GetDefaultDecoderBufferConverterCapacity(DemuxerStream::VIDEO),
&remote_consumer_handle);
CHECK(mojo_decoder_buffer_writer_);
DCHECK(!stable_video_frame_handle_releaser_remote_.is_bound());
mojo::PendingReceiver<stable::mojom::VideoFrameHandleReleaser>
stable_video_frame_handle_releaser_receiver =
stable_video_frame_handle_releaser_remote_
.BindNewPipeAndPassReceiver();
// base::Unretained() is safe because `this` owns the `mojo::Remote`.
stable_video_frame_handle_releaser_remote_.set_disconnect_handler(
base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this)));
DCHECK(!stable_media_log_receiver_.is_bound());
CHECK(!has_error_);
// TODO(b/171813538): plumb the remaining parameters.
remote_decoder_->Construct(
client_receiver_.BindNewEndpointAndPassRemote(),
stable_media_log_receiver_.BindNewPipeAndPassRemote(),
std::move(stable_video_frame_handle_releaser_receiver),
std::move(remote_consumer_handle), gfx::ColorSpace());
}
OOPVideoDecoder::~OOPVideoDecoder() {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& pending_decode : pending_decodes_) {
decoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(pending_decode.second),
DecoderStatus::Codes::kAborted));
}
}
void OOPVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!init_cb_);
CHECK(pending_decodes_.empty());
CHECK(!reset_cb_);
if (has_error_) {
// TODO(b/171813538): create specific error code for this decoder.
std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
return;
}
// TODO(b/171813538): implement the re-initialization logic. The client can
// call Initialize() multiple times. This implies properly asserting the
// invariant imposed by media::VideoDecoder::Initialize(): "No VideoDecoder
// calls should be made before |init_cb| is executed."
if (output_cb_) {
// TODO(b/171813538): create specific error code for this decoder.
std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
return;
}
init_cb_ = std::move(init_cb);
output_cb_ = output_cb;
waiting_cb_ = waiting_cb;
// TODO(b/171813538): plumb protected content.
mojo::PendingRemote<stable::mojom::StableCdmContext>
pending_remote_cdm_context;
remote_decoder_->Initialize(config, low_delay,
std::move(pending_remote_cdm_context),
base::BindOnce(&OOPVideoDecoder::OnInitializeDone,
weak_this_factory_.GetWeakPtr()));
}
void OOPVideoDecoder::OnInitializeDone(const DecoderStatus& status,
bool needs_bitstream_conversion,
int32_t max_decode_requests,
VideoDecoderType decoder_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
if (!status.is_ok() || (decoder_type != VideoDecoderType::kVaapi &&
decoder_type != VideoDecoderType::kV4L2)) {
Stop();
return;
}
decoder_type_ = decoder_type;
std::move(init_cb_).Run(status);
}
void OOPVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
DecodeCB decode_cb) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!init_cb_);
CHECK(!reset_cb_);
if (has_error_ || decoder_type_ == VideoDecoderType::kUnknown) {
decoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(decode_cb),
DecoderStatus::Codes::kNotInitialized));
return;
}
if (decode_counter_ == std::numeric_limits<uint64_t>::max()) {
// Error out in case of overflow.
decoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
return;
}
const uint64_t decode_id = decode_counter_++;
pending_decodes_.insert({decode_id, std::move(decode_cb)});
const bool is_flushing = buffer->end_of_stream();
CHECK(buffer);
mojom::DecoderBufferPtr mojo_buffer =
mojo_decoder_buffer_writer_->WriteDecoderBuffer(buffer);
if (!mojo_buffer) {
Stop();
return;
}
remote_decoder_->Decode(
std::move(buffer),
base::BindOnce(&OOPVideoDecoder::OnDecodeDone,
weak_this_factory_.GetWeakPtr(), decode_id, is_flushing));
}
void OOPVideoDecoder::OnDecodeDone(uint64_t decode_id,
bool is_flushing,
const DecoderStatus& status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
// Check that decode callbacks are called in the same order as Decode().
CHECK(!pending_decodes_.empty());
if (pending_decodes_.cbegin()->first != decode_id) {
VLOGF(2) << "Unexpected decode callback for request " << decode_id;
Stop();
return;
}
// Check that the |decode_cb| corresponding to the flush is not called until
// the decode callback has been called for each pending decode.
if (is_flushing && pending_decodes_.size() != 1) {
VLOGF(2) << "Received a flush callback while having pending decodes";
Stop();
return;
}
auto it = pending_decodes_.begin();
DecodeCB decode_cb = std::move(it->second);
pending_decodes_.erase(it);
std::move(decode_cb).Run(status);
}
void OOPVideoDecoder::Reset(base::OnceClosure reset_cb) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!init_cb_);
CHECK(!reset_cb_);
if (has_error_ || decoder_type_ == VideoDecoderType::kUnknown) {
std::move(reset_cb).Run();
return;
}
reset_cb_ = std::move(reset_cb);
remote_decoder_->Reset(base::BindOnce(&OOPVideoDecoder::OnResetDone,
weak_this_factory_.GetWeakPtr()));
}
void OOPVideoDecoder::OnResetDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
CHECK(reset_cb_);
if (!pending_decodes_.empty()) {
VLOGF(2) << "Received a reset callback while having pending decodes";
Stop();
return;
}
std::move(reset_cb_).Run();
}
void OOPVideoDecoder::Stop() {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (has_error_)
return;
has_error_ = true;
// There may be in-flight decode, initialize or reset callbacks.
// Invalidate any outstanding weak pointers so those callbacks are ignored.
weak_this_factory_.InvalidateWeakPtrs();
// |init_cb_| is likely to reentrantly destruct |this|, so we check for that
// using an on-stack WeakPtr.
base::WeakPtr<OOPVideoDecoder> weak_this = weak_this_factory_.GetWeakPtr();
client_receiver_.reset();
stable_media_log_receiver_.reset();
remote_decoder_.reset();
mojo_decoder_buffer_writer_.reset();
stable_video_frame_handle_releaser_remote_.reset();
if (init_cb_)
std::move(init_cb_).Run(DecoderStatus::Codes::kFailed);
if (!weak_this)
return;
for (auto& pending_decode : pending_decodes_) {
std::move(pending_decode.second).Run(DecoderStatus::Codes::kFailed);
if (!weak_this)
return;
}
pending_decodes_.clear();
if (reset_cb_)
std::move(reset_cb_).Run();
}
void OOPVideoDecoder::ReleaseVideoFrame(
const base::UnguessableToken& release_token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
CHECK(stable_video_frame_handle_releaser_remote_.is_bound());
stable_video_frame_handle_releaser_remote_->ReleaseVideoFrame(release_token);
}
void OOPVideoDecoder::ApplyResolutionChange() {
NOTREACHED();
}
bool OOPVideoDecoder::NeedsBitstreamConversion() const {
NOTIMPLEMENTED();
NOTREACHED();
return false;
}
bool OOPVideoDecoder::CanReadWithoutStalling() const {
NOTIMPLEMENTED();
NOTREACHED();
return true;
}
int OOPVideoDecoder::GetMaxDecodeRequests() const {
NOTIMPLEMENTED();
NOTREACHED();
return 4;
}
VideoDecoderType OOPVideoDecoder::GetDecoderType() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return decoder_type_;
}
bool OOPVideoDecoder::IsPlatformDecoder() const {
NOTIMPLEMENTED();
NOTREACHED();
return true;
}
bool OOPVideoDecoder::NeedsTranscryption() {
return false;
}
void OOPVideoDecoder::OnVideoFrameDecoded(
const scoped_refptr<VideoFrame>& frame,
bool can_read_without_stalling,
const base::UnguessableToken& release_token) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
// The destruction observer will be called after the client releases the
// video frame. BindToCurrentLoop() is used to make sure that the WeakPtr
// is dereferenced on the correct sequence.
frame->AddDestructionObserver(BindToCurrentLoop(
base::BindOnce(&OOPVideoDecoder::ReleaseVideoFrame,
weak_this_factory_.GetWeakPtr(), release_token)));
// TODO(b/220915557): validate |frame|.
if (output_cb_)
output_cb_.Run(frame);
}
void OOPVideoDecoder::OnWaiting(WaitingReason reason) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!has_error_);
if (waiting_cb_)
waiting_cb_.Run(reason);
}
void OOPVideoDecoder::AddLogRecord(const MediaLogRecord& event) {
VLOGF(2);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (media_log_)
media_log_->AddLogRecord(std::make_unique<media::MediaLogRecord>(event));
}
} // namespace media