| // Copyright 2013 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/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/containers/queue.h" |
| #include "base/feature_list.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/no_destructor.h" |
| #include "build/build_config.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| // Necessary to convert async media::VideoDecoder to sync CdmVideoDecoder. |
| // Typically not recommended for production code, but is ok here since |
| // ClearKeyCdm is only for testing. |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "media/base/decode_status.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/media_util.h" |
| #include "media/cdm/cdm_type_conversion.h" |
| #include "media/cdm/library_cdm/cdm_host_proxy.h" |
| #include "media/media_buildflags.h" |
| #include "third_party/libyuv/include/libyuv/planar_functions.h" |
| |
| #if BUILDFLAG(ENABLE_LIBVPX) |
| #include "media/filters/vpx_video_decoder.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_DAV1D_DECODER) |
| #include "media/filters/dav1d_video_decoder.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS) |
| #include "media/filters/ffmpeg_video_decoder.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_LIBGAV1_DECODER) |
| #include "media/filters/gav1_video_decoder.h" |
| #endif |
| |
| namespace media { |
| |
| namespace { |
| |
| media::VideoDecoderConfig ToClearMediaVideoDecoderConfig( |
| const cdm::VideoDecoderConfig_3& config) { |
| gfx::Size coded_size(config.coded_size.width, config.coded_size.width); |
| |
| VideoDecoderConfig media_config( |
| ToMediaVideoCodec(config.codec), ToMediaVideoCodecProfile(config.profile), |
| VideoDecoderConfig::AlphaMode::kIsOpaque, |
| ToMediaColorSpace(config.color_space), kNoTransformation, coded_size, |
| gfx::Rect(coded_size), coded_size, |
| std::vector<uint8_t>(config.extra_data, |
| config.extra_data + config.extra_data_size), |
| EncryptionScheme::kUnencrypted); |
| |
| return media_config; |
| } |
| |
| bool ToCdmVideoFrame(const VideoFrame& video_frame, |
| CdmHostProxy* cdm_host_proxy, |
| CdmVideoDecoder::CdmVideoFrame* cdm_video_frame) { |
| DCHECK(cdm_video_frame); |
| |
| if (!video_frame.IsMappable()) { |
| DVLOG(1) << "VideoFrame is not mappable"; |
| return false; |
| } |
| |
| if (!IsYuvPlanar(video_frame.format())) { |
| DVLOG(1) << "Only YUV planar format supported"; |
| return false; |
| } |
| |
| if (VideoFrame::NumPlanes(video_frame.format()) != 3u) { |
| DVLOG(1) << "Only 3-plane format supported"; |
| return false; |
| } |
| |
| auto cdm_video_format = ToCdmVideoFormat(video_frame.format()); |
| if (cdm_video_format == cdm::kUnknownVideoFormat) { |
| DVLOG(1) << "VideoFrame has unsupported format: " << video_frame.format(); |
| return false; |
| } |
| |
| // Get required allocation size for a tightly packed frame. |
| auto space_required = VideoFrame::AllocationSize(video_frame.format(), |
| video_frame.coded_size()); |
| auto* buffer = cdm_host_proxy->Allocate(space_required); |
| if (!buffer) { |
| LOG(ERROR) << __func__ << ": Buffer allocation failed."; |
| return false; |
| } |
| |
| buffer->SetSize(base::checked_cast<uint32_t>(space_required)); |
| cdm_video_frame->SetFrameBuffer(buffer); |
| cdm_video_frame->SetFormat(cdm_video_format); |
| cdm_video_frame->SetSize( |
| {video_frame.coded_size().width(), video_frame.coded_size().height()}); |
| cdm_video_frame->SetTimestamp(video_frame.timestamp().InMicroseconds()); |
| // TODO(crbug.com/707127): Set ColorSpace here. It's not trivial to convert |
| // a gfx::ColorSpace (from VideoFrame) to another other ColorSpace like |
| // cdm::ColorSpace. |
| |
| static_assert(VideoFrame::kYPlane == cdm::kYPlane && cdm::kYPlane == 0, ""); |
| static_assert(VideoFrame::kUPlane == cdm::kUPlane && cdm::kUPlane == 1, ""); |
| static_assert(VideoFrame::kVPlane == cdm::kVPlane && cdm::kVPlane == 2, ""); |
| |
| uint8_t* dst = buffer->Data(); |
| uint32_t offset = 0; |
| for (int plane = 0; plane < 3; ++plane) { |
| const uint8_t* src = video_frame.data(plane); |
| int src_stride = video_frame.stride(plane); |
| int row_bytes = video_frame.row_bytes(plane); |
| int rows = video_frame.rows(plane); |
| |
| auto cdm_plane = static_cast<cdm::VideoPlane>(plane); |
| cdm_video_frame->SetPlaneOffset(cdm_plane, offset); |
| // Since it's tightly packed, the stride is the same as row bytes. |
| cdm_video_frame->SetStride(cdm_plane, row_bytes); |
| |
| libyuv::CopyPlane(src, src_stride, dst, row_bytes, row_bytes, rows); |
| dst += row_bytes * rows; |
| offset += row_bytes * rows; |
| } |
| |
| DCHECK_GE(space_required, offset) << ": Space mismatch"; |
| return true; |
| } |
| |
| // Media VideoDecoders typically assumes a global environment where a lot of |
| // things are already setup in the process, e.g. base::ThreadTaskRunnerHandle |
| // and base::CommandLine. These will be available in the component build because |
| // the CDM and the host is depending on the same base/ target. In static build, |
| // they will not be available and we have to setup it by ourselves. |
| void SetupGlobalEnvironmentIfNeeded() { |
| // Creating a base::SingleThreadTaskExecutor to setup |
| // base::ThreadTaskRunnerHandle. |
| if (!base::ThreadTaskRunnerHandle::IsSet()) { |
| static base::NoDestructor<base::SingleThreadTaskExecutor> task_executor; |
| } |
| |
| // Initialize CommandLine if not already initialized. Since this is a DLL, |
| // just use empty arguments. |
| if (!base::CommandLine::InitializedForCurrentProcess()) { |
| #if defined(OS_WIN) |
| // Use InitUsingArgvForTesting() instead of Init() to avoid dependency on |
| // shell32 API which might not work in the sandbox. See crbug.com/1242710. |
| base::CommandLine::InitUsingArgvForTesting(0, nullptr); |
| #else |
| base::CommandLine::Init(0, nullptr); |
| #endif |
| } |
| } |
| |
| // Adapts a media::VideoDecoder to a CdmVideoDecoder. Media VideoDecoders |
| // operations are asynchronous, often posting callbacks to the task runner. The |
| // CdmVideoDecoder operations are synchronous. Therefore, after calling |
| // media::VideoDecoder, we need to run a RunLoop manually and wait for the |
| // asynchronous operation to finish. The RunLoop must be of type |
| // |kNestableTasksAllowed| because we could be running the RunLoop in a task, |
| // e.g. in component builds when we share the same task runner as the host. In |
| // a static build, this is not necessary. |
| class VideoDecoderAdapter final : public CdmVideoDecoder { |
| public: |
| VideoDecoderAdapter(CdmHostProxy* cdm_host_proxy, |
| std::unique_ptr<VideoDecoder> video_decoder) |
| : cdm_host_proxy_(cdm_host_proxy), |
| video_decoder_(std::move(video_decoder)) { |
| DCHECK(cdm_host_proxy_); |
| } |
| |
| VideoDecoderAdapter(const VideoDecoderAdapter&) = delete; |
| VideoDecoderAdapter& operator=(const VideoDecoderAdapter&) = delete; |
| |
| ~VideoDecoderAdapter() final = default; |
| |
| // CdmVideoDecoder implementation. |
| Status Initialize(const cdm::VideoDecoderConfig_3& config) final { |
| auto clear_config = ToClearMediaVideoDecoderConfig(config); |
| DVLOG(1) << __func__ << ": " << clear_config.AsHumanReadableString(); |
| DCHECK(!last_init_result_.has_value()); |
| |
| // Initialize |video_decoder_| and wait for completion. |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| video_decoder_->Initialize( |
| clear_config, |
| /* low_delay = */ false, |
| /* cdm_context = */ nullptr, |
| base::BindOnce(&VideoDecoderAdapter::OnInitialized, |
| weak_factory_.GetWeakPtr(), run_loop.QuitClosure()), |
| base::BindRepeating(&VideoDecoderAdapter::OnVideoFrameReady, |
| weak_factory_.GetWeakPtr()), |
| /* waiting_cb = */ base::DoNothing()); |
| run_loop.Run(); |
| |
| auto result = std::move(last_init_result_.value()); |
| last_init_result_.reset(); |
| |
| return result; |
| } |
| |
| void Deinitialize() final { |
| // Do nothing since |video_decoder_| supports reinitialization without |
| // the need to deinitialize first. |
| } |
| |
| void Reset() final { |
| DVLOG(2) << __func__; |
| |
| // Reset |video_decoder_| and wait for completion. |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| video_decoder_->Reset(base::BindOnce(&VideoDecoderAdapter::OnReset, |
| weak_factory_.GetWeakPtr(), |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| cdm::Status Decode(scoped_refptr<DecoderBuffer> buffer, |
| CdmVideoFrame* decoded_frame) final { |
| DVLOG(3) << __func__; |
| DCHECK(!last_decode_status_.has_value()); |
| |
| // Call |video_decoder_| Decode() and wait for completion. |
| base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); |
| video_decoder_->Decode( |
| std::move(buffer), |
| base::BindOnce(&VideoDecoderAdapter::OnDecoded, |
| weak_factory_.GetWeakPtr(), run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| auto decode_status = last_decode_status_.value(); |
| last_decode_status_.reset(); |
| |
| // "kAborted" shouldn't happen during a sync decode, so treat it as an |
| // error. |
| DCHECK_NE(decode_status.code(), StatusCode::kAborted); |
| |
| if (!decode_status.is_ok()) |
| return cdm::kDecodeError; |
| |
| if (decoded_video_frames_.empty()) |
| return cdm::kNeedMoreData; |
| |
| auto video_frame = decoded_video_frames_.front(); |
| decoded_video_frames_.pop(); |
| |
| return ToCdmVideoFrame(*video_frame, cdm_host_proxy_, decoded_frame) |
| ? cdm::kSuccess |
| : cdm::kDecodeError; |
| } |
| |
| private: |
| void OnInitialized(base::OnceClosure quit_closure, Status status) { |
| DVLOG(1) << __func__ << " success = " << status.is_ok(); |
| DCHECK(!last_init_result_.has_value()); |
| last_init_result_ = std::move(status); |
| std::move(quit_closure).Run(); |
| } |
| |
| void OnVideoFrameReady(scoped_refptr<VideoFrame> video_frame) { |
| // Do not queue EOS frames, which is not needed. |
| if (video_frame->metadata().end_of_stream) |
| return; |
| |
| decoded_video_frames_.push(std::move(video_frame)); |
| } |
| |
| void OnReset(base::OnceClosure quit_closure) { |
| VideoFrameQueue empty_queue; |
| std::swap(decoded_video_frames_, empty_queue); |
| std::move(quit_closure).Run(); |
| } |
| |
| void OnDecoded(base::OnceClosure quit_closure, Status decode_status) { |
| DCHECK(!last_decode_status_.has_value()); |
| last_decode_status_ = std::move(decode_status); |
| std::move(quit_closure).Run(); |
| } |
| |
| CdmHostProxy* const cdm_host_proxy_; |
| std::unique_ptr<VideoDecoder> video_decoder_; |
| |
| // Results of |video_decoder_| operations. Set iff the callback of the |
| // operation has been called. |
| absl::optional<Status> last_init_result_; |
| absl::optional<Status> last_decode_status_; |
| |
| // Queue of decoded video frames. |
| using VideoFrameQueue = base::queue<scoped_refptr<VideoFrame>>; |
| VideoFrameQueue decoded_video_frames_; |
| |
| base::WeakPtrFactory<VideoDecoderAdapter> weak_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<CdmVideoDecoder> CreateVideoDecoder( |
| CdmHostProxy* cdm_host_proxy, |
| const cdm::VideoDecoderConfig_3& config) { |
| SetupGlobalEnvironmentIfNeeded(); |
| |
| static base::NoDestructor<media::NullMediaLog> null_media_log; |
| std::unique_ptr<VideoDecoder> video_decoder; |
| |
| #if BUILDFLAG(ENABLE_LIBVPX) |
| if (config.codec == cdm::kCodecVp8 || config.codec == cdm::kCodecVp9) |
| video_decoder = std::make_unique<VpxVideoDecoder>(); |
| #endif |
| |
| #if BUILDFLAG(ENABLE_LIBGAV1_DECODER) |
| if (base::FeatureList::IsEnabled(kGav1VideoDecoder)) { |
| if (config.codec == cdm::kCodecAv1) |
| video_decoder.reset(new Gav1VideoDecoder(null_media_log.get())); |
| } else |
| #endif // BUILDFLAG(ENABLE_LIBGAV1_DECODER) |
| { |
| #if BUILDFLAG(ENABLE_DAV1D_DECODER) |
| if (config.codec == cdm::kCodecAv1) |
| video_decoder = std::make_unique<Dav1dVideoDecoder>(null_media_log.get()); |
| #endif |
| } |
| |
| #if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS) |
| if (!video_decoder) |
| video_decoder = std::make_unique<FFmpegVideoDecoder>(null_media_log.get()); |
| #endif |
| |
| if (!video_decoder) |
| return nullptr; |
| |
| return std::make_unique<VideoDecoderAdapter>(cdm_host_proxy, |
| std::move(video_decoder)); |
| } |
| |
| } // namespace media |