Convert the printf-style MOZ_LOG calls in this area to the type-safe MOZ_LOG_FMT macro, which validates the format string against its arguments at compile time. This is a mechanical refactor with no behavior change. Differential Revision: https://phabricator.services.mozilla.com/D304269
966 lines
34 KiB
C++
966 lines
34 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "DecoderTemplate.h"
|
|
|
|
#include <atomic>
|
|
#include <utility>
|
|
|
|
#include "DecoderTypes.h"
|
|
#include "MediaInfo.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/Try.h"
|
|
#include "mozilla/dom/DOMException.h"
|
|
#include "mozilla/dom/Event.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/VideoDecoderBinding.h"
|
|
#include "mozilla/dom/VideoFrame.h"
|
|
#include "mozilla/dom/WorkerCommon.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsString.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
mozilla::LazyLogModule gWebCodecsLog("WebCodecs");
|
|
|
|
namespace mozilla::dom {
|
|
|
|
#ifdef LOG_INTERNAL
|
|
# undef LOG_INTERNAL
|
|
#endif // LOG_INTERNAL
|
|
#define LOG_INTERNAL(level, msg, ...) \
|
|
MOZ_LOG_FMT(gWebCodecsLog, LogLevel::level, msg, ##__VA_ARGS__)
|
|
|
|
#ifdef LOG
|
|
# undef LOG
|
|
#endif // LOG
|
|
#define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
|
|
|
|
#ifdef LOGW
|
|
# undef LOGW
|
|
#endif // LOGW
|
|
#define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
|
|
|
|
#ifdef LOGE
|
|
# undef LOGE
|
|
#endif // LOGE
|
|
#define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
|
|
|
|
#ifdef LOGV
|
|
# undef LOGV
|
|
#endif // LOGV
|
|
#define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
|
|
|
|
#define AUTO_DECODER_MARKER(var, postfix) \
|
|
AutoWebCodecsMarker var(DecoderType::Name.get(), postfix);
|
|
|
|
/*
|
|
* Below are ControlMessage classes implementations
|
|
*/
|
|
|
|
template <typename DecoderType>
|
|
DecoderTemplate<DecoderType>::ConfigureMessage::ConfigureMessage(
|
|
WebCodecsId aConfigId, already_AddRefed<ConfigTypeInternal> aConfig)
|
|
: ControlMessage(aConfigId),
|
|
mConfig(aConfig),
|
|
mCodec(NS_ConvertUTF16toUTF8(mConfig->mCodec)) {}
|
|
|
|
template <typename DecoderType>
|
|
nsCString DecoderTemplate<DecoderType>::ConfigureMessage::ToString() const {
|
|
return nsPrintfCString("configure #%zu (%s)", ControlMessage::mConfigId,
|
|
mCodec.get());
|
|
}
|
|
|
|
/* static */
|
|
template <typename DecoderType>
|
|
typename DecoderTemplate<DecoderType>::ConfigureMessage*
|
|
DecoderTemplate<DecoderType>::ConfigureMessage::Create(
|
|
already_AddRefed<ConfigTypeInternal> aConfig) {
|
|
// This needs to be atomic since this can run on the main thread or worker
|
|
// thread.
|
|
static std::atomic<WebCodecsId> sNextId = 0;
|
|
return new ConfigureMessage(++sNextId, std::move(aConfig));
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
DecoderTemplate<DecoderType>::DecodeMessage::DecodeMessage(
|
|
WebCodecsId aSeqId, WebCodecsId aConfigId,
|
|
UniquePtr<InputTypeInternal>&& aData)
|
|
: ControlMessage(aConfigId), mSeqId(aSeqId), mData(std::move(aData)) {}
|
|
|
|
template <typename DecoderType>
|
|
nsCString DecoderTemplate<DecoderType>::DecodeMessage::ToString() const {
|
|
return nsPrintfCString("decode #%zu (config #%zu)", mSeqId,
|
|
ControlMessage::mConfigId);
|
|
}
|
|
|
|
static int64_t GenerateUniqueId() {
|
|
// This needs to be atomic since this can run on the main thread or worker
|
|
// thread.
|
|
static std::atomic<int64_t> sNextId = 0;
|
|
return ++sNextId;
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
DecoderTemplate<DecoderType>::FlushMessage::FlushMessage(WebCodecsId aSeqId,
|
|
WebCodecsId aConfigId)
|
|
: ControlMessage(aConfigId),
|
|
mSeqId(aSeqId),
|
|
mUniqueId(GenerateUniqueId()) {}
|
|
|
|
template <typename DecoderType>
|
|
nsCString DecoderTemplate<DecoderType>::FlushMessage::ToString() const {
|
|
return nsPrintfCString("flush #%zu (config #%zu)", mSeqId,
|
|
ControlMessage::mConfigId);
|
|
}
|
|
|
|
/*
|
|
* Below are DecoderTemplate implementation
|
|
*/
|
|
|
|
template <typename DecoderType>
|
|
DecoderTemplate<DecoderType>::DecoderTemplate(
|
|
nsIGlobalObject* aGlobalObject,
|
|
RefPtr<WebCodecsErrorCallback>&& aErrorCallback,
|
|
RefPtr<OutputCallbackType>&& aOutputCallback)
|
|
: DOMEventTargetHelper(aGlobalObject),
|
|
mErrorCallback(std::move(aErrorCallback)),
|
|
mOutputCallback(std::move(aOutputCallback)),
|
|
mState(CodecState::Unconfigured),
|
|
mKeyChunkRequired(true),
|
|
mMessageQueueBlocked(false),
|
|
mDecodeQueueSize(0),
|
|
mDequeueEventScheduled(false),
|
|
mLatestConfigureId(0),
|
|
mDecodeCounter(0),
|
|
mFlushCounter(0) {}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::Configure(const ConfigType& aConfig,
|
|
ErrorResult& aRv) {
|
|
AssertIsOnOwningThread();
|
|
|
|
LOG("{} {}, Configure: codec {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
NS_ConvertUTF16toUTF8(aConfig.mCodec).get());
|
|
|
|
nsCString errorMessage;
|
|
if (!DecoderType::Validate(aConfig, errorMessage)) {
|
|
LOG("Configure: Validate error: {}", errorMessage.get());
|
|
aRv.ThrowTypeError(errorMessage);
|
|
return;
|
|
}
|
|
|
|
if (mState == CodecState::Closed) {
|
|
LOG("Configure: CodecState::Closed, rejecting with InvalidState");
|
|
aRv.ThrowInvalidStateError("The codec is no longer usable");
|
|
return;
|
|
}
|
|
|
|
// Clone a ConfigType as the active decoder config.
|
|
RefPtr<ConfigTypeInternal> config =
|
|
DecoderType::CreateConfigInternal(aConfig);
|
|
if (!config) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED); // Invalid description data.
|
|
return;
|
|
}
|
|
|
|
// Audio encoders are all software, no need to do anything.
|
|
// This is incomplete and will be implemented fully in bug 1967793
|
|
if constexpr (std::is_same_v<ConfigType, VideoDecoderConfig>) {
|
|
ApplyResistFingerprintingIfNeeded(config, GetRelevantGlobal());
|
|
}
|
|
|
|
mState = CodecState::Configured;
|
|
mKeyChunkRequired = true;
|
|
mDecodeCounter = 0;
|
|
mFlushCounter = 0;
|
|
|
|
mControlMessageQueue.emplace(
|
|
UniquePtr<ControlMessage>(ConfigureMessage::Create(config.forget())));
|
|
mLatestConfigureId = mControlMessageQueue.back()->mConfigId;
|
|
LOG("{} {} enqueues {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
mControlMessageQueue.back()->ToString().get());
|
|
ProcessControlMessageQueue();
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::Decode(InputType& aInput, ErrorResult& aRv) {
|
|
AssertIsOnOwningThread();
|
|
|
|
LOG("{} {}, Decode {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
aInput.ToString().get());
|
|
|
|
if (mState != CodecState::Configured) {
|
|
aRv.ThrowInvalidStateError("Decoder must be configured first");
|
|
return;
|
|
}
|
|
|
|
if (mKeyChunkRequired) {
|
|
// TODO: Verify input's data is truly a key chunk
|
|
if (!DecoderType::IsKeyChunk(aInput)) {
|
|
aRv.ThrowDataError(
|
|
nsPrintfCString("%s needs a key chunk", DecoderType::Name.get()));
|
|
return;
|
|
}
|
|
mKeyChunkRequired = false;
|
|
}
|
|
|
|
mAsyncDurationTracker.Start(
|
|
aInput.Timestamp(),
|
|
AutoWebCodecsMarker(DecoderType::Name.get(), ".decode-duration"));
|
|
mDecodeQueueSize += 1;
|
|
mControlMessageQueue.emplace(UniquePtr<ControlMessage>(
|
|
new DecodeMessage(++mDecodeCounter, mLatestConfigureId,
|
|
DecoderType::CreateInputInternal(aInput))));
|
|
LOGV("{} {} enqueues {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
mControlMessageQueue.back()->ToString().get());
|
|
ProcessControlMessageQueue();
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
already_AddRefed<Promise> DecoderTemplate<DecoderType>::Flush(
|
|
ErrorResult& aRv) {
|
|
AssertIsOnOwningThread();
|
|
|
|
LOG("{} {}, Flush", DecoderType::Name.get(), fmt::ptr(this));
|
|
|
|
if (mState != CodecState::Configured) {
|
|
LOG("{} {}, wrong state!", DecoderType::Name.get(), fmt::ptr(this));
|
|
aRv.ThrowInvalidStateError("Decoder must be configured first");
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return p.forget();
|
|
}
|
|
|
|
mKeyChunkRequired = true;
|
|
|
|
auto msg = UniquePtr<ControlMessage>(
|
|
new FlushMessage(++mFlushCounter, mLatestConfigureId));
|
|
const auto flushPromiseId = msg->AsFlushMessage()->mUniqueId;
|
|
MOZ_ASSERT(!mPendingFlushPromises.Contains(flushPromiseId));
|
|
mPendingFlushPromises.Insert(flushPromiseId, p);
|
|
|
|
mControlMessageQueue.emplace(std::move(msg));
|
|
|
|
LOG("{} {} enqueues {}, with unique id {}", DecoderType::Name.get(),
|
|
fmt::ptr(this), mControlMessageQueue.back()->ToString().get(),
|
|
flushPromiseId);
|
|
ProcessControlMessageQueue();
|
|
return p.forget();
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::Reset(ErrorResult& aRv) {
|
|
AssertIsOnOwningThread();
|
|
|
|
LOG("{} {}, Reset", DecoderType::Name.get(), fmt::ptr(this));
|
|
|
|
if (auto r = ResetInternal(NS_ERROR_DOM_ABORT_ERR); r.isErr()) {
|
|
aRv.Throw(r.unwrapErr());
|
|
}
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::Close(ErrorResult& aRv) {
|
|
AssertIsOnOwningThread();
|
|
|
|
LOG("{} {}, Close", DecoderType::Name.get(), fmt::ptr(this));
|
|
|
|
if (auto r = CloseInternalWithAbort(); r.isErr()) {
|
|
aRv.Throw(r.unwrapErr());
|
|
}
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
Result<Ok, nsresult> DecoderTemplate<DecoderType>::ResetInternal(
|
|
const nsresult& aResult) {
|
|
AssertIsOnOwningThread();
|
|
|
|
if (mState == CodecState::Closed) {
|
|
return Err(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
}
|
|
|
|
mState = CodecState::Unconfigured;
|
|
mDecodeCounter = 0;
|
|
mFlushCounter = 0;
|
|
|
|
CancelPendingControlMessagesAndFlushPromises(aResult);
|
|
DestroyDecoderAgentIfAny();
|
|
|
|
if (mDecodeQueueSize > 0) {
|
|
mDecodeQueueSize = 0;
|
|
ScheduleDequeueEventIfNeeded();
|
|
}
|
|
|
|
LOG("{} {} now has its message queue unblocked", DecoderType::Name.get(),
|
|
fmt::ptr(this));
|
|
mMessageQueueBlocked = false;
|
|
|
|
return Ok();
|
|
}
|
|
template <typename DecoderType>
|
|
Result<Ok, nsresult> DecoderTemplate<DecoderType>::CloseInternalWithAbort() {
|
|
AssertIsOnOwningThread();
|
|
|
|
MOZ_TRY(ResetInternal(NS_ERROR_DOM_ABORT_ERR));
|
|
mState = CodecState::Closed;
|
|
return Ok();
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::CloseInternal(const nsresult& aResult) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aResult != NS_ERROR_DOM_ABORT_ERR, "Use CloseInternalWithAbort");
|
|
|
|
// Return early if already closed. This can happen when async error handling
|
|
// tasks race with user-initiated close().
|
|
if (mState == CodecState::Closed) {
|
|
return;
|
|
}
|
|
|
|
auto r = ResetInternal(aResult);
|
|
if (r.isErr()) {
|
|
nsCString name;
|
|
GetErrorName(r.unwrapErr(), name);
|
|
LOGE("Error in ResetInternal during CloseInternal: {}", name.get());
|
|
}
|
|
mState = CodecState::Closed;
|
|
nsCString error;
|
|
GetErrorName(aResult, error);
|
|
LOGE("{} {} Close on error: {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
error.get());
|
|
ReportError(aResult);
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::ReportError(const nsresult& aResult) {
|
|
AssertIsOnOwningThread();
|
|
|
|
RefPtr<DOMException> e = DOMException::Create(aResult);
|
|
RefPtr<WebCodecsErrorCallback> cb(mErrorCallback);
|
|
cb->Call(*e);
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::OutputDecodedData(
|
|
const nsTArray<RefPtr<MediaData>>&& aData,
|
|
const ConfigTypeInternal& aConfig) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
|
|
if (!GetParentObject()) {
|
|
LOGE("{} {} Canceling output callbacks since parent-object is gone",
|
|
DecoderType::Name.get(), fmt::ptr(this));
|
|
return;
|
|
}
|
|
|
|
nsTArray<RefPtr<OutputType>> frames =
|
|
DecodedDataToOutputType(GetParentObject(), std::move(aData), aConfig);
|
|
RefPtr<OutputCallbackType> cb(mOutputCallback);
|
|
for (RefPtr<OutputType>& frame : frames) {
|
|
LOG("Outputing decoded data: ts: {}", frame->Timestamp());
|
|
RefPtr<OutputType> f = frame;
|
|
mAsyncDurationTracker.End(f->Timestamp());
|
|
cb->Call((OutputType&)(*f));
|
|
}
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::ScheduleDequeueEventIfNeeded() {
|
|
AssertIsOnOwningThread();
|
|
|
|
if (mDequeueEventScheduled) {
|
|
return;
|
|
}
|
|
mDequeueEventScheduled = true;
|
|
|
|
QueueATask("dequeue event task", [self = RefPtr{this}]() {
|
|
self->FireEvent(nsGkAtoms::ondequeue, u"dequeue"_ns);
|
|
self->mDequeueEventScheduled = false;
|
|
});
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
nsresult DecoderTemplate<DecoderType>::FireEvent(nsAtom* aTypeWithOn,
|
|
const nsAString& aEventType) {
|
|
if (aTypeWithOn && !HasListenersFor(aTypeWithOn)) {
|
|
LOGV("{} {} has no {} event listener", DecoderType::Name.get(),
|
|
fmt::ptr(this), NS_ConvertUTF16toUTF8(aEventType).get());
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
LOGV("Dispatch {} event to {} {}", NS_ConvertUTF16toUTF8(aEventType).get(),
|
|
DecoderType::Name.get(), fmt::ptr(this));
|
|
RefPtr<Event> event = new Event(this, nullptr, nullptr);
|
|
event->InitEvent(aEventType, true, true);
|
|
event->SetTrusted(true);
|
|
this->DispatchEvent(*event);
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::ProcessControlMessageQueue() {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
|
|
while (!mMessageQueueBlocked && !mControlMessageQueue.empty()) {
|
|
UniquePtr<ControlMessage>& msg = mControlMessageQueue.front();
|
|
if (msg->AsConfigureMessage()) {
|
|
if (ProcessConfigureMessage(msg) ==
|
|
MessageProcessedResult::NotProcessed) {
|
|
break;
|
|
}
|
|
} else if (msg->AsDecodeMessage()) {
|
|
if (ProcessDecodeMessage(msg) == MessageProcessedResult::NotProcessed) {
|
|
break;
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(msg->AsFlushMessage());
|
|
if (ProcessFlushMessage(msg) == MessageProcessedResult::NotProcessed) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::CancelPendingControlMessagesAndFlushPromises(
|
|
const nsresult& aResult) {
|
|
AssertIsOnOwningThread();
|
|
|
|
// Cancel the message that is being processed.
|
|
if (mProcessingMessage) {
|
|
LOG("{} {} cancels current {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
mProcessingMessage->ToString().get());
|
|
mProcessingMessage->Cancel();
|
|
mProcessingMessage.reset();
|
|
}
|
|
|
|
// Clear the message queue.
|
|
while (!mControlMessageQueue.empty()) {
|
|
LOG("{} {} cancels pending {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
mControlMessageQueue.front()->ToString().get());
|
|
MOZ_ASSERT(!mControlMessageQueue.front()->IsProcessing());
|
|
mControlMessageQueue.pop();
|
|
}
|
|
|
|
// If there are pending flush promises, reject them.
|
|
mPendingFlushPromises.Clear([&](const int64_t& id, const RefPtr<Promise>& p) {
|
|
LOG("{} {}, reject the promise for flush {} (unique id)",
|
|
DecoderType::Name.get(), fmt::ptr(this), id);
|
|
p->MaybeReject(aResult);
|
|
});
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
template <typename Func>
|
|
void DecoderTemplate<DecoderType>::QueueATask(const char* aName,
|
|
Func&& aSteps) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(
|
|
NS_NewRunnableFunction(aName, std::forward<Func>(aSteps))));
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
MessageProcessedResult DecoderTemplate<DecoderType>::ProcessConfigureMessage(
|
|
UniquePtr<ControlMessage>& aMessage) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
MOZ_ASSERT(aMessage->AsConfigureMessage());
|
|
|
|
AUTO_DECODER_MARKER(marker, ".configure");
|
|
|
|
if (mProcessingMessage) {
|
|
LOG("{} {} is processing {}. Defer {}", DecoderType::Name.get(),
|
|
fmt::ptr(this), mProcessingMessage->ToString().get(),
|
|
aMessage->ToString().get());
|
|
return MessageProcessedResult::NotProcessed;
|
|
}
|
|
|
|
mProcessingMessage.reset(aMessage.release());
|
|
mControlMessageQueue.pop();
|
|
|
|
ConfigureMessage* msg = mProcessingMessage->AsConfigureMessage();
|
|
LOG("{} {} starts processing {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
msg->ToString().get());
|
|
|
|
DestroyDecoderAgentIfAny();
|
|
|
|
mMessageQueueBlocked = true;
|
|
|
|
nsAutoCString errorMessage;
|
|
auto i = DecoderType::CreateTrackInfo(msg->Config());
|
|
if (i.isErr()) {
|
|
nsCString res;
|
|
GetErrorName(i.unwrapErr(), res);
|
|
errorMessage.AppendPrintf("CreateTrackInfo failed: %s", res.get());
|
|
} else if (!DecoderType::IsSupported(msg->Config())) {
|
|
errorMessage.Append("Not supported.");
|
|
} else if (!CreateDecoderAgent(msg->mConfigId, msg->TakeConfig(),
|
|
i.unwrap())) {
|
|
errorMessage.Append("DecoderAgent creation failed.");
|
|
}
|
|
if (!errorMessage.IsEmpty()) {
|
|
LOGE("{} {} ProcessConfigureMessage error (sync): {}",
|
|
DecoderType::Name.get(), fmt::ptr(this), errorMessage.get());
|
|
|
|
mProcessingMessage.reset();
|
|
QueueATask("Error while configuring decoder",
|
|
[self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
self->CloseInternal(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
|
});
|
|
return MessageProcessedResult::Processed;
|
|
}
|
|
|
|
MOZ_ASSERT(mAgent);
|
|
MOZ_ASSERT(mActiveConfig);
|
|
|
|
LOG("{} {} now blocks message-queue-processing", DecoderType::Name.get(),
|
|
fmt::ptr(this));
|
|
|
|
bool preferSW = mActiveConfig->mHardwareAcceleration ==
|
|
HardwareAcceleration::Prefer_software;
|
|
bool lowLatency = mActiveConfig->mOptimizeForLatency.isSome() &&
|
|
mActiveConfig->mOptimizeForLatency.value();
|
|
|
|
mAgent->Configure(preferSW, lowLatency)
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, id = mAgent->mId, m = std::move(marker)](
|
|
const DecoderAgent::ConfigurePromise::ResolveOrRejectValue&
|
|
aResult) mutable {
|
|
MOZ_ASSERT(self->mProcessingMessage);
|
|
MOZ_ASSERT(self->mProcessingMessage->AsConfigureMessage());
|
|
MOZ_ASSERT(self->mState == CodecState::Configured);
|
|
MOZ_ASSERT(self->mAgent);
|
|
MOZ_ASSERT(id == self->mAgent->mId);
|
|
MOZ_ASSERT(self->mActiveConfig);
|
|
|
|
ConfigureMessage* msg =
|
|
self->mProcessingMessage->AsConfigureMessage();
|
|
LOG("{} {}, DecoderAgent #{} {} has been {}. now unblocks "
|
|
"message-queue-processing",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
msg->ToString().get(),
|
|
aResult.IsResolve() ? "resolved" : "rejected");
|
|
|
|
msg->Complete();
|
|
self->mProcessingMessage.reset();
|
|
|
|
if (aResult.IsReject()) {
|
|
// The spec asks to close the decoder with an
|
|
// NotSupportedError so we log the exact error here.
|
|
const MediaResult& error = aResult.RejectValue();
|
|
LOGE("{} {}, DecoderAgent #{} failed to configure: {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
error.Description().get());
|
|
|
|
self->QueueATask(
|
|
"Error during configure",
|
|
[self = RefPtr{self}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
self->CloseInternal(
|
|
NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
|
|
});
|
|
return;
|
|
}
|
|
|
|
LOG("{} {}, DecoderAgent #{} configured successfully. {} decode "
|
|
"requests are pending",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
self->mDecodeQueueSize);
|
|
self->mMessageQueueBlocked = false;
|
|
self->ProcessControlMessageQueue();
|
|
})
|
|
->Track(msg->Request());
|
|
|
|
return MessageProcessedResult::Processed;
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
MessageProcessedResult DecoderTemplate<DecoderType>::ProcessDecodeMessage(
|
|
UniquePtr<ControlMessage>& aMessage) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
MOZ_ASSERT(aMessage->AsDecodeMessage());
|
|
|
|
AUTO_DECODER_MARKER(marker, ".decode-process");
|
|
|
|
if (mProcessingMessage) {
|
|
LOGV("{} {} is processing {}. Defer {}", DecoderType::Name.get(),
|
|
fmt::ptr(this), mProcessingMessage->ToString().get(),
|
|
aMessage->ToString().get());
|
|
return MessageProcessedResult::NotProcessed;
|
|
}
|
|
|
|
mProcessingMessage.reset(aMessage.release());
|
|
mControlMessageQueue.pop();
|
|
|
|
DecodeMessage* msg = mProcessingMessage->AsDecodeMessage();
|
|
LOGV("{} {} starts processing {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
msg->ToString().get());
|
|
|
|
mDecodeQueueSize -= 1;
|
|
ScheduleDequeueEventIfNeeded();
|
|
|
|
// Treat it like decode error if no DecoderAgent is available or the encoded
|
|
// data is invalid.
|
|
auto closeOnError = [&]() {
|
|
mProcessingMessage.reset();
|
|
QueueATask("Error during decode",
|
|
[self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
self->CloseInternal(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
|
|
});
|
|
return MessageProcessedResult::Processed;
|
|
};
|
|
|
|
if (!mAgent) {
|
|
LOGE("{} {} is not configured", DecoderType::Name.get(), fmt::ptr(this));
|
|
return closeOnError();
|
|
}
|
|
|
|
MOZ_ASSERT(mActiveConfig);
|
|
RefPtr<MediaRawData> data = InputDataToMediaRawData(
|
|
std::move(msg->mData), *(mAgent->mInfo), *mActiveConfig);
|
|
if (!data) {
|
|
LOGE("{} {}, data for {} is empty or invalid", DecoderType::Name.get(),
|
|
fmt::ptr(this), msg->ToString().get());
|
|
return closeOnError();
|
|
}
|
|
|
|
mAgent->Decode(data.get())
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, id = mAgent->mId, m = std::move(marker)](
|
|
DecoderAgent::DecodePromise::ResolveOrRejectValue&&
|
|
aResult) mutable {
|
|
MOZ_ASSERT(self->mProcessingMessage);
|
|
MOZ_ASSERT(self->mProcessingMessage->AsDecodeMessage());
|
|
MOZ_ASSERT(self->mState == CodecState::Configured);
|
|
MOZ_ASSERT(self->mAgent);
|
|
MOZ_ASSERT(id == self->mAgent->mId);
|
|
MOZ_ASSERT(self->mActiveConfig);
|
|
|
|
DecodeMessage* msg = self->mProcessingMessage->AsDecodeMessage();
|
|
LOGV("{} {}, DecoderAgent #{} {} has been {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
msg->ToString().get(),
|
|
aResult.IsResolve() ? "resolved" : "rejected");
|
|
|
|
nsCString msgStr = msg->ToString();
|
|
|
|
msg->Complete();
|
|
self->mProcessingMessage.reset();
|
|
|
|
if (aResult.IsReject()) {
|
|
// The spec asks to queue a task to run close the decoder
|
|
// with an EncodingError so we log the exact error here.
|
|
const MediaResult& error = aResult.RejectValue();
|
|
LOGE("{} {}, DecoderAgent #{} {} failed: {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
msgStr.get(), error.Description().get());
|
|
self->QueueATask(
|
|
"Error during decode runnable",
|
|
[self = RefPtr{self}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
self->CloseInternal(
|
|
NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
|
|
});
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aResult.IsResolve());
|
|
nsTArray<RefPtr<MediaData>> data =
|
|
std::move(aResult.ResolveValue());
|
|
if (data.IsEmpty()) {
|
|
LOGV("{} {} got no data for {}", DecoderType::Name.get(),
|
|
fmt::ptr(self.get()), msgStr.get());
|
|
} else {
|
|
LOGV("{} {}, schedule {} decoded data output for {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()),
|
|
data.Length(), msgStr.get());
|
|
|
|
m.End();
|
|
AUTO_DECODER_MARKER(outMarker, ".decode-output");
|
|
|
|
self->QueueATask("Output Decoded Data",
|
|
[self = RefPtr{self}, data = std::move(data),
|
|
config = RefPtr{self->mActiveConfig},
|
|
om = std::move(outMarker)]()
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY mutable {
|
|
self->OutputDecodedData(std::move(data),
|
|
*config);
|
|
});
|
|
}
|
|
self->ProcessControlMessageQueue();
|
|
})
|
|
->Track(msg->Request());
|
|
|
|
return MessageProcessedResult::Processed;
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
MessageProcessedResult DecoderTemplate<DecoderType>::ProcessFlushMessage(
|
|
UniquePtr<ControlMessage>& aMessage) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
MOZ_ASSERT(aMessage->AsFlushMessage());
|
|
|
|
AUTO_DECODER_MARKER(marker, ".flush");
|
|
|
|
if (mProcessingMessage) {
|
|
LOG("{} {} is processing {}. Defer {}", DecoderType::Name.get(),
|
|
fmt::ptr(this), mProcessingMessage->ToString().get(),
|
|
aMessage->ToString().get());
|
|
return MessageProcessedResult::NotProcessed;
|
|
}
|
|
|
|
mProcessingMessage.reset(aMessage.release());
|
|
mControlMessageQueue.pop();
|
|
|
|
FlushMessage* msg = mProcessingMessage->AsFlushMessage();
|
|
LOG("{} {} starts processing {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
msg->ToString().get());
|
|
|
|
// No agent, no thing to do. The promise has been rejected with the
|
|
// appropriate error in ResetInternal already.
|
|
if (!mAgent) {
|
|
LOGE("{} {} no agent, nothing to do", DecoderType::Name.get(),
|
|
fmt::ptr(this));
|
|
mProcessingMessage.reset();
|
|
return MessageProcessedResult::Processed;
|
|
}
|
|
|
|
mAgent->DrainAndFlush()
|
|
->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, id = mAgent->mId, m = std::move(marker),
|
|
this](DecoderAgent::DecodePromise::ResolveOrRejectValue&&
|
|
aResult) mutable {
|
|
MOZ_ASSERT(self->mProcessingMessage);
|
|
MOZ_ASSERT(self->mProcessingMessage->AsFlushMessage());
|
|
MOZ_ASSERT(self->mState == CodecState::Configured);
|
|
MOZ_ASSERT(self->mAgent);
|
|
MOZ_ASSERT(id == self->mAgent->mId);
|
|
MOZ_ASSERT(self->mActiveConfig);
|
|
|
|
FlushMessage* msg = self->mProcessingMessage->AsFlushMessage();
|
|
LOG("{} {}, DecoderAgent #{} {} has been {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
msg->ToString().get(),
|
|
aResult.IsResolve() ? "resolved" : "rejected");
|
|
|
|
nsCString msgStr = msg->ToString();
|
|
|
|
msg->Complete();
|
|
|
|
const auto flushPromiseId = msg->mUniqueId;
|
|
|
|
// If flush failed, it means decoder fails to decode the data
|
|
// sent before, so we treat it like decode error. We reject
|
|
// the promise first and then queue a task to close
|
|
// VideoDecoder with an EncodingError.
|
|
if (aResult.IsReject()) {
|
|
const MediaResult& error = aResult.RejectValue();
|
|
LOGE("{} {}, DecoderAgent #{} failed to flush: {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
error.Description().get());
|
|
// Reject with an EncodingError instead of the error we got
|
|
// above.
|
|
self->QueueATask(
|
|
"Error during flush runnable",
|
|
[self = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
// If Reset() was invoked before this task executes, the
|
|
// promise in mPendingFlushPromises is handled there.
|
|
// Otherwise, the promise is going to be rejected by
|
|
// CloseInternal() below.
|
|
self->mProcessingMessage.reset();
|
|
self->CloseInternal(
|
|
NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
|
|
});
|
|
return;
|
|
}
|
|
|
|
nsTArray<RefPtr<MediaData>> data =
|
|
std::move(aResult.ResolveValue());
|
|
|
|
if (data.IsEmpty()) {
|
|
LOG("{} {} gets no data for {}", DecoderType::Name.get(),
|
|
fmt::ptr(self.get()), msgStr.get());
|
|
} else {
|
|
LOG("{} {}, schedule {} decoded data output for {}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), data.Length(),
|
|
msgStr.get());
|
|
}
|
|
|
|
m.End();
|
|
AUTO_DECODER_MARKER(outMarker, ".flush-output");
|
|
|
|
self->QueueATask(
|
|
"Flush: output decoding data task",
|
|
[self = RefPtr{self}, data = std::move(data),
|
|
config = RefPtr{self->mActiveConfig}, flushPromiseId,
|
|
om = std::move(
|
|
outMarker)]() MOZ_CAN_RUN_SCRIPT_BOUNDARY mutable {
|
|
self->OutputDecodedData(std::move(data), *config);
|
|
// If Reset() was invoked before this task executes, or
|
|
// during the output callback above in the execution of
|
|
// this task, the promise in mPendingFlushPromises is
|
|
// handled there. Otherwise, the promise is resolved here.
|
|
if (Maybe<RefPtr<Promise>> p =
|
|
self->mPendingFlushPromises.Take(flushPromiseId)) {
|
|
LOG("{} {}, resolving the promise for flush {} (unique id)",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()),
|
|
flushPromiseId);
|
|
p.value()->MaybeResolveWithUndefined();
|
|
}
|
|
});
|
|
self->mProcessingMessage.reset();
|
|
self->ProcessControlMessageQueue();
|
|
})
|
|
->Track(msg->Request());
|
|
|
|
return MessageProcessedResult::Processed;
|
|
}
|
|
|
|
// CreateDecoderAgent will create an DecoderAgent paired with a xpcom-shutdown
|
|
// blocker and a worker-reference. Besides the needs mentioned in the header
|
|
// file, the blocker and the worker-reference also provides an entry point for
|
|
// us to clean up the resources. Other than the decoder dtor, Reset(), or
|
|
// Close(), the resources should be cleaned up in the following situations:
|
|
// 1. Decoder on window, closing document
|
|
// 2. Decoder on worker, closing document
|
|
// 3. Decoder on worker, terminating worker
|
|
//
|
|
// In case 1, the entry point to clean up is in the mShutdownBlocker's
|
|
// ShutdownpPomise-resolver. In case 2, the entry point is in mWorkerRef's
|
|
// shutting down callback. In case 3, the entry point is in mWorkerRef's
|
|
// shutting down callback.
|
|
|
|
template <typename DecoderType>
|
|
bool DecoderTemplate<DecoderType>::CreateDecoderAgent(
|
|
DecoderAgent::Id aId, already_AddRefed<ConfigTypeInternal> aConfig,
|
|
UniquePtr<TrackInfo>&& aInfo) {
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == CodecState::Configured);
|
|
MOZ_ASSERT(!mAgent);
|
|
MOZ_ASSERT(!mActiveConfig);
|
|
MOZ_ASSERT(!mShutdownBlocker);
|
|
MOZ_ASSERT_IF(!NS_IsMainThread(), !mWorkerRef);
|
|
|
|
auto resetOnFailure = MakeScopeExit([&]() {
|
|
mAgent = nullptr;
|
|
mActiveConfig = nullptr;
|
|
mShutdownBlocker = nullptr;
|
|
mWorkerRef = nullptr;
|
|
});
|
|
|
|
// If the decoder is on worker, get a worker reference.
|
|
if (!NS_IsMainThread()) {
|
|
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
|
if (NS_WARN_IF(!workerPrivate)) {
|
|
return false;
|
|
}
|
|
|
|
// Clean up all the resources when worker is going away.
|
|
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
|
|
workerPrivate, "DecoderTemplate::CreateDecoderAgent",
|
|
[self = RefPtr{this}]() {
|
|
LOG("{} {}, worker is going away", DecoderType::Name.get(),
|
|
fmt::ptr(self.get()));
|
|
(void)self->ResetInternal(NS_ERROR_DOM_ABORT_ERR);
|
|
});
|
|
if (NS_WARN_IF(!workerRef)) {
|
|
return false;
|
|
}
|
|
|
|
mWorkerRef = new ThreadSafeWorkerRef(workerRef);
|
|
}
|
|
|
|
mAgent = MakeRefPtr<DecoderAgent>(aId, std::move(aInfo));
|
|
mActiveConfig = std::move(aConfig);
|
|
|
|
// ShutdownBlockingTicket requires an unique name to register its own
|
|
// nsIAsyncShutdownBlocker since each blocker needs a distinct name.
|
|
// To do that, we use DecoderAgent's unique id to create a unique name.
|
|
nsAutoString uniqueName;
|
|
uniqueName.AppendPrintf(
|
|
"Blocker for DecoderAgent #%d (codec: %s) @ %p", mAgent->mId,
|
|
NS_ConvertUTF16toUTF8(mActiveConfig->mCodec).get(), mAgent.get());
|
|
|
|
mShutdownBlocker = media::ShutdownBlockingTicket::Create(
|
|
uniqueName, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
|
|
if (!mShutdownBlocker) {
|
|
LOGE("{} {} failed to create {}", DecoderType::Name.get(), fmt::ptr(this),
|
|
NS_ConvertUTF16toUTF8(uniqueName).get());
|
|
return false;
|
|
}
|
|
|
|
// Clean up all the resources when xpcom-will-shutdown arrives since the page
|
|
// is going to be closed.
|
|
mShutdownBlocker->ShutdownPromise()->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, id = mAgent->mId,
|
|
ref = mWorkerRef](bool /* aUnUsed*/) {
|
|
LOG("{} {} gets xpcom-will-shutdown notification for DecoderAgent #{}",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id);
|
|
(void)self->ResetInternal(NS_ERROR_DOM_ABORT_ERR);
|
|
},
|
|
[self = RefPtr{this}, id = mAgent->mId,
|
|
ref = mWorkerRef](bool /* aUnUsed*/) {
|
|
LOG("{} {} removes shutdown-blocker #{} before getting any "
|
|
"notification. DecoderAgent #{} should have been dropped",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id, id);
|
|
MOZ_ASSERT(!self->mAgent || self->mAgent->mId != id);
|
|
});
|
|
|
|
LOG("{} {} creates DecoderAgent #{} @ {} and its shutdown-blocker",
|
|
DecoderType::Name.get(), fmt::ptr(this), mAgent->mId,
|
|
fmt::ptr(mAgent.get()));
|
|
|
|
resetOnFailure.release();
|
|
return true;
|
|
}
|
|
|
|
template <typename DecoderType>
|
|
void DecoderTemplate<DecoderType>::DestroyDecoderAgentIfAny() {
|
|
AssertIsOnOwningThread();
|
|
|
|
if (!mAgent) {
|
|
LOG("{} {} has no DecoderAgent to destroy", DecoderType::Name.get(),
|
|
fmt::ptr(this));
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mActiveConfig);
|
|
MOZ_ASSERT(mShutdownBlocker);
|
|
MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerRef);
|
|
|
|
LOG("{} {} destroys DecoderAgent #{} @ {}", DecoderType::Name.get(),
|
|
fmt::ptr(this), mAgent->mId, fmt::ptr(mAgent.get()));
|
|
mActiveConfig = nullptr;
|
|
RefPtr<DecoderAgent> agent = std::move(mAgent);
|
|
// mShutdownBlocker should be kept alive until the shutdown is done.
|
|
// mWorkerRef is used to ensure this task won't be discarded in worker.
|
|
agent->Shutdown()->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, id = agent->mId, ref = std::move(mWorkerRef),
|
|
blocker = std::move(mShutdownBlocker)](
|
|
const ShutdownPromise::ResolveOrRejectValue& aResult) {
|
|
LOG("{} {}, DecoderAgent #{}'s shutdown has been {}. Drop its "
|
|
"shutdown-blocker now",
|
|
DecoderType::Name.get(), fmt::ptr(self.get()), id,
|
|
aResult.IsResolve() ? "resolved" : "rejected");
|
|
});
|
|
}
|
|
|
|
template class DecoderTemplate<VideoDecoderTraits>;
|
|
template class DecoderTemplate<AudioDecoderTraits>;
|
|
|
|
#undef LOG
|
|
#undef LOGW
|
|
#undef LOGE
|
|
#undef LOGV
|
|
#undef LOG_INTERNAL
|
|
|
|
} // namespace mozilla::dom
|