blob: 5c979e41b1b8383dea766aed5b72509310d00e8f [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 "third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h"
#include <limits>
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "build/buildflag.h"
#include "media/base/decoder_factory.h"
#include "media/base/media_util.h"
#include "media/base/status_codes.h"
#include "media/base/video_decoder_config.h"
#include "media/mojo/buildflags.h"
#include "media/mojo/clients/mojo_decoder_factory.h"
#include "media/mojo/mojom/interface_factory.mojom.h"
#include "media/renderers/default_decoder_factory.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/webcodecs/decoder_selector.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "ui/gfx/color_space.h"
using DecoderDetails = blink::VideoDecoderBroker::DecoderDetails;
namespace WTF {
template <>
struct CrossThreadCopier<media::VideoDecoderConfig>
: public CrossThreadCopierPassThrough<media::VideoDecoderConfig> {
STATIC_ONLY(CrossThreadCopier);
};
template <>
struct CrossThreadCopier<media::Status>
: public CrossThreadCopierPassThrough<media::Status> {
STATIC_ONLY(CrossThreadCopier);
};
template <>
struct CrossThreadCopier<base::Optional<DecoderDetails>>
: public CrossThreadCopierPassThrough<base::Optional<DecoderDetails>> {
STATIC_ONLY(CrossThreadCopier);
};
} // namespace WTF
namespace blink {
// Wrapper class for state and API calls that must be made from the
// |media_task_runner_|. Construction must happen on blink main thread to safely
// make use of ExecutionContext and Document. These GC blink types must not be
// stored/referenced by any other method.
class MediaVideoTaskWrapper {
public:
using CrossThreadOnceInitCB =
WTF::CrossThreadOnceFunction<void(media::Status status,
base::Optional<DecoderDetails>)>;
using CrossThreadOnceDecodeCB =
WTF::CrossThreadOnceFunction<void(const media::Status&)>;
using CrossThreadOnceResetCB = WTF::CrossThreadOnceClosure;
MediaVideoTaskWrapper(
base::WeakPtr<CrossThreadVideoDecoderClient> weak_client,
ExecutionContext& execution_context,
media::GpuVideoAcceleratorFactories* gpu_factories,
std::unique_ptr<media::MediaLog> media_log,
scoped_refptr<base::SequencedTaskRunner> media_task_runner,
scoped_refptr<base::SequencedTaskRunner> main_task_runner)
: weak_client_(std::move(weak_client)),
media_task_runner_(std::move(media_task_runner)),
main_task_runner_(std::move(main_task_runner)),
gpu_factories_(gpu_factories),
media_log_(std::move(media_log)) {
DVLOG(2) << __func__;
DETACH_FROM_SEQUENCE(sequence_checker_);
// TODO(chcunningham): set_disconnect_handler?
// Mojo connection setup must occur here on the main thread where its safe
// to use |execution_context| APIs.
mojo::PendingRemote<media::mojom::InterfaceFactory> media_interface_factory;
Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
media_interface_factory.InitWithNewPipeAndPassReceiver());
// Mojo remote must be bound on media thread where it will be used.
// |Unretained| is safe because |this| must be destroyed on the media task
// runner.
PostCrossThreadTask(
*media_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&MediaVideoTaskWrapper::BindOnTaskRunner,
WTF::CrossThreadUnretained(this),
std::move(media_interface_factory)));
// TODO(sandersd): Target color space is used by DXVA VDA to pick an
// efficient conversion for FP16 HDR content, and for no other purpose.
// For <video>, we use the document's colorspace, but for WebCodecs we can't
// infer that frames will be rendered to a document (there might not even be
// a document). If this is relevant for WebCodecs, we should make it a
// configuration hint.
target_color_space_ = gfx::ColorSpace::CreateSRGB();
}
virtual ~MediaVideoTaskWrapper() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
MediaVideoTaskWrapper(const MediaVideoTaskWrapper&) = delete;
MediaVideoTaskWrapper& operator=(const MediaVideoTaskWrapper&) = delete;
void Initialize(const media::VideoDecoderConfig& config) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
selector_ = std::make_unique<WebCodecsVideoDecoderSelector>(
media_task_runner_,
// TODO(chcunningham): Its ugly that we don't use a WeakPtr here, but
// its not possible because the callback returns non-void. It happens
// to be safe given the way the callback is called (never posted), but
// we should refactor the return to be an out-param so we can be
// consistent in using weak pointers.
WTF::BindRepeating(&MediaVideoTaskWrapper::OnCreateDecoders,
WTF::Unretained(this)),
WTF::BindRepeating(&MediaVideoTaskWrapper::OnDecodeOutput,
weak_factory_.GetWeakPtr()));
selector_->SelectDecoder(
config, WTF::Bind(&MediaVideoTaskWrapper::OnDecoderSelected,
weak_factory_.GetWeakPtr()));
}
void Decode(scoped_refptr<media::DecoderBuffer> buffer, int cb_id) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!decoder_) {
OnDecodeDone(cb_id, media::DecodeStatus::DECODE_ERROR);
return;
}
decoder_->Decode(std::move(buffer),
WTF::Bind(&MediaVideoTaskWrapper::OnDecodeDone,
weak_factory_.GetWeakPtr(), cb_id));
}
void Reset(int cb_id) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!decoder_) {
OnReset(cb_id);
return;
}
decoder_->Reset(WTF::Bind(&MediaVideoTaskWrapper::OnReset,
weak_factory_.GetWeakPtr(), cb_id));
}
void UpdateHardwarePreference(HardwarePreference preference) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (hardware_preference_ != preference) {
hardware_preference_ = preference;
decoder_factory_needs_update_ = true;
}
}
private:
void BindOnTaskRunner(
mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
media_interface_factory_.Bind(std::move(interface_factory));
}
void UpdateDecoderFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(decoder_factory_needs_update_);
decoder_factory_needs_update_ = false;
// Bind the |interface_factory_| above before passing to
// |external_decoder_factory|.
std::unique_ptr<media::DecoderFactory> external_decoder_factory;
#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
if (hardware_preference_ != HardwarePreference::kDeny) {
external_decoder_factory = std::make_unique<media::MojoDecoderFactory>(
media_interface_factory_.get());
}
#endif
if (hardware_preference_ == HardwarePreference::kRequire) {
decoder_factory_ = std::move(external_decoder_factory);
return;
}
decoder_factory_ = std::make_unique<media::DefaultDecoderFactory>(
std::move(external_decoder_factory));
}
void OnRequestOverlayInfo(bool decoder_requires_restart_for_overlay,
media::ProvideOverlayInfoCB overlay_info_cb) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Android overlays are not supported.
if (overlay_info_cb)
std::move(overlay_info_cb).Run(media::OverlayInfo());
}
std::vector<std::unique_ptr<media::VideoDecoder>> OnCreateDecoders() {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (decoder_factory_needs_update_)
UpdateDecoderFactory();
std::vector<std::unique_ptr<media::VideoDecoder>> video_decoders;
// We can end up with a null |decoder_factory_| if
// |hardware_preference_| filtered out all available factories.
if (decoder_factory_) {
decoder_factory_->CreateVideoDecoders(
media_task_runner_, gpu_factories_, media_log_.get(),
WTF::BindRepeating(&MediaVideoTaskWrapper::OnRequestOverlayInfo,
weak_factory_.GetWeakPtr()),
target_color_space_, &video_decoders);
}
return video_decoders;
}
void OnDecoderSelected(std::unique_ptr<media::VideoDecoder> decoder) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We're done with it.
DCHECK(selector_);
selector_.reset();
decoder_ = std::move(decoder);
media::Status status(media::StatusCode::kDecoderUnsupportedConfig);
base::Optional<DecoderDetails> decoder_details;
if (decoder_) {
status = media::OkStatus();
decoder_details = DecoderDetails(
{decoder_->GetDisplayName(), decoder_->GetDecoderType(),
decoder_->IsPlatformDecoder(), decoder_->NeedsBitstreamConversion(),
decoder_->GetMaxDecodeRequests()});
}
// Fire |init_cb|.
PostCrossThreadTask(
*main_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&CrossThreadVideoDecoderClient::OnInitialize,
weak_client_, status, decoder_details));
}
void OnDecodeOutput(scoped_refptr<media::VideoFrame> frame) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
*main_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&CrossThreadVideoDecoderClient::OnDecodeOutput,
weak_client_, std::move(frame),
decoder_->CanReadWithoutStalling()));
}
void OnDecodeDone(int cb_id, media::Status status) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
*main_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&CrossThreadVideoDecoderClient::OnDecodeDone,
weak_client_, cb_id, std::move(status)));
}
void OnReset(int cb_id) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PostCrossThreadTask(
*main_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&CrossThreadVideoDecoderClient::OnReset,
weak_client_, cb_id));
}
base::WeakPtr<CrossThreadVideoDecoderClient> weak_client_;
scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
media::GpuVideoAcceleratorFactories* gpu_factories_;
mojo::Remote<media::mojom::InterfaceFactory> media_interface_factory_;
std::unique_ptr<WebCodecsVideoDecoderSelector> selector_;
std::unique_ptr<media::DecoderFactory> decoder_factory_;
std::unique_ptr<media::VideoDecoder> decoder_;
gfx::ColorSpace target_color_space_;
HardwarePreference hardware_preference_ = HardwarePreference::kAllow;
bool decoder_factory_needs_update_ = true;
std::unique_ptr<media::MediaLog> media_log_;
SEQUENCE_CHECKER(sequence_checker_);
// Using unretained for decoder/selector callbacks is generally not safe /
// fragile. Some decoders (e.g. those that offload) will call the output
// callback after destruction.
base::WeakPtrFactory<MediaVideoTaskWrapper> weak_factory_{this};
};
constexpr char VideoDecoderBroker::kDefaultDisplayName[];
VideoDecoderBroker::VideoDecoderBroker(
ExecutionContext& execution_context,
media::GpuVideoAcceleratorFactories* gpu_factories,
media::MediaLog* media_log)
: media_task_runner_(
gpu_factories
// GpuFactories requires we use its task runner when available.
? gpu_factories->GetTaskRunner()
// Otherwise, use a worker task runner to avoid scheduling decoder
// work on the main thread.
: worker_pool::CreateSequencedTaskRunner({})) {
DVLOG(2) << __func__;
media_tasks_ = std::make_unique<MediaVideoTaskWrapper>(
weak_factory_.GetWeakPtr(), execution_context, gpu_factories,
media_log->Clone(), media_task_runner_,
execution_context.GetTaskRunner(TaskType::kInternalMedia));
}
VideoDecoderBroker::~VideoDecoderBroker() {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
media_task_runner_->DeleteSoon(FROM_HERE, std::move(media_tasks_));
}
media::VideoDecoderType VideoDecoderBroker::GetDecoderType() const {
return decoder_details_ ? decoder_details_->decoder_id
: media::VideoDecoderType::kBroker;
}
std::string VideoDecoderBroker::GetDisplayName() const {
return decoder_details_ ? decoder_details_->display_name
: VideoDecoderBroker::kDefaultDisplayName;
}
bool VideoDecoderBroker::IsPlatformDecoder() const {
return decoder_details_ ? decoder_details_->is_platform_decoder : false;
}
void VideoDecoderBroker::SetHardwarePreference(
HardwarePreference hardware_preference) {
PostCrossThreadTask(
*media_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&MediaVideoTaskWrapper::UpdateHardwarePreference,
WTF::CrossThreadUnretained(media_tasks_.get()),
hardware_preference));
}
void VideoDecoderBroker::Initialize(const media::VideoDecoderConfig& config,
bool low_delay,
media::CdmContext* cdm_context,
InitCB init_cb,
const OutputCB& output_cb,
const media::WaitingCB& waiting_cb) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!init_cb_) << "Initialize already pending";
// The following are not currently supported in WebCodecs.
// TODO(chcunningham): Should |low_delay| be supported? Should it be
// hard-coded to true?
DCHECK(!low_delay);
DCHECK(!cdm_context);
DCHECK(!waiting_cb);
init_cb_ = std::move(init_cb);
output_cb_ = output_cb;
// Clear details from previously initialized decoder. New values will arrive
// via OnInitialize().
decoder_details_.reset();
PostCrossThreadTask(
*media_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&MediaVideoTaskWrapper::Initialize,
WTF::CrossThreadUnretained(media_tasks_.get()),
config));
}
int VideoDecoderBroker::CreateCallbackId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// 0 and -1 are reserved by wtf::HashMap ("empty" and "deleted").
while (++last_callback_id_ == 0 ||
last_callback_id_ == std::numeric_limits<uint32_t>::max() ||
pending_decode_cb_map_.Contains(last_callback_id_) ||
pending_reset_cb_map_.Contains(last_callback_id_))
;
return last_callback_id_;
}
void VideoDecoderBroker::OnInitialize(media::Status status,
base::Optional<DecoderDetails> details) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(init_cb_);
decoder_details_ = details;
std::move(init_cb_).Run(status);
}
void VideoDecoderBroker::Decode(scoped_refptr<media::DecoderBuffer> buffer,
DecodeCB decode_cb) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const int callback_id = CreateCallbackId();
pending_decode_cb_map_.insert(callback_id, std::move(decode_cb));
PostCrossThreadTask(
*media_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&MediaVideoTaskWrapper::Decode,
WTF::CrossThreadUnretained(media_tasks_.get()),
buffer, callback_id));
}
void VideoDecoderBroker::OnDecodeDone(int cb_id, media::Status status) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(pending_decode_cb_map_.Contains(cb_id));
auto iter = pending_decode_cb_map_.find(cb_id);
DecodeCB decode_cb = std::move(iter->value);
pending_decode_cb_map_.erase(cb_id);
// Do this last. Caller may destruct |this| in response to the callback while
// this method is still on the stack.
std::move(decode_cb).Run(std::move(status));
}
void VideoDecoderBroker::Reset(base::OnceClosure reset_cb) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const int callback_id = CreateCallbackId();
pending_reset_cb_map_.insert(callback_id, std::move(reset_cb));
PostCrossThreadTask(
*media_task_runner_, FROM_HERE,
WTF::CrossThreadBindOnce(&MediaVideoTaskWrapper::Reset,
WTF::CrossThreadUnretained(media_tasks_.get()),
callback_id));
}
bool VideoDecoderBroker::NeedsBitstreamConversion() const {
return decoder_details_ ? decoder_details_->needs_bitstream_conversion
: false;
}
bool VideoDecoderBroker::CanReadWithoutStalling() const {
return can_read_without_stalling_;
}
int VideoDecoderBroker::GetMaxDecodeRequests() const {
return decoder_details_ ? decoder_details_->max_decode_requests : 1;
}
void VideoDecoderBroker::OnReset(int cb_id) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(pending_reset_cb_map_.Contains(cb_id));
auto iter = pending_reset_cb_map_.find(cb_id);
base::OnceClosure reset_cb = std::move(iter->value);
pending_reset_cb_map_.erase(cb_id);
// Do this last. Caller may destruct |this| in response to the callback while
// this method is still on the stack.
std::move(reset_cb).Run();
}
void VideoDecoderBroker::OnDecodeOutput(scoped_refptr<media::VideoFrame> frame,
bool can_read_without_stalling) {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(output_cb_);
can_read_without_stalling_ = can_read_without_stalling;
output_cb_.Run(std::move(frame));
}
} // namespace blink