blob: 3906b5430d026e67dfdadae061491471e55c6cbc [file] [log] [blame]
// Copyright 2019 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/decoder_template.h"
#include <limits>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "media/media_buildflags.h"
#include "media/video/gpu_video_accelerator_factories.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_frame_output_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/modules/webcodecs/audio_decoder.h"
#include "third_party/blink/renderer/modules/webcodecs/audio_frame.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_config_eval.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h"
#include "third_party/blink/renderer/modules/webcodecs/video_decoder.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/persistent.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/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace blink {
namespace {
void GetGpuFactoriesOnMainThread(
media::GpuVideoAcceleratorFactories** gpu_factories_out,
base::WaitableEvent* waitable_event) {
DCHECK(IsMainThread());
*gpu_factories_out = Platform::Current()->GetGpuFactories();
waitable_event->Signal();
}
} // namespace
template <typename Traits>
DecoderTemplate<Traits>::DecoderTemplate(ScriptState* script_state,
const InitType* init,
ExceptionState& exception_state)
: ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
script_state_(script_state),
state_(V8CodecState::Enum::kUnconfigured) {
DVLOG(1) << __func__;
DCHECK(init->hasOutput());
DCHECK(init->hasError());
ExecutionContext* context = GetExecutionContext();
DCHECK(context);
// TODO(crbug.com/1151005): Use a real MediaLog in worker contexts too.
if (IsMainThread()) {
logger_ = std::make_unique<CodecLogger>(
context, context->GetTaskRunner(TaskType::kInternalMedia));
} else {
// This will create a logger backed by a NullMediaLog, which does nothing.
logger_ = std::make_unique<CodecLogger>();
}
logger_->log()->SetProperty<media::MediaLogProperty::kFrameUrl>(
context->Url().GetString().Ascii());
output_cb_ = init->output();
error_cb_ = init->error();
if (Traits::kNeedsGpuFactories) {
if (IsMainThread()) {
gpu_factories_ = Platform::Current()->GetGpuFactories();
} else {
base::WaitableEvent waitable_event;
if (PostCrossThreadTask(
*Thread::MainThread()->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(&GetGpuFactoriesOnMainThread,
CrossThreadUnretained(&gpu_factories_),
CrossThreadUnretained(&waitable_event)))) {
waitable_event.Wait();
}
}
}
}
template <typename Traits>
DecoderTemplate<Traits>::~DecoderTemplate() {
DVLOG(1) << __func__;
}
template <typename Traits>
int32_t DecoderTemplate<Traits>::decodeQueueSize() {
return num_pending_decodes_;
}
template <typename Traits>
bool DecoderTemplate<Traits>::IsClosed() {
return state_ == V8CodecState::Enum::kClosed;
}
template <typename Traits>
HardwarePreference DecoderTemplate<Traits>::GetHardwarePreference(
const ConfigType&) {
return HardwarePreference::kAllow;
}
template <typename Traits>
void DecoderTemplate<Traits>::SetHardwarePreference(HardwarePreference) {}
template <typename Traits>
void DecoderTemplate<Traits>::configure(const ConfigType* config,
ExceptionState& exception_state) {
DVLOG(1) << __func__;
if (ThrowIfCodecStateClosed(state_, "decode", exception_state))
return;
auto media_config = std::make_unique<MediaConfigType>();
String console_message;
CodecConfigEval eval =
MakeMediaConfig(*config, media_config.get(), &console_message);
switch (eval) {
case CodecConfigEval::kInvalid:
exception_state.ThrowTypeError(console_message);
return;
case CodecConfigEval::kUnsupported:
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
console_message);
return;
case CodecConfigEval::kSupported:
// Good, lets proceed.
break;
}
state_ = V8CodecState(V8CodecState::Enum::kConfigured);
Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kConfigure;
request->media_config = std::move(media_config);
request->reset_generation = reset_generation_;
request->hw_pref = GetHardwarePreference(*config);
requests_.push_back(request);
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::decode(const InputType* chunk,
ExceptionState& exception_state) {
DVLOG(3) << __func__;
if (ThrowIfCodecStateClosed(state_, "decode", exception_state))
return;
if (ThrowIfCodecStateUnconfigured(state_, "decode", exception_state))
return;
Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kDecode;
request->reset_generation = reset_generation_;
auto status_or_buffer = MakeDecoderBuffer(*chunk);
if (status_or_buffer.has_value()) {
request->decoder_buffer = std::move(status_or_buffer).value();
} else {
request->status = std::move(status_or_buffer).error();
}
requests_.push_back(request);
++num_pending_decodes_;
ProcessRequests();
}
template <typename Traits>
ScriptPromise DecoderTemplate<Traits>::flush(ExceptionState& exception_state) {
DVLOG(3) << __func__;
if (ThrowIfCodecStateClosed(state_, "flush", exception_state))
return ScriptPromise();
if (ThrowIfCodecStateUnconfigured(state_, "flush", exception_state))
return ScriptPromise();
Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kFlush;
ScriptPromiseResolver* resolver =
MakeGarbageCollected<ScriptPromiseResolver>(script_state_);
request->resolver = resolver;
request->reset_generation = reset_generation_;
requests_.push_back(request);
ProcessRequests();
return resolver->Promise();
}
template <typename Traits>
void DecoderTemplate<Traits>::reset(ExceptionState& exception_state) {
DVLOG(3) << __func__;
if (ThrowIfCodecStateClosed(state_, "reset", exception_state))
return;
ResetAlgorithm();
}
template <typename Traits>
void DecoderTemplate<Traits>::close(ExceptionState& exception_state) {
DVLOG(3) << __func__;
if (ThrowIfCodecStateClosed(state_, "close", exception_state))
return;
Shutdown();
}
template <typename Traits>
void DecoderTemplate<Traits>::ProcessRequests() {
DVLOG(3) << __func__;
DCHECK(!IsClosed());
while (!pending_request_ && !requests_.IsEmpty()) {
Request* request = requests_.front();
// Skip processing for requests that are canceled by a recent reset().
if (request->reset_generation != reset_generation_) {
if (request->resolver) {
request->resolver.Release()->Reject();
}
requests_.pop_front();
continue;
}
DCHECK_EQ(request->reset_generation, reset_generation_);
switch (request->type) {
case Request::Type::kConfigure:
if (!ProcessConfigureRequest(request))
return;
break;
case Request::Type::kDecode:
if (!ProcessDecodeRequest(request))
return;
break;
case Request::Type::kFlush:
if (!ProcessFlushRequest(request))
return;
break;
case Request::Type::kReset:
if (!ProcessResetRequest(request))
return;
break;
}
requests_.pop_front();
}
}
template <typename Traits>
bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) {
DVLOG(3) << __func__;
DCHECK(!IsClosed());
DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kConfigure);
DCHECK(request->media_config);
// TODO(sandersd): Record this configuration as pending but don't apply it
// until there is a decode request.
if (!decoder_) {
decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_),
gpu_factories_, logger_->log());
if (!decoder_) {
Shutdown(
logger_->MakeException("Internal error: Could not create decoder.",
media::StatusCode::kDecoderCreationFailed));
return false;
}
// Processing continues in OnInitializeDone().
// Note: OnInitializeDone() must not call ProcessRequests() reentrantly,
// which can happen if InitializeDecoder() calls it synchronously.
pending_request_ = request;
initializing_sync_ = true;
SetHardwarePreference(pending_request_->hw_pref);
Traits::InitializeDecoder(
*decoder_, *pending_request_->media_config,
WTF::Bind(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)),
WTF::BindRepeating(&DecoderTemplate::OnOutput, WrapWeakPersistent(this),
reset_generation_));
initializing_sync_ = false;
return true;
}
if (pending_decodes_.size() + 1 >
size_t{Traits::GetMaxDecodeRequests(*decoder_)}) {
// Try again after OnDecodeDone().
return false;
}
// Processing continues in OnConfigureFlushDone().
pending_request_ = request;
decoder_->Decode(media::DecoderBuffer::CreateEOSBuffer(),
WTF::Bind(&DecoderTemplate::OnConfigureFlushDone,
WrapWeakPersistent(this)));
return true;
}
template <typename Traits>
bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
DVLOG(3) << __func__;
DCHECK_EQ(state_, V8CodecState::Enum::kConfigured);
DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kDecode);
DCHECK_GT(num_pending_decodes_, 0);
if (!decoder_) {
Shutdown(logger_->MakeException(
"Decoding error: no decoder found.",
media::StatusCode::kDecoderInitializeNeverCompleted));
return false;
}
if (pending_decodes_.size() + 1 >
size_t{Traits::GetMaxDecodeRequests(*decoder_)}) {
// Try again after OnDecodeDone().
return false;
}
// The request may be invalid, if so report that now.
if (!request->decoder_buffer || request->decoder_buffer->data_size() == 0) {
if (request->status.is_ok()) {
Shutdown(logger_->MakeException("Null or empty decoder buffer.",
media::StatusCode::kDecoderFailedDecode));
} else {
Shutdown(logger_->MakeException("Decoder error.", request->status));
}
return false;
}
// Submit for decoding.
//
// |pending_decode_id_| must not be 0 nor max because it HashMap reserves
// these values for "emtpy" and "deleted".
while (++pending_decode_id_ == 0 ||
pending_decode_id_ == std::numeric_limits<uint32_t>::max() ||
pending_decodes_.Contains(pending_decode_id_))
;
pending_decodes_.Set(pending_decode_id_, request);
--num_pending_decodes_;
decoder_->Decode(std::move(request->decoder_buffer),
WTF::Bind(&DecoderTemplate::OnDecodeDone,
WrapWeakPersistent(this), pending_decode_id_));
return true;
}
template <typename Traits>
bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) {
DVLOG(3) << __func__;
DCHECK(!IsClosed());
DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kFlush);
// flush() can only be called when state = "configured", in which case we
// should always have a decoder.
DCHECK(decoder_);
if (pending_decodes_.size() + 1 >
size_t{Traits::GetMaxDecodeRequests(*decoder_)}) {
// Try again after OnDecodeDone().
return false;
}
// Processing continues in OnFlushDone().
pending_request_ = request;
decoder_->Decode(
media::DecoderBuffer::CreateEOSBuffer(),
WTF::Bind(&DecoderTemplate::OnFlushDone, WrapWeakPersistent(this)));
return true;
}
template <typename Traits>
bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) {
DVLOG(3) << __func__;
DCHECK(!IsClosed());
DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kReset);
DCHECK_GT(reset_generation_, 0u);
// Processing continues in OnResetDone().
pending_request_ = request;
// Signal [[codec implementation]] to cease producing output for the previous
// configuration.
decoder_->Reset(
WTF::Bind(&DecoderTemplate::OnResetDone, WrapWeakPersistent(this)));
return true;
}
template <typename Traits>
void DecoderTemplate<Traits>::Shutdown(DOMException* exception) {
DVLOG(3) << __func__;
if (IsClosed())
return;
// Abort pending work (otherwise it will never complete)
if (pending_request_) {
if (pending_request_->resolver)
pending_request_->resolver.Release()->Reject();
pending_request_.Release();
}
// Abort all upcoming work.
ResetAlgorithm();
// Store the error callback so that we can use it after clearing state.
V8WebCodecsErrorCallback* error_cb = error_cb_.Get();
// Prevent any new public API calls during teardown.
// This should make it safe to call into JS synchronously.
state_ = V8CodecState(V8CodecState::Enum::kClosed);
// Prevent any late callbacks running.
output_cb_.Release();
error_cb_.Release();
// Prevent any further logging from being reported.
logger_->Neuter();
// Clear decoding and JS-visible queue state.
decoder_.reset();
pending_decodes_.clear();
num_pending_decodes_ = 0;
// Fire the error callback if necessary.
if (exception)
error_cb->InvokeAndReportException(nullptr, exception);
}
template <typename Traits>
void DecoderTemplate<Traits>::ResetAlgorithm() {
if (state_ == V8CodecState::Enum::kUnconfigured)
return;
state_ = V8CodecState(V8CodecState::Enum::kUnconfigured);
// Increment reset counter to cause older pending requests to be rejected. See
// ProcessRequests().
reset_generation_++;
// Any previous pending decode will be filtered by ProcessRequests(). Reset
// the count immediately to report the correct value in decodeQueueSize().
num_pending_decodes_ = 0;
Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kReset;
request->reset_generation = reset_generation_;
requests_.push_back(request);
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::OnConfigureFlushDone(media::Status status) {
DVLOG(3) << __func__;
if (IsClosed())
return;
DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
if (!status.is_ok()) {
Shutdown(logger_->MakeException(
"Internal error: failed to flush out frames from previous config.",
status));
return;
}
SetHardwarePreference(pending_request_->hw_pref);
// Processing continues in OnInitializeDone().
Traits::InitializeDecoder(
*decoder_, *pending_request_->media_config,
WTF::Bind(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)),
WTF::BindRepeating(&DecoderTemplate::OnOutput, WrapWeakPersistent(this),
reset_generation_));
}
template <typename Traits>
void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) {
DVLOG(3) << __func__;
if (IsClosed())
return;
DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
if (!status.is_ok()) {
std::string error_message = "Decoder initialization error.";
if (status.code() == media::StatusCode::kDecoderUnsupportedConfig) {
error_message =
"Unsupported configuration. Check isConfigSupported() prior to "
"calling configure().";
}
Shutdown(logger_->MakeException(error_message, status));
return;
}
Traits::UpdateDecoderLog(*decoder_, *pending_request_->media_config,
logger_->log());
pending_request_.Release();
if (!initializing_sync_)
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id, media::Status status) {
DVLOG(3) << __func__;
if (IsClosed())
return;
if (!status.is_ok() && status.code() != media::StatusCode::kAborted) {
Shutdown(logger_->MakeException("Decoding error.", status));
return;
}
DCHECK(pending_decodes_.Contains(id));
auto it = pending_decodes_.find(id);
pending_decodes_.erase(it);
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::OnFlushDone(media::Status status) {
DVLOG(3) << __func__;
if (IsClosed())
return;
DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kFlush);
if (!status.is_ok()) {
Shutdown(logger_->MakeException("Flushing error.", status));
return;
}
pending_request_.Release()->resolver.Release()->Resolve();
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::OnResetDone() {
DVLOG(3) << __func__;
if (IsClosed())
return;
DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kReset);
pending_request_.Release();
ProcessRequests();
}
template <typename Traits>
void DecoderTemplate<Traits>::OnOutput(uint32_t reset_generation,
scoped_refptr<MediaOutputType> output) {
DVLOG(3) << __func__;
// Suppress outputs belonging to an earlier reset_generation.
if (reset_generation != reset_generation_)
return;
if (state_.AsEnum() != V8CodecState::Enum::kConfigured)
return;
auto* context = GetExecutionContext();
if (!context)
return;
output_cb_->InvokeAndReportException(
nullptr, Traits::MakeOutput(std::move(output), context));
}
template <typename Traits>
void DecoderTemplate<Traits>::ContextDestroyed() {
state_ = V8CodecState(V8CodecState::Enum::kClosed);
logger_->Neuter();
decoder_.reset();
}
template <typename Traits>
void DecoderTemplate<Traits>::Trace(Visitor* visitor) const {
visitor->Trace(script_state_);
visitor->Trace(output_cb_);
visitor->Trace(error_cb_);
visitor->Trace(requests_);
visitor->Trace(pending_request_);
visitor->Trace(pending_decodes_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
template <typename Traits>
bool DecoderTemplate<Traits>::HasPendingActivity() const {
return pending_request_ || !requests_.IsEmpty();
}
template <typename Traits>
void DecoderTemplate<Traits>::Request::Trace(Visitor* visitor) const {
visitor->Trace(resolver);
}
template class DecoderTemplate<AudioDecoderTraits>;
template class DecoderTemplate<VideoDecoderTraits>;
} // namespace blink