| // Copyright 2019 The Chromium Authors |
| // 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/atomic_sequence_num.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/decoder_status.h" |
| #include "media/base/media_util.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/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_data_output_callback.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_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/dom/events/event.h" |
| #include "third_party/blink/renderer/core/execution_context/agent.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/modules/webcodecs/audio_data.h" |
| #include "third_party/blink/renderer/modules/webcodecs/audio_decoder.h" |
| #include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h" |
| #include "third_party/blink/renderer/modules/webcodecs/gpu_factories_retriever.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/cross_thread_handle.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" |
| |
| namespace blink { |
| |
| namespace { |
| constexpr const char kCategory[] = "media"; |
| |
| base::AtomicSequenceNumber g_sequence_num_for_counters; |
| } // namespace |
| |
| // static |
| template <typename Traits> |
| const CodecTraceNames* DecoderTemplate<Traits>::GetTraceNames() { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecTraceNames, trace_names, |
| (Traits::GetName())); |
| return &trace_names; |
| } |
| |
| template <typename Traits> |
| DecoderTemplate<Traits>::DecoderTemplate(ScriptState* script_state, |
| const InitType* init, |
| ExceptionState& exception_state) |
| : ActiveScriptWrappable<DecoderTemplate<Traits>>({}), |
| ReclaimableCodec(ReclaimableCodec::CodecType::kDecoder, |
| ExecutionContext::From(script_state)), |
| script_state_(script_state), |
| state_(V8CodecState::Enum::kUnconfigured), |
| trace_counter_id_(g_sequence_num_for_counters.GetNext()) { |
| DVLOG(1) << __func__; |
| DCHECK(init->hasOutput()); |
| DCHECK(init->hasError()); |
| |
| ExecutionContext* context = GetExecutionContext(); |
| DCHECK(context); |
| |
| main_thread_task_runner_ = |
| context->GetTaskRunner(TaskType::kInternalMediaRealTime); |
| |
| logger_ = std::make_unique<CodecLogger<media::DecoderStatus>>( |
| context, main_thread_task_runner_); |
| |
| logger_->log()->SetProperty<media::MediaLogProperty::kFrameUrl>( |
| context->Url().GetString().Ascii()); |
| |
| output_cb_ = init->output(); |
| error_cb_ = init->error(); |
| } |
| |
| template <typename Traits> |
| DecoderTemplate<Traits>::~DecoderTemplate() { |
| DVLOG(1) << __func__; |
| base::UmaHistogramSparse( |
| String::Format("Blink.WebCodecs.%s.FinalStatus", Traits::GetName()) |
| .Ascii() |
| .c_str(), |
| static_cast<int>(logger_->status_code())); |
| } |
| |
| template <typename Traits> |
| uint32_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::kNoPreference; |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::GetLowDelayPreference(const ConfigType&) { |
| return false; |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::SetHardwarePreference(HardwarePreference) {} |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnActiveConfigChanged(const MediaConfigType&) {} |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::configure(const ConfigType* config, |
| ExceptionState& exception_state) { |
| DVLOG(1) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (ThrowIfCodecStateClosed(state_, "decode", exception_state)) |
| return; |
| |
| String js_error_message; |
| if (!IsValidConfig(*config, &js_error_message)) { |
| exception_state.ThrowTypeError(js_error_message); |
| return; |
| } |
| |
| std::optional<MediaConfigType> media_config = |
| MakeMediaConfig(*config, &js_error_message); |
| |
| // Audio/VideoDecoder don't yet support encryption. |
| if (media_config && media_config->is_encrypted()) { |
| js_error_message = "Encrypted content is not supported"; |
| media_config = std::nullopt; |
| } |
| |
| MarkCodecActive(); |
| |
| state_ = V8CodecState(V8CodecState::Enum::kConfigured); |
| require_key_frame_ = true; |
| |
| Request* request = MakeGarbageCollected<Request>(); |
| request->type = Request::Type::kConfigure; |
| if (media_config.has_value()) { |
| request->media_config = std::make_unique<MediaConfigType>(*media_config); |
| } else { |
| request->js_error_message = js_error_message; |
| } |
| request->reset_generation = reset_generation_; |
| request->hw_pref = GetHardwarePreference(*config); |
| request->low_delay = GetLowDelayPreference(*config); |
| requests_.push_back(request); |
| ProcessRequests(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::decode(const InputType* chunk, |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 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 = MakeInput(*chunk, require_key_frame_); |
| if (status_or_buffer.has_value()) { |
| request->decoder_buffer = std::move(status_or_buffer).value(); |
| require_key_frame_ = false; |
| } else { |
| request->status = std::move(status_or_buffer).error(); |
| if (request->status == media::DecoderStatus::Codes::kKeyFrameRequired) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kDataError, |
| String(request->status.message())); |
| return; |
| } |
| } |
| MarkCodecActive(); |
| |
| requests_.push_back(request); |
| ++num_pending_decodes_; |
| ProcessRequests(); |
| } |
| |
| template <typename Traits> |
| ScriptPromise<IDLUndefined> DecoderTemplate<Traits>::flush( |
| ExceptionState& exception_state) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (ThrowIfCodecStateClosed(state_, "flush", exception_state)) |
| return EmptyPromise(); |
| |
| if (ThrowIfCodecStateUnconfigured(state_, "flush", exception_state)) |
| return EmptyPromise(); |
| |
| MarkCodecActive(); |
| |
| require_key_frame_ = true; |
| |
| Request* request = MakeGarbageCollected<Request>(); |
| request->type = Request::Type::kFlush; |
| auto* resolver = |
| MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(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__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (ThrowIfCodecStateClosed(state_, "reset", exception_state)) |
| return; |
| |
| MarkCodecActive(); |
| |
| ResetAlgorithm(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::close(ExceptionState& exception_state) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (ThrowIfCodecStateClosed(state_, "close", exception_state)) |
| return; |
| |
| Shutdown(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::ProcessRequests() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsClosed()); |
| while (!pending_request_ && !requests_.empty()) { |
| Request* request = requests_.front(); |
| |
| // Skip processing for requests that are canceled by a recent reset(). |
| if (MaybeAbortRequest(request)) { |
| requests_.pop_front(); |
| continue; |
| } |
| |
| TraceQueueSizes(); |
| |
| 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(); |
| } |
| |
| TraceQueueSizes(); |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsClosed()); |
| DCHECK(!pending_request_); |
| DCHECK_EQ(request->type, Request::Type::kConfigure); |
| |
| if (decoder() && |
| pending_decodes_.size() + 1 > |
| static_cast<size_t>(Traits::GetMaxDecodeRequests(*decoder()))) { |
| // Try again after OnDecodeDone(). |
| return false; |
| } |
| |
| // TODO(sandersd): Record this configuration as pending but don't apply it |
| // until there is a decode request. |
| pending_request_ = request; |
| pending_request_->StartTracing(); |
| |
| if (!request->media_config) { |
| main_thread_task_runner_->PostTask( |
| FROM_HERE, |
| BindOnce(&DecoderTemplate<Traits>::Shutdown, WrapWeakPersistent(this), |
| WrapPersistent(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotSupportedError, |
| request->js_error_message)))); |
| return false; |
| } |
| |
| if (gpu_factories_.has_value()) { |
| ContinueConfigureWithGpuFactories(request, gpu_factories_.value()); |
| } else if (Traits::kNeedsGpuFactories) { |
| RetrieveGpuFactoriesWithKnownDecoderSupport(CrossThreadBindOnce( |
| &DecoderTemplate<Traits>::ContinueConfigureWithGpuFactories, |
| MakeUnwrappingCrossThreadHandle(this), |
| MakeUnwrappingCrossThreadHandle(request))); |
| } else { |
| ContinueConfigureWithGpuFactories(request, nullptr); |
| } |
| return true; |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::ContinueConfigureWithGpuFactories( |
| Request* request, |
| media::GpuVideoAcceleratorFactories* gpu_factories) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(request); |
| DCHECK_EQ(request->type, Request::Type::kConfigure); |
| |
| if (IsClosed()) { |
| return; |
| } |
| |
| gpu_factories_ = gpu_factories; |
| |
| if (MaybeAbortRequest(request)) { |
| DCHECK_EQ(request, pending_request_); |
| pending_request_.Release()->EndTracing(); |
| ProcessRequests(); |
| return; |
| } |
| |
| if (!decoder()) { |
| decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_), |
| gpu_factories_.value(), logger_->log()); |
| if (!decoder()) { |
| Shutdown(MakeOperationError( |
| "Internal error: Could not create decoder.", |
| media::DecoderStatus::Codes::kFailedToCreateDecoder)); |
| return; |
| } |
| |
| SetHardwarePreference(request->hw_pref.value()); |
| // Processing continues in OnInitializeDone(). |
| // Note: OnInitializeDone() must not call ProcessRequests() reentrantly, |
| // which can happen if InitializeDecoder() calls it synchronously. |
| initializing_sync_ = true; |
| Traits::InitializeDecoder( |
| *decoder(), request->low_delay.value(), *request->media_config, |
| BindOnce(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)), |
| blink::BindRepeating(&DecoderTemplate::OnOutput, |
| WrapWeakPersistent(this), reset_generation_)); |
| initializing_sync_ = false; |
| return; |
| } |
| |
| // Processing continues in OnFlushDone(). |
| decoder()->Decode( |
| media::DecoderBuffer::CreateEOSBuffer(), |
| BindOnce(&DecoderTemplate::OnFlushDone, WrapWeakPersistent(this))); |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, V8CodecState::Enum::kConfigured); |
| DCHECK(!pending_request_); |
| DCHECK_EQ(request->type, Request::Type::kDecode); |
| DCHECK_GT(num_pending_decodes_, 0u); |
| |
| if (!decoder()) { |
| Shutdown(MakeEncodingError("Decoding error: no decoder found.", |
| media::DecoderStatus::Codes::kNotInitialized)); |
| return false; |
| } |
| |
| if (pending_decodes_.size() + 1 > |
| static_cast<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->empty()) { |
| if (request->status.is_ok()) { |
| Shutdown(MakeEncodingError("Null or empty decoder buffer.", |
| media::DecoderStatus::Codes::kFailed)); |
| } else { |
| Shutdown(MakeEncodingError("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_; |
| ScheduleDequeueEvent(); |
| |
| if (media::MediaTraceIsEnabled()) { |
| request->decode_trace = std::make_unique<media::ScopedDecodeTrace>( |
| GetTraceNames()->decode.c_str(), *request->decoder_buffer); |
| } |
| |
| decoder()->Decode(std::move(request->decoder_buffer), |
| BindOnce(&DecoderTemplate::OnDecodeDone, |
| WrapWeakPersistent(this), pending_decode_id_)); |
| return true; |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsClosed()); |
| DCHECK(!pending_request_); |
| DCHECK_EQ(request->type, Request::Type::kFlush); |
| DCHECK_EQ(state_, V8CodecState::Enum::kConfigured); |
| |
| // flush() can only be called when state = "configured", in which case we |
| // should always have a decoder. |
| DCHECK(decoder()); |
| |
| if (pending_decodes_.size() + 1 > |
| static_cast<size_t>(Traits::GetMaxDecodeRequests(*decoder()))) { |
| // Try again after OnDecodeDone(). |
| return false; |
| } |
| |
| // Processing continues in OnFlushDone(). |
| pending_request_ = request; |
| pending_request_->StartTracing(); |
| |
| decoder()->Decode( |
| media::DecoderBuffer::CreateEOSBuffer(), |
| BindOnce(&DecoderTemplate::OnFlushDone, WrapWeakPersistent(this))); |
| return true; |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsClosed()); |
| DCHECK(!pending_request_); |
| DCHECK_EQ(request->type, Request::Type::kReset); |
| DCHECK_GT(reset_generation_, 0u); |
| |
| // Signal [[codec implementation]] to cease producing output for the previous |
| // configuration. |
| if (decoder()) { |
| pending_request_ = request; |
| pending_request_->StartTracing(); |
| |
| // Processing continues in OnResetDone(). |
| decoder()->Reset( |
| BindOnce(&DecoderTemplate::OnResetDone, WrapWeakPersistent(this))); |
| } |
| |
| return true; |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::Shutdown(DOMException* exception) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsClosed()) |
| return; |
| |
| TRACE_EVENT1(kCategory, GetTraceNames()->shutdown.c_str(), "has_exception", |
| !!exception); |
| |
| shutting_down_ = true; |
| shutting_down_due_to_error_ = exception; |
| |
| // Abort pending work (otherwise it will never complete) |
| if (pending_request_) { |
| if (pending_request_->resolver) { |
| pending_request_->resolver.Release()->Reject( |
| exception |
| ? exception |
| : MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kAbortError, "Aborted due to close()")); |
| } |
| |
| pending_request_.Release()->EndTracing(/*shutting_down=*/true); |
| } |
| |
| // Abort all upcoming work. |
| ResetAlgorithm(); |
| ReleaseCodecPressure(); |
| |
| // 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. Use DeleteSoon() to avoid |
| // deleting decoder_ when its callback (e.g. OnDecodeDone()) may be below us |
| // in the stack. |
| main_thread_task_runner_->DeleteSoon(FROM_HERE, std::move(decoder_)); |
| |
| if (pending_request_) { |
| // This request was added as part of calling ResetAlgorithm above. However, |
| // OnResetDone() will never execute, since we are now in a kClosed state, |
| // and |decoder_| has been reset. |
| DCHECK_EQ(pending_request_->type, Request::Type::kReset); |
| pending_request_.Release()->EndTracing(/*shutting_down=*/true); |
| } |
| |
| bool trace_enabled = false; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED(kCategory, &trace_enabled); |
| if (trace_enabled) { |
| for (auto& pending_decode : pending_decodes_) |
| pending_decode.value->decode_trace.reset(); |
| } |
| |
| pending_decodes_.clear(); |
| num_pending_decodes_ = 0; |
| ScheduleDequeueEvent(); |
| |
| if (exception) { |
| error_cb->InvokeAndReportException(nullptr, exception); |
| } |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::ResetAlgorithm() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 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; |
| ScheduleDequeueEvent(); |
| |
| // Since configure is always required after reset we can drop any cached |
| // configuration. |
| active_config_.reset(); |
| |
| 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>::OnFlushDone(media::DecoderStatus status) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsClosed()) |
| return; |
| |
| DCHECK(pending_request_); |
| DCHECK(pending_request_->type == Request::Type::kConfigure || |
| pending_request_->type == Request::Type::kFlush); |
| |
| if (!status.is_ok()) { |
| Shutdown(MakeEncodingError("Error during flush.", status)); |
| return; |
| } |
| |
| // If reset() has been called during the Flush(), we can skip reinitialization |
| // since the client is required to do so manually. |
| const bool is_flush = pending_request_->type == Request::Type::kFlush; |
| if (is_flush && MaybeAbortRequest(pending_request_)) { |
| pending_request_.Release()->EndTracing(); |
| ProcessRequests(); |
| return; |
| } |
| |
| if (!is_flush) |
| SetHardwarePreference(pending_request_->hw_pref.value()); |
| |
| // Processing continues in OnInitializeDone(). |
| Traits::InitializeDecoder( |
| *decoder(), is_flush ? low_delay_ : pending_request_->low_delay.value(), |
| is_flush ? *active_config_ : *pending_request_->media_config, |
| BindOnce(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)), |
| blink::BindRepeating(&DecoderTemplate::OnOutput, WrapWeakPersistent(this), |
| reset_generation_)); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnInitializeDone(media::DecoderStatus status) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsClosed()) |
| return; |
| |
| DCHECK(pending_request_); |
| DCHECK(pending_request_->type == Request::Type::kConfigure || |
| pending_request_->type == Request::Type::kFlush); |
| |
| const bool is_flush = pending_request_->type == Request::Type::kFlush; |
| if (!status.is_ok()) { |
| std::string error_message; |
| if (is_flush) { |
| error_message = "Error during initialize after flush."; |
| } else if (status.code() == |
| media::DecoderStatus::Codes::kUnsupportedConfig) { |
| error_message = |
| "Unsupported configuration. Check isConfigSupported() prior to " |
| "calling configure()."; |
| } else { |
| error_message = "Decoder initialization error."; |
| } |
| Shutdown(MakeOperationError(error_message, status)); |
| return; |
| } |
| |
| if (is_flush) { |
| pending_request_->resolver.Release()->Resolve(); |
| } else { |
| Traits::UpdateDecoderLog(*decoder(), *pending_request_->media_config, |
| logger_->log()); |
| |
| if (decoder()->IsPlatformDecoder()) |
| ApplyCodecPressure(); |
| else |
| ReleaseCodecPressure(); |
| |
| low_delay_ = pending_request_->low_delay.value(); |
| active_config_ = std::move(pending_request_->media_config); |
| OnActiveConfigChanged(*active_config_); |
| } |
| |
| pending_request_.Release()->EndTracing(); |
| |
| if (!initializing_sync_) |
| ProcessRequests(); |
| else |
| DCHECK(!is_flush); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id, |
| media::DecoderStatus status) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsClosed()) |
| return; |
| |
| auto it = pending_decodes_.find(id); |
| if (it != pending_decodes_.end()) { |
| if (it->value->decode_trace) |
| it->value->decode_trace->EndTrace(status); |
| pending_decodes_.erase(it); |
| } |
| |
| if (!status.is_ok() && |
| status.code() != media::DecoderStatus::Codes::kAborted) { |
| Shutdown(MakeEncodingError("Decoding error.", std::move(status))); |
| return; |
| } |
| |
| ProcessRequests(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnResetDone() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (IsClosed()) |
| return; |
| |
| DCHECK(pending_request_); |
| DCHECK_EQ(pending_request_->type, Request::Type::kReset); |
| |
| pending_request_.Release()->EndTracing(); |
| ProcessRequests(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnOutput(uint32_t reset_generation, |
| scoped_refptr<MediaOutputType> output) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // 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; |
| |
| auto output_or_error = MakeOutput(std::move(output), context); |
| |
| if (!output_or_error.has_value()) { |
| Shutdown(MakeEncodingError("Error creating output from decoded data", |
| std::move(output_or_error).error())); |
| return; |
| } |
| |
| OutputType* blink_output = std::move(output_or_error).value(); |
| |
| TRACE_EVENT_BEGIN1(kCategory, GetTraceNames()->output.c_str(), "timestamp", |
| blink_output->timestamp()); |
| |
| output_cb_->InvokeAndReportException(nullptr, blink_output); |
| |
| TRACE_EVENT_END0(kCategory, GetTraceNames()->output.c_str()); |
| |
| MarkCodecActive(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::TraceQueueSizes() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TRACE_COUNTER_ID2(kCategory, GetTraceNames()->requests_counter.c_str(), |
| trace_counter_id_, "decodes", num_pending_decodes_, "other", |
| requests_.size() - num_pending_decodes_); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::DispatchDequeueEvent(Event* event) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| probe::AsyncTask async_task(GetExecutionContext(), |
| event->async_task_context()); |
| dequeue_event_pending_ = false; |
| DispatchEvent(*event); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::ScheduleDequeueEvent() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (dequeue_event_pending_) |
| return; |
| dequeue_event_pending_ = true; |
| |
| Event* event = Event::Create(event_type_names::kDequeue); |
| event->SetTarget(this); |
| event->async_task_context()->Schedule(GetExecutionContext(), event->type()); |
| |
| main_thread_task_runner_->PostTask( |
| FROM_HERE, BindOnce(&DecoderTemplate<Traits>::DispatchDequeueEvent, |
| WrapWeakPersistent(this), WrapPersistent(event))); |
| } |
| |
| template <typename Traits> |
| ExecutionContext* DecoderTemplate<Traits>::GetExecutionContext() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return ExecutionContextLifecycleObserver::GetExecutionContext(); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::ContextDestroyed() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Deallocate resources and suppress late callbacks from media thread. |
| Shutdown(); |
| } |
| |
| 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_); |
| visitor->Trace(shutting_down_due_to_error_); |
| EventTarget::Trace(visitor); |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| ReclaimableCodec::Trace(visitor); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::OnCodecReclaimed(DOMException* exception) { |
| TRACE_EVENT0(kCategory, GetTraceNames()->reclaimed.c_str()); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(is_applying_codec_pressure()); |
| |
| if (state_.AsEnum() == V8CodecState::Enum::kUnconfigured) { |
| decoder_.reset(); |
| |
| // This codec isn't holding on to any resources, and doesn't need to be |
| // reclaimed. |
| ReleaseCodecPressure(); |
| return; |
| } |
| |
| DCHECK_EQ(state_.AsEnum(), V8CodecState::Enum::kConfigured); |
| Shutdown(exception); |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::HasPendingActivity() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return pending_request_ || !requests_.empty(); |
| } |
| |
| template <typename Traits> |
| bool DecoderTemplate<Traits>::MaybeAbortRequest(Request* request) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (request->reset_generation == reset_generation_) { |
| return false; |
| } |
| |
| if (request->resolver) { |
| request->resolver.Release()->Reject( |
| shutting_down_due_to_error_ |
| ? shutting_down_due_to_error_.Get() |
| : MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kAbortError, |
| shutting_down_ ? "Aborted due to close()" |
| : "Aborted due to reset()")); |
| } |
| return true; |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::Request::Trace(Visitor* visitor) const { |
| visitor->Trace(resolver); |
| } |
| |
| template <typename Traits> |
| const char* DecoderTemplate<Traits>::Request::TraceNameFromType() { |
| using RequestType = typename DecoderTemplate<Traits>::Request::Type; |
| |
| const CodecTraceNames* trace_names = DecoderTemplate<Traits>::GetTraceNames(); |
| |
| switch (type) { |
| case RequestType::kConfigure: |
| return trace_names->configure.c_str(); |
| case RequestType::kDecode: |
| return trace_names->decode.c_str(); |
| case RequestType::kFlush: |
| return trace_names->flush.c_str(); |
| case RequestType::kReset: |
| return trace_names->reset.c_str(); |
| } |
| return "InvalidCodecTraceName"; |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::Request::StartTracing() { |
| #if DCHECK_IS_ON() |
| DCHECK(!is_tracing); |
| is_tracing = true; |
| #endif |
| TRACE_EVENT_BEGIN(kCategory, perfetto::DynamicString(TraceNameFromType()), |
| perfetto::Track::FromPointer(this)); |
| } |
| |
| template <typename Traits> |
| void DecoderTemplate<Traits>::Request::EndTracing(bool shutting_down) { |
| #if DCHECK_IS_ON() |
| DCHECK(is_tracing); |
| is_tracing = false; |
| #endif |
| TRACE_EVENT_END(kCategory, perfetto::Track::FromPointer(this), "completed", |
| !shutting_down); |
| } |
| |
| template <typename Traits> |
| DOMException* DecoderTemplate<Traits>::MakeOperationError( |
| std::string error_msg, |
| media::DecoderStatus status) { |
| if (!decoder_ || decoder_->IsPlatformDecoder()) { |
| return logger_->MakeOperationError(std::move(error_msg), std::move(status)); |
| } |
| return logger_->MakeSoftwareCodecOperationError(std::move(error_msg), |
| std::move(status)); |
| } |
| |
| template <typename Traits> |
| DOMException* DecoderTemplate<Traits>::MakeEncodingError( |
| std::string error_msg, |
| media::DecoderStatus status) { |
| if (!decoder_ || decoder_->IsPlatformDecoder()) { |
| return logger_->MakeEncodingError(std::move(error_msg), std::move(status)); |
| } |
| return logger_->MakeSoftwareCodecEncodingError(std::move(error_msg), |
| std::move(status)); |
| } |
| |
| template class DecoderTemplate<AudioDecoderTraits>; |
| template class DecoderTemplate<VideoDecoderTraits>; |
| |
| } // namespace blink |