1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "EncoderAgent.h"
9 #include "PDMFactory.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/Logging.h"
12 #include "nsThreadUtils.h"
14 extern mozilla::LazyLogModule gWebCodecsLog
;
20 #endif // LOG_INTERNAL
21 #define LOG_INTERNAL(level, msg, ...) \
22 MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__))
27 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
32 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
37 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
42 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
44 EncoderAgent::EncoderAgent(WebCodecsId aId
)
46 mOwnerThread(GetCurrentSerialEventTarget()),
47 mPEMFactory(MakeRefPtr
<PEMFactory
>()),
49 mState(State::Unconfigured
) {
50 MOZ_ASSERT(mOwnerThread
);
51 MOZ_ASSERT(mPEMFactory
);
52 LOG("EncoderAgent #%zu (%p) ctor", mId
, this);
55 EncoderAgent::~EncoderAgent() {
56 LOG("EncoderAgent #%zu (%p) dtor", mId
, this);
57 MOZ_ASSERT(mState
== State::Unconfigured
, "encoder released in wrong state");
58 MOZ_ASSERT(!mEncoder
, "encoder must be shutdown");
61 RefPtr
<EncoderAgent::ConfigurePromise
> EncoderAgent::Configure(
62 const EncoderConfig
& aConfig
) {
63 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
64 MOZ_ASSERT(mState
== State::Unconfigured
|| mState
== State::Error
);
65 MOZ_ASSERT(mConfigurePromise
.IsEmpty());
66 MOZ_ASSERT(!mCreateRequest
.Exists());
67 MOZ_ASSERT(!mInitRequest
.Exists());
69 if (mState
== State::Error
) {
70 LOGE("EncoderAgent #%zu (%p) tried to configure in error state", mId
, this);
71 return ConfigurePromise::CreateAndReject(
72 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
73 "Cannot configure in error state"),
77 MOZ_ASSERT(mState
== State::Unconfigured
);
78 MOZ_ASSERT(!mEncoder
);
79 SetState(State::Configuring
);
81 LOG("EncoderAgent #%zu (%p) is creating an encoder (%s)", mId
, this,
82 GetCodecTypeString(aConfig
.mCodec
));
84 RefPtr
<ConfigurePromise
> p
= mConfigurePromise
.Ensure(__func__
);
86 mPEMFactory
->CreateEncoderAsync(aConfig
, dom::GetWebCodecsEncoderTaskQueue())
88 mOwnerThread
, __func__
,
89 [self
= RefPtr
{this}](RefPtr
<MediaDataEncoder
>&& aEncoder
) {
90 self
->mCreateRequest
.Complete();
92 // If EncoderAgent has been shut down, shut the created encoder down
94 if (!self
->mShutdownWhileCreationPromise
.IsEmpty()) {
95 MOZ_ASSERT(self
->mState
== State::ShuttingDown
);
96 MOZ_ASSERT(self
->mConfigurePromise
.IsEmpty(),
97 "configuration should have been rejected");
100 "EncoderAgent #%zu (%p) has been shut down. We need to shut "
101 "the newly created encoder down",
102 self
->mId
, self
.get());
103 aEncoder
->Shutdown()->Then(
104 self
->mOwnerThread
, __func__
,
105 [self
](const ShutdownPromise::ResolveOrRejectValue
& aValue
) {
106 MOZ_ASSERT(self
->mState
== State::ShuttingDown
);
109 "EncoderAgent #%zu (%p), newly created encoder "
112 self
->mId
, self
.get(),
113 aValue
.IsResolve() ? "resolved" : "rejected");
115 self
->SetState(State::Unconfigured
);
117 self
->mShutdownWhileCreationPromise
.ResolveOrReject(
123 self
->mEncoder
= aEncoder
.forget();
124 LOG("EncoderAgent #%zu (%p) has created a encoder, now initialize "
126 self
->mId
, self
.get());
127 self
->mEncoder
->Init()
129 self
->mOwnerThread
, __func__
,
131 self
->mInitRequest
.Complete();
132 LOG("EncoderAgent #%zu (%p) has initialized the encoder",
133 self
->mId
, self
.get());
134 self
->SetState(State::Configured
);
135 self
->mConfigurePromise
.Resolve(true, __func__
);
137 [self
](const MediaResult
& aError
) {
138 self
->mInitRequest
.Complete();
140 "EncoderAgent #%zu (%p) failed to initialize the "
142 self
->mId
, self
.get());
143 self
->SetState(State::Error
);
144 self
->mConfigurePromise
.Reject(aError
, __func__
);
146 ->Track(self
->mInitRequest
);
148 [self
= RefPtr
{this}](const MediaResult
& aError
) {
149 self
->mCreateRequest
.Complete();
150 LOGE("EncoderAgent #%zu (%p) failed to create a encoder", self
->mId
,
153 // If EncoderAgent has been shut down, we need to resolve the
155 if (!self
->mShutdownWhileCreationPromise
.IsEmpty()) {
156 MOZ_ASSERT(self
->mState
== State::ShuttingDown
);
157 MOZ_ASSERT(self
->mConfigurePromise
.IsEmpty(),
158 "configuration should have been rejected");
161 "EncoderAgent #%zu (%p) has been shut down. Resolve the "
162 "shutdown promise right away since encoder creation failed",
163 self
->mId
, self
.get());
165 self
->SetState(State::Unconfigured
);
166 self
->mShutdownWhileCreationPromise
.Resolve(true, __func__
);
170 self
->SetState(State::Error
);
171 self
->mConfigurePromise
.Reject(aError
, __func__
);
173 ->Track(mCreateRequest
);
178 RefPtr
<EncoderAgent::ReconfigurationPromise
> EncoderAgent::Reconfigure(
179 const RefPtr
<const EncoderConfigurationChangeList
>& aConfigChanges
) {
180 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
181 MOZ_ASSERT(mState
== State::Configured
|| mState
== State::Error
);
182 MOZ_ASSERT(mReconfigurationPromise
.IsEmpty());
184 if (mState
== State::Error
) {
185 LOGE("EncoderAgent #%zu (%p) tried to reconfigure in error state", mId
,
187 return ReconfigurationPromise::CreateAndReject(
188 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
189 "Cannot reconfigure in error state"),
193 MOZ_ASSERT(mEncoder
);
194 SetState(State::Configuring
);
196 LOG("EncoderAgent #%zu (%p) is reconfiguring its encoder (%s)", mId
, this,
197 NS_ConvertUTF16toUTF8(aConfigChanges
->ToString().get()).get());
199 RefPtr
<ReconfigurationPromise
> p
= mReconfigurationPromise
.Ensure(__func__
);
201 mEncoder
->Reconfigure(aConfigChanges
)
203 mOwnerThread
, __func__
,
204 [self
= RefPtr
{this}](bool) {
205 self
->mReconfigurationRequest
.Complete();
206 LOGE("EncoderAgent #%zu (%p) reconfigure success", self
->mId
,
208 self
->mReconfigurationPromise
.Resolve(true, __func__
);
210 [self
= RefPtr
{this}](const MediaResult
& aError
) {
211 self
->mReconfigurationRequest
.Complete();
212 LOGE("EncoderAgent #%zu (%p) reconfigure failure", self
->mId
,
214 // Not a a fatal error per se, the owner will deal with it.
215 self
->mReconfigurationPromise
.Reject(aError
, __func__
);
217 ->Track(mReconfigurationRequest
);
222 RefPtr
<ShutdownPromise
> EncoderAgent::Shutdown() {
223 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
226 MediaResult(NS_ERROR_DOM_MEDIA_CANCELED
, "Canceled by encoder shutdown");
228 // If the encoder creation has not been completed yet, wait until the encoder
229 // being created has been shut down.
230 if (mCreateRequest
.Exists()) {
231 MOZ_ASSERT(!mInitRequest
.Exists());
232 MOZ_ASSERT(!mConfigurePromise
.IsEmpty());
233 MOZ_ASSERT(!mEncoder
);
234 MOZ_ASSERT(mState
== State::Configuring
);
235 MOZ_ASSERT(mShutdownWhileCreationPromise
.IsEmpty());
238 "EncoderAgent #%zu (%p) shutdown while the encoder creation for "
239 "configuration is in flight. Reject the configuration now and defer "
240 "the shutdown until the created encoder has been shut down",
243 // Reject the configuration in flight.
244 mConfigurePromise
.Reject(r
, __func__
);
246 // Get the promise that will be resolved when the encoder being created has
248 SetState(State::ShuttingDown
);
249 return mShutdownWhileCreationPromise
.Ensure(__func__
);
252 // If encoder creation has been completed, we must have the encoder now.
253 MOZ_ASSERT(mEncoder
);
255 // Cancel pending initialization for configuration in flight if any.
256 mInitRequest
.DisconnectIfExists();
257 mConfigurePromise
.RejectIfExists(r
, __func__
);
259 mReconfigurationRequest
.DisconnectIfExists();
260 mReconfigurationPromise
.RejectIfExists(r
, __func__
);
262 // Cancel encoder in flight if any.
263 mEncodeRequest
.DisconnectIfExists();
264 mEncodePromise
.RejectIfExists(r
, __func__
);
266 // Cancel flush-out in flight if any.
267 mDrainRequest
.DisconnectIfExists();
268 mEncodeRequest
.DisconnectIfExists();
270 mDrainRequest
.DisconnectIfExists();
271 mDrainPromise
.RejectIfExists(r
, __func__
);
273 SetState(State::Unconfigured
);
275 RefPtr
<MediaDataEncoder
> encoder
= std::move(mEncoder
);
276 return encoder
->Shutdown();
279 RefPtr
<EncoderAgent::EncodePromise
> EncoderAgent::Encode(MediaData
* aInput
) {
280 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
282 MOZ_ASSERT(mState
== State::Configured
|| mState
== State::Error
);
283 MOZ_ASSERT(mEncodePromise
.IsEmpty());
284 MOZ_ASSERT(!mEncodeRequest
.Exists());
286 if (mState
== State::Error
) {
287 LOGE("EncoderAgent #%zu (%p) tried to encoder in error state", mId
, this);
288 return EncodePromise::CreateAndReject(
289 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
290 "Cannot encoder in error state"),
294 MOZ_ASSERT(mState
== State::Configured
);
295 MOZ_ASSERT(mEncoder
);
296 SetState(State::Encoding
);
298 RefPtr
<EncodePromise
> p
= mEncodePromise
.Ensure(__func__
);
300 mEncoder
->Encode(aInput
)
302 mOwnerThread
, __func__
,
303 [self
= RefPtr
{this}](MediaDataEncoder::EncodedData
&& aData
) {
304 self
->mEncodeRequest
.Complete();
305 LOGV("EncoderAgent #%zu (%p) encode successful", self
->mId
,
307 self
->SetState(State::Configured
);
308 self
->mEncodePromise
.Resolve(std::move(aData
), __func__
);
310 [self
= RefPtr
{this}](const MediaResult
& aError
) {
311 self
->mEncodeRequest
.Complete();
312 LOGV("EncoderAgent #%zu (%p) failed to encode", self
->mId
,
314 self
->SetState(State::Error
);
315 self
->mEncodePromise
.Reject(aError
, __func__
);
317 ->Track(mEncodeRequest
);
322 RefPtr
<EncoderAgent::EncodePromise
> EncoderAgent::Drain() {
323 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
324 // This can be called when reconfiguring the encoder.
325 MOZ_ASSERT(mState
== State::Configured
|| mState
== State::Configuring
);
326 MOZ_ASSERT(mDrainPromise
.IsEmpty());
327 MOZ_ASSERT(mEncoder
);
329 SetState(State::Flushing
);
331 RefPtr
<EncodePromise
> p
= mDrainPromise
.Ensure(__func__
);
336 void EncoderAgent::DryUntilDrain() {
337 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
338 MOZ_ASSERT(mState
== State::Flushing
);
339 MOZ_ASSERT(!mDrainRequest
.Exists());
340 MOZ_ASSERT(mEncoder
);
342 LOG("EncoderAgent #%zu (%p) is draining the encoder", mId
, this);
345 mOwnerThread
, __func__
,
346 [self
= RefPtr
{this}](MediaDataEncoder::EncodedData
&& aData
) {
347 self
->mDrainRequest
.Complete();
349 if (aData
.IsEmpty()) {
350 LOG("EncoderAgent #%zu (%p) is dry now", self
->mId
, self
.get());
351 self
->SetState(State::Configured
);
352 self
->mDrainPromise
.Resolve(std::move(self
->mDrainData
),
357 LOG("EncoderAgent #%zu (%p) drained %zu encoder data. Keep "
360 self
->mId
, self
.get(), aData
.Length());
361 self
->mDrainData
.AppendElements(std::move(aData
));
362 self
->DryUntilDrain();
364 [self
= RefPtr
{this}](const MediaResult
& aError
) {
365 self
->mDrainRequest
.Complete();
367 LOGE("EncoderAgent %p failed to drain encoder", self
.get());
368 self
->mDrainData
.Clear();
369 self
->mDrainPromise
.Reject(aError
, __func__
);
371 ->Track(mDrainRequest
);
374 void EncoderAgent::SetState(State aState
) {
375 MOZ_ASSERT(mOwnerThread
->IsOnCurrentThread());
377 auto validateStateTransition
= [](State aOldState
, State aNewState
) {
379 case State::Unconfigured
:
380 return aNewState
== State::Configuring
;
381 case State::Configuring
:
382 return aNewState
== State::Configured
|| aNewState
== State::Error
||
383 aNewState
== State::Flushing
||
384 aNewState
== State::Unconfigured
||
385 aNewState
== State::ShuttingDown
;
386 case State::Configured
:
387 return aNewState
== State::Unconfigured
||
388 aNewState
== State::Configuring
||
389 aNewState
== State::Encoding
|| aNewState
== State::Flushing
;
390 case State::Encoding
:
391 case State::Flushing
:
392 return aNewState
== State::Configured
|| aNewState
== State::Error
||
393 aNewState
== State::Unconfigured
;
394 case State::ShuttingDown
:
395 return aNewState
== State::Unconfigured
;
397 return aNewState
== State::Unconfigured
;
401 MOZ_ASSERT_UNREACHABLE("Unhandled state transition");
405 auto stateToString
= [](State aState
) -> const char* {
407 case State::Unconfigured
:
408 return "Unconfigured";
409 case State::Configuring
:
410 return "Configuring";
411 case State::Configured
:
413 case State::Encoding
:
415 case State::Flushing
:
417 case State::ShuttingDown
:
418 return "ShuttingDown";
424 MOZ_ASSERT_UNREACHABLE("Unhandled state type");
428 DebugOnly
<bool> isValid
= validateStateTransition(mState
, aState
);
429 LOGV("EncoderAgent #%zu (%p) state change: %s -> %s", mId
, this,
430 stateToString(mState
), stateToString(aState
));
441 } // namespace mozilla