blob: 6024cd72b36ec2116abce76e37b13fe918159a69 [file] [log] [blame]
// Copyright 2021 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/encoder_base.h"
#include <string>
#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.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_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_encoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_encode_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_init.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/modules/webcodecs/audio_encoder.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/video_encoder.h"
#include "third_party/blink/renderer/platform/bindings/enumeration_base.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/instrumentation/use_counter.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"
namespace blink {
namespace {
constexpr const char kCategory[] = "media";
base::AtomicSequenceNumber g_sequence_num_for_counters;
} // namespace
// static
template <typename Traits>
const CodecTraceNames* EncoderBase<Traits>::GetTraceNames() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecTraceNames, trace_names,
(Traits::GetName()));
return &trace_names;
}
template <typename Traits>
EncoderBase<Traits>::EncoderBase(ScriptState* script_state,
const InitType* init,
ExceptionState& exception_state)
: ReclaimableCodec(ReclaimableCodec::CodecType::kEncoder,
ExecutionContext::From(script_state)),
state_(V8CodecState::Enum::kUnconfigured),
script_state_(script_state),
trace_counter_id_(g_sequence_num_for_counters.GetNext()) {
auto* context = ExecutionContext::From(script_state);
callback_runner_ = context->GetTaskRunner(TaskType::kInternalMediaRealTime);
logger_ = std::make_unique<CodecLogger<media::EncoderStatus>>(
GetExecutionContext(), callback_runner_);
media::MediaLog* log = logger_->log();
logger_->SendPlayerNameInformation(*context, Traits::GetName());
log->SetProperty<media::MediaLogProperty::kFrameUrl>(
GetExecutionContext()->Url().GetString().Ascii());
output_callback_ = init->output();
if (init->hasError())
error_callback_ = init->error();
}
template <typename Traits>
EncoderBase<Traits>::~EncoderBase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::UmaHistogramSparse(
String::Format("Blink.WebCodecs.%s.FinalStatus", Traits::GetName())
.Ascii()
.c_str(),
static_cast<int>(logger_->status_code()));
}
template <typename Traits>
void EncoderBase<Traits>::configure(const ConfigType* config,
ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ThrowIfCodecStateClosed(state_, "configure", exception_state))
return;
InternalConfigType* parsed_config = ParseConfig(config, exception_state);
if (!parsed_config) {
DCHECK(exception_state.HadException());
return;
}
if (!VerifyCodecSupport(parsed_config, exception_state)) {
DCHECK(exception_state.HadException());
return;
}
MarkCodecActive();
Request* request = MakeGarbageCollected<Request>();
request->reset_count = reset_count_;
if (media_encoder_ && active_config_ &&
state_.AsEnum() == V8CodecState::Enum::kConfigured &&
CanReconfigure(*active_config_, *parsed_config)) {
request->type = Request::Type::kReconfigure;
} else {
state_ = V8CodecState(V8CodecState::Enum::kConfigured);
request->type = Request::Type::kConfigure;
}
active_config_ = parsed_config;
EnqueueRequest(request);
}
template <typename Traits>
void EncoderBase<Traits>::encode(InputType* input,
const EncodeOptionsType* opts,
ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ThrowIfCodecStateClosed(state_, "encode", exception_state))
return;
if (ThrowIfCodecStateUnconfigured(state_, "encode", exception_state))
return;
DCHECK(active_config_);
// This will fail if |input| is already closed.
auto* internal_input = input->clone(exception_state);
if (!internal_input) {
// Remove exceptions relating to cloning closed input.
exception_state.ClearException();
exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
"Cannot encode closed input.");
return;
}
MarkCodecActive();
Request* request = MakeGarbageCollected<Request>();
request->reset_count = reset_count_;
request->type = Request::Type::kEncode;
request->input = internal_input;
request->encodeOpts = opts;
++requested_encodes_;
EnqueueRequest(request);
}
template <typename Traits>
void EncoderBase<Traits>::close(ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ThrowIfCodecStateClosed(state_, "close", exception_state))
return;
state_ = V8CodecState(V8CodecState::Enum::kClosed);
ResetInternal();
output_callback_.Clear();
error_callback_.Clear();
}
template <typename Traits>
ScriptPromise EncoderBase<Traits>::flush(ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ThrowIfCodecStateClosed(state_, "flush", exception_state))
return ScriptPromise();
if (ThrowIfCodecStateUnconfigured(state_, "flush", exception_state))
return ScriptPromise();
MarkCodecActive();
Request* request = MakeGarbageCollected<Request>();
request->resolver =
MakeGarbageCollected<ScriptPromiseResolver>(script_state_);
request->reset_count = reset_count_;
request->type = Request::Type::kFlush;
EnqueueRequest(request);
return request->resolver->Promise();
}
template <typename Traits>
void EncoderBase<Traits>::reset(ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ThrowIfCodecStateClosed(state_, "reset", exception_state))
return;
TRACE_EVENT0(kCategory, GetTraceNames()->reset.c_str());
state_ = V8CodecState(V8CodecState::Enum::kUnconfigured);
ResetInternal();
}
template <typename Traits>
void EncoderBase<Traits>::ResetInternal() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
reset_count_++;
while (!requests_.empty()) {
Request* pending_req = requests_.TakeFirst();
DCHECK(pending_req);
if (pending_req->resolver)
pending_req->resolver.Release()->Resolve();
if (pending_req->input)
pending_req->input.Release()->close();
}
requested_encodes_ = 0;
blocking_request_in_progress_ = false;
// Schedule deletion of |media_encoder_| for later.
// ResetInternal() might be called by an error reporting callback called by
// |media_encoder_|. If we delete it now, this thread might come back up
// the call stack and continu executing code belonging to deleted
// |media_encoder_|.
callback_runner_->DeleteSoon(FROM_HERE, std::move(media_encoder_));
// This codec isn't holding on to any resources, and doesn't need to be
// reclaimed.
ReleaseCodecPressure();
}
template <typename Traits>
void EncoderBase<Traits>::HandleError(DOMException* ex) {
if (state_.AsEnum() == V8CodecState::Enum::kClosed)
return;
TRACE_EVENT0(kCategory, GetTraceNames()->handle_error.c_str());
// Save a temp before we clear the callback.
V8WebCodecsErrorCallback* error_callback = error_callback_.Get();
state_ = V8CodecState(V8CodecState::Enum::kClosed);
ResetInternal();
// Errors are permanent. Shut everything down.
error_callback_.Clear();
output_callback_.Clear();
// Prevent further logging.
logger_->Neuter();
if (!script_state_->ContextIsValid() || !error_callback)
return;
ScriptState::Scope scope(script_state_);
error_callback->InvokeAndReportException(nullptr, ex);
}
template <typename Traits>
void EncoderBase<Traits>::EnqueueRequest(Request* request) {
requests_.push_back(request);
ProcessRequests();
}
template <typename Traits>
void EncoderBase<Traits>::ProcessRequests() {
while (!requests_.empty() && ReadyToProcessNextRequest()) {
TraceQueueSizes();
Request* request = requests_.TakeFirst();
DCHECK(request);
switch (request->type) {
case Request::Type::kConfigure:
ProcessConfigure(request);
break;
case Request::Type::kReconfigure:
ProcessReconfigure(request);
break;
case Request::Type::kEncode:
ProcessEncode(request);
break;
case Request::Type::kFlush:
ProcessFlush(request);
break;
default:
NOTREACHED();
}
}
TraceQueueSizes();
}
template <typename Traits>
bool EncoderBase<Traits>::ReadyToProcessNextRequest() {
return !blocking_request_in_progress_;
}
template <typename Traits>
void EncoderBase<Traits>::ProcessFlush(Request* request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(state_, V8CodecState::Enum::kConfigured);
DCHECK(media_encoder_);
DCHECK_EQ(request->type, Request::Type::kFlush);
auto done_callback = [](EncoderBase<Traits>* self, Request* req,
media::EncoderStatus status) {
DCHECK(req);
DCHECK(req->resolver);
if (!self) {
req->resolver.Release()->Reject();
req->EndTracing(/*aborted=*/true);
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
if (self->reset_count_ != req->reset_count) {
req->resolver.Release()->Reject();
req->EndTracing(/*aborted=*/true);
return;
}
if (status.is_ok()) {
req->resolver.Release()->Resolve();
} else {
self->HandleError(
self->logger_->MakeException("Flushing error.", std::move(status)));
req->resolver.Release()->Reject();
}
req->EndTracing();
self->blocking_request_in_progress_ = false;
self->ProcessRequests();
};
request->StartTracing();
blocking_request_in_progress_ = true;
media_encoder_->Flush(ConvertToBaseOnceCallback(
CrossThreadBindOnce(done_callback, WrapCrossThreadWeakPersistent(this),
WrapCrossThreadPersistent(request))));
}
template <typename Traits>
void EncoderBase<Traits>::OnCodecReclaimed(DOMException* exception) {
TRACE_EVENT0(kCategory, GetTraceNames()->reclaimed.c_str());
DCHECK_EQ(state_.AsEnum(), V8CodecState::Enum::kConfigured);
HandleError(exception);
}
template <typename Traits>
void EncoderBase<Traits>::ContextDestroyed() {
state_ = V8CodecState(V8CodecState::Enum::kClosed);
ResetInternal();
logger_->Neuter();
}
template <typename Traits>
bool EncoderBase<Traits>::HasPendingActivity() const {
return blocking_request_in_progress_ || !requests_.empty();
}
template <typename Traits>
void EncoderBase<Traits>::TraceQueueSizes() const {
TRACE_COUNTER_ID2(kCategory, GetTraceNames()->requests_counter.c_str(),
trace_counter_id_, "encodes", requested_encodes_, "other",
requests_.size() - requested_encodes_);
}
template <typename Traits>
void EncoderBase<Traits>::Trace(Visitor* visitor) const {
visitor->Trace(active_config_);
visitor->Trace(script_state_);
visitor->Trace(output_callback_);
visitor->Trace(error_callback_);
visitor->Trace(requests_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
ReclaimableCodec::Trace(visitor);
}
template <typename Traits>
void EncoderBase<Traits>::Request::Trace(Visitor* visitor) const {
visitor->Trace(input);
visitor->Trace(encodeOpts);
visitor->Trace(resolver);
}
template <typename Traits>
const char* EncoderBase<Traits>::Request::TraceNameFromType() {
using RequestType = typename EncoderBase<Traits>::Request::Type;
const CodecTraceNames* trace_names = EncoderBase<Traits>::GetTraceNames();
switch (type) {
case RequestType::kConfigure:
return trace_names->configure.c_str();
case RequestType::kEncode:
return trace_names->encode.c_str();
case RequestType::kFlush:
return trace_names->flush.c_str();
case RequestType::kReconfigure:
return trace_names->reconfigure.c_str();
}
return "InvalidCodecTraceName";
}
template <typename Traits>
void EncoderBase<Traits>::Request::StartTracingVideoEncode(bool is_keyframe) {
#if DCHECK_IS_ON()
DCHECK(!is_tracing);
is_tracing = true;
#endif
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kCategory, TraceNameFromType(), this,
"key_frame", is_keyframe);
}
template <typename Traits>
void EncoderBase<Traits>::Request::StartTracing() {
#if DCHECK_IS_ON()
DCHECK(!is_tracing);
is_tracing = true;
#endif
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kCategory, TraceNameFromType(), this);
}
template <typename Traits>
void EncoderBase<Traits>::Request::EndTracing(bool aborted) {
#if DCHECK_IS_ON()
DCHECK(is_tracing);
is_tracing = false;
#endif
TRACE_EVENT_NESTABLE_ASYNC_END1(kCategory, TraceNameFromType(), this,
"aborted", aborted);
}
template class EncoderBase<VideoEncoderTraits>;
template class EncoderBase<AudioEncoderTraits>;
} // namespace blink