blob: a3a2a23be8bcf9b5388fe48e4cd45718a2c9074c [file] [log] [blame]
// Copyright 2016 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/mojo/services/mojo_video_decoder_service.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/decoder_buffer.h"
#include "media/base/simple_sync_token_client.h"
#include "media/base/video_decoder.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/common/mojo_decoder_buffer_converter.h"
#include "media/mojo/services/mojo_cdm_service_context.h"
#include "media/mojo/services/mojo_media_client.h"
#include "media/mojo/services/mojo_media_log.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/system/handle.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace media {
namespace {
// Number of active (Decode() was called at least once)
// MojoVideoDecoderService instances that are alive.
//
// Since MojoVideoDecoderService is constructed only by the MediaFactory,
// this will only ever be accessed from a single thread.
static int32_t g_num_active_mvd_instances = 0;
const char kInitializeTraceName[] = "MojoVideoDecoderService::Initialize";
const char kDecodeTraceName[] = "MojoVideoDecoderService::Decode";
const char kResetTraceName[] = "MojoVideoDecoderService::Reset";
base::debug::CrashKeyString* GetNumVideoDecodersCrashKeyString() {
static base::debug::CrashKeyString* codec_count_crash_key =
base::debug::AllocateCrashKeyString("num-video-decoders",
base::debug::CrashKeySize::Size32);
return codec_count_crash_key;
}
} // namespace
class VideoFrameHandleReleaserImpl final
: public mojom::VideoFrameHandleReleaser {
public:
VideoFrameHandleReleaserImpl() { DVLOG(3) << __func__; }
VideoFrameHandleReleaserImpl(const VideoFrameHandleReleaserImpl&) = delete;
VideoFrameHandleReleaserImpl& operator=(const VideoFrameHandleReleaserImpl&) =
delete;
~VideoFrameHandleReleaserImpl() final { DVLOG(3) << __func__; }
// Register a VideoFrame to recieve release callbacks. A reference to |frame|
// will be held until the remote client calls ReleaseVideoFrame() or is
// disconnected.
//
// Returns an UnguessableToken which the client must use to release the
// VideoFrame.
base::UnguessableToken RegisterVideoFrame(scoped_refptr<VideoFrame> frame) {
base::UnguessableToken token = base::UnguessableToken::Create();
DVLOG(3) << __func__ << " => " << token.ToString();
video_frames_[token] = std::move(frame);
return token;
}
// mojom::MojoVideoFrameHandleReleaser implementation
void ReleaseVideoFrame(const base::UnguessableToken& release_token,
const gpu::SyncToken& release_sync_token) final {
DVLOG(3) << __func__ << "(" << release_token.ToString() << ")";
auto it = video_frames_.find(release_token);
if (it == video_frames_.end()) {
mojo::ReportBadMessage("Unknown |release_token|.");
return;
}
SimpleSyncTokenClient client(release_sync_token);
it->second->UpdateReleaseSyncToken(&client);
video_frames_.erase(it);
}
private:
// TODO(sandersd): Also track age, so that an overall limit can be enforced.
std::map<base::UnguessableToken, scoped_refptr<VideoFrame>> video_frames_;
};
MojoVideoDecoderService::MojoVideoDecoderService(
MojoMediaClient* mojo_media_client,
MojoCdmServiceContext* mojo_cdm_service_context)
: mojo_media_client_(mojo_media_client),
mojo_cdm_service_context_(mojo_cdm_service_context) {
DVLOG(1) << __func__;
DCHECK(mojo_media_client_);
DCHECK(mojo_cdm_service_context_);
weak_this_ = weak_factory_.GetWeakPtr();
}
MojoVideoDecoderService::~MojoVideoDecoderService() {
DVLOG(1) << __func__;
if (init_cb_) {
OnDecoderInitialized(
Status(StatusCode::kMojoDecoderDeletedWithoutInitialization)
.WithData("decoder", "MojoVideoDecoder"));
}
if (reset_cb_)
OnDecoderReset();
if (is_active_instance_) {
g_num_active_mvd_instances--;
base::debug::SetCrashKeyString(
GetNumVideoDecodersCrashKeyString(),
base::NumberToString(g_num_active_mvd_instances));
}
// Destruct the VideoDecoder here so its destruction duration is included by
// the histogram timer below.
weak_factory_.InvalidateWeakPtrs();
decoder_.reset();
}
void MojoVideoDecoderService::GetSupportedConfigs(
GetSupportedConfigsCallback callback) {
DVLOG(3) << __func__;
mojo_media_client_->GetSupportedVideoDecoderConfigs(
base::BindOnce(&MojoVideoDecoderService::OnSupportedVideoDecoderConfigs,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void MojoVideoDecoderService::OnSupportedVideoDecoderConfigs(
GetSupportedConfigsCallback callback,
SupportedVideoDecoderConfigs configs) {
std::move(callback).Run(std::move(configs),
mojo_media_client_->GetDecoderImplementationType());
}
void MojoVideoDecoderService::Construct(
mojo::PendingAssociatedRemote<mojom::VideoDecoderClient> client,
mojo::PendingRemote<mojom::MediaLog> media_log,
mojo::PendingReceiver<mojom::VideoFrameHandleReleaser>
video_frame_handle_releaser_receiver,
mojo::ScopedDataPipeConsumerHandle decoder_buffer_pipe,
mojom::CommandBufferIdPtr command_buffer_id,
const gfx::ColorSpace& target_color_space) {
DVLOG(1) << __func__;
TRACE_EVENT0("media", "MojoVideoDecoderService::Construct");
if (decoder_) {
mojo::ReportBadMessage("Construct() already called");
return;
}
client_.Bind(std::move(client));
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::ThreadTaskRunnerHandle::Get();
media_log_ =
std::make_unique<MojoMediaLog>(std::move(media_log), task_runner);
video_frame_handle_releaser_ = mojo::MakeSelfOwnedReceiver(
std::make_unique<VideoFrameHandleReleaserImpl>(),
std::move(video_frame_handle_releaser_receiver));
mojo_decoder_buffer_reader_ =
std::make_unique<MojoDecoderBufferReader>(std::move(decoder_buffer_pipe));
decoder_ = mojo_media_client_->CreateVideoDecoder(
task_runner, media_log_.get(), std::move(command_buffer_id),
base::BindRepeating(
&MojoVideoDecoderService::OnDecoderRequestedOverlayInfo, weak_this_),
target_color_space);
}
void MojoVideoDecoderService::Initialize(
const VideoDecoderConfig& config,
bool low_delay,
const absl::optional<base::UnguessableToken>& cdm_id,
InitializeCallback callback) {
DVLOG(1) << __func__ << " config = " << config.AsHumanReadableString()
<< ", cdm_id = "
<< CdmContext::CdmIdToString(base::OptionalOrNullptr(cdm_id));
DCHECK(!init_cb_);
DCHECK(callback);
TRACE_EVENT_ASYNC_BEGIN2(
"media", kInitializeTraceName, this, "config",
config.AsHumanReadableString(), "cdm_id",
CdmContext::CdmIdToString(base::OptionalOrNullptr(cdm_id)));
init_cb_ = std::move(callback);
if (!decoder_) {
OnDecoderInitialized(StatusCode::kMojoDecoderNoWrappedDecoder);
return;
}
// |cdm_context_ref_| must be kept as long as |cdm_context| is used by the
// |decoder_|. We do NOT support resetting |cdm_context_ref_| because in
// general we don't support resetting CDM in the media pipeline.
if (cdm_id) {
if (!cdm_id_) {
DCHECK(!cdm_context_ref_);
cdm_id_ = cdm_id;
cdm_context_ref_ =
mojo_cdm_service_context_->GetCdmContextRef(cdm_id.value());
} else if (cdm_id != cdm_id_) {
// TODO(xhwang): Replace with mojo::ReportBadMessage().
NOTREACHED() << "The caller should not switch CDM";
OnDecoderInitialized(StatusCode::kDecoderMissingCdmForEncryptedContent);
return;
}
}
// Get CdmContext, which could be null.
CdmContext* cdm_context =
cdm_context_ref_ ? cdm_context_ref_->GetCdmContext() : nullptr;
if (config.is_encrypted() && !cdm_context) {
DVLOG(1) << "CdmContext for "
<< CdmContext::CdmIdToString(base::OptionalOrNullptr(cdm_id))
<< " not found for encrypted video";
OnDecoderInitialized(StatusCode::kDecoderMissingCdmForEncryptedContent);
return;
}
auto gfx_cs = config.color_space_info().ToGfxColorSpace();
codec_string_ = base::StringPrintf(
"name=%s:codec=%s:profile=%d:size=%s:cs=[%d,%d,%d,%d]:hdrm=%d",
GetDecoderName(decoder_->GetDecoderType()).c_str(),
GetCodecName(config.codec()).c_str(), config.profile(),
config.coded_size().ToString().c_str(),
static_cast<int>(gfx_cs.GetPrimaryID()),
static_cast<int>(gfx_cs.GetTransferID()),
static_cast<int>(gfx_cs.GetMatrixID()),
static_cast<int>(gfx_cs.GetRangeID()), config.hdr_metadata().has_value());
using Self = MojoVideoDecoderService;
decoder_->Initialize(
config, low_delay, cdm_context,
base::BindOnce(&Self::OnDecoderInitialized, weak_this_),
base::BindRepeating(&Self::OnDecoderOutput, weak_this_),
base::BindRepeating(&Self::OnDecoderWaiting, weak_this_));
}
void MojoVideoDecoderService::Decode(mojom::DecoderBufferPtr buffer,
DecodeCallback callback) {
DVLOG(3) << __func__ << " pts=" << buffer->timestamp.InMilliseconds();
DCHECK(callback);
std::unique_ptr<ScopedDecodeTrace> trace_event;
if (ScopedDecodeTrace::IsEnabled()) {
// Because multiple Decode() calls may be in flight, each call needs a
// unique trace event class to identify it. This scoped event is bound
// into the OnDecodeDone callback to ensure the trace is always closed.
trace_event = std::make_unique<ScopedDecodeTrace>(
kDecodeTraceName, buffer->is_key_frame, buffer->timestamp);
}
if (!decoder_) {
OnDecoderDecoded(std::move(callback), std::move(trace_event),
DecodeStatus::DECODE_ERROR);
return;
}
if (!is_active_instance_) {
is_active_instance_ = true;
g_num_active_mvd_instances++;
base::UmaHistogramExactLinear("Media.MojoVideoDecoder.ActiveInstances",
g_num_active_mvd_instances, 64);
base::debug::SetCrashKeyString(
GetNumVideoDecodersCrashKeyString(),
base::NumberToString(g_num_active_mvd_instances));
// This will be overwritten as subsequent decoders are created.
static auto* last_codec_crash_key = base::debug::AllocateCrashKeyString(
"last-video-decoder", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(last_codec_crash_key, codec_string_);
}
mojo_decoder_buffer_reader_->ReadDecoderBuffer(
std::move(buffer),
base::BindOnce(&MojoVideoDecoderService::OnReaderRead, weak_this_,
std::move(callback), std::move(trace_event)));
}
void MojoVideoDecoderService::Reset(ResetCallback callback) {
DVLOG(2) << __func__;
TRACE_EVENT_ASYNC_BEGIN0("media", kResetTraceName, this);
DCHECK(callback);
DCHECK(!reset_cb_);
reset_cb_ = std::move(callback);
if (!decoder_) {
OnDecoderReset();
return;
}
// Flush the reader so that pending decodes will be dispatched first.
mojo_decoder_buffer_reader_->Flush(
base::BindOnce(&MojoVideoDecoderService::OnReaderFlushed, weak_this_));
}
void MojoVideoDecoderService::OnDecoderInitialized(Status status) {
DVLOG(1) << __func__;
DCHECK(!status.is_ok() || decoder_);
DCHECK(init_cb_);
TRACE_EVENT_ASYNC_END1("media", kInitializeTraceName, this, "success",
status.code());
if (!status.is_ok()) {
std::move(init_cb_).Run(
status, false, 1,
decoder_ ? decoder_->GetDecoderType() : VideoDecoderType::kUnknown);
return;
}
std::move(init_cb_).Run(status, decoder_->NeedsBitstreamConversion(),
decoder_->GetMaxDecodeRequests(),
decoder_->GetDecoderType());
}
void MojoVideoDecoderService::OnReaderRead(
DecodeCallback callback,
std::unique_ptr<ScopedDecodeTrace> trace_event,
scoped_refptr<DecoderBuffer> buffer) {
DVLOG(3) << __func__;
if (trace_event) {
TRACE_EVENT_ASYNC_STEP_PAST1(
"media", kDecodeTraceName, trace_event.get(), "ReadDecoderBuffer",
"decoder_buffer", buffer ? buffer->AsHumanReadableString() : "null");
}
if (!buffer) {
OnDecoderDecoded(std::move(callback), std::move(trace_event),
DecodeStatus::DECODE_ERROR);
return;
}
decoder_->Decode(
buffer,
base::BindOnce(&MojoVideoDecoderService::OnDecoderDecoded, weak_this_,
std::move(callback), std::move(trace_event)));
}
void MojoVideoDecoderService::OnReaderFlushed() {
decoder_->Reset(
base::BindOnce(&MojoVideoDecoderService::OnDecoderReset, weak_this_));
}
void MojoVideoDecoderService::OnDecoderDecoded(
DecodeCallback callback,
std::unique_ptr<ScopedDecodeTrace> trace_event,
media::Status status) {
DVLOG(3) << __func__;
if (trace_event) {
TRACE_EVENT_ASYNC_STEP_PAST0("media", kDecodeTraceName, trace_event.get(),
"Decode");
trace_event->EndTrace(status);
}
std::move(callback).Run(std::move(status));
}
void MojoVideoDecoderService::OnDecoderReset() {
DVLOG(2) << __func__;
DCHECK(reset_cb_);
TRACE_EVENT_ASYNC_END0("media", kResetTraceName, this);
std::move(reset_cb_).Run();
}
void MojoVideoDecoderService::OnDecoderOutput(scoped_refptr<VideoFrame> frame) {
DVLOG(3) << __func__ << " pts=" << frame->timestamp().InMilliseconds();
DCHECK(client_);
DCHECK(decoder_);
TRACE_EVENT1("media", "MojoVideoDecoderService::OnDecoderOutput",
"video_frame", frame->AsHumanReadableString());
// All MojoVideoDecoder-based decoders are hardware decoders. If you're the
// first to implement an out-of-process decoder that is not power efficent,
// you can remove this DCHECK.
DCHECK(frame->metadata().power_efficient);
absl::optional<base::UnguessableToken> release_token;
if (frame->HasReleaseMailboxCB() && video_frame_handle_releaser_) {
// |video_frame_handle_releaser_| is explicitly constructed with a
// VideoFrameHandleReleaserImpl in Construct().
VideoFrameHandleReleaserImpl* releaser =
static_cast<VideoFrameHandleReleaserImpl*>(
video_frame_handle_releaser_->impl());
release_token = releaser->RegisterVideoFrame(frame);
}
client_->OnVideoFrameDecoded(std::move(frame),
decoder_->CanReadWithoutStalling(),
std::move(release_token));
}
void MojoVideoDecoderService::OnDecoderWaiting(WaitingReason reason) {
DVLOG(3) << __func__;
DCHECK(client_);
TRACE_EVENT1("media", "MojoVideoDecoderService::OnDecoderWaiting", "reason",
static_cast<int>(reason));
client_->OnWaiting(reason);
}
void MojoVideoDecoderService::OnOverlayInfoChanged(
const OverlayInfo& overlay_info) {
DVLOG(2) << __func__;
DCHECK(client_);
DCHECK(decoder_);
DCHECK(provide_overlay_info_cb_);
TRACE_EVENT0("media", "MojoVideoDecoderService::OnOverlayInfoChanged");
provide_overlay_info_cb_.Run(overlay_info);
}
void MojoVideoDecoderService::OnDecoderRequestedOverlayInfo(
bool restart_for_transitions,
ProvideOverlayInfoCB provide_overlay_info_cb) {
DVLOG(2) << __func__;
DCHECK(client_);
DCHECK(decoder_);
DCHECK(!provide_overlay_info_cb_);
TRACE_EVENT0("media",
"MojoVideoDecoderService::OnDecoderRequestedOverlayInfo");
provide_overlay_info_cb_ = std::move(provide_overlay_info_cb);
client_->RequestOverlayInfo(restart_for_transitions);
}
} // namespace media