| // 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 |