Skip to content

Commit fa73101

Browse files
committed
Bug 1978232 - Enable batch encoding in EncoderAgent and EncoderTemplate r=media-playback-reviewers,padenot,jolin
This patch enables batch encoding in `EncoderAgent` and `EncoderTemplate`. `EncoderAgent` now accepts an array of samples rather than a single sample. With this extended capability, `EncoderTemplate` can now merge multiple `encode` requests issued from WebCodecs' users into a single one containing consecutive samples, then submit to `EncoderAgent` as a batch. A preference is also introduced to control the maximum batch size, making it easier to disable (when it is set to 0 or 1) or experiement with and measure performance under different size values. Differential Revision: https://phabricator.services.mozilla.com/D261676
1 parent 7b82005 commit fa73101

File tree

6 files changed

+114
-37
lines changed

6 files changed

+114
-37
lines changed

dom/media/platforms/EncoderConfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ class EncoderConfig final {
240240
uint32_t mBitrate{};
241241
uint32_t mMinBitrate{};
242242
uint32_t mMaxBitrate{};
243-
Usage mUsage{};
243+
Usage mUsage{Usage::Record};
244244
// Video-only
245245
HardwarePreference mHardwarePreference{HardwarePreference::None};
246246
SampleFormat mFormat{dom::ImageBitmapFormat::YUV420P};

dom/media/webcodecs/EncoderAgent.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,10 @@ RefPtr<ShutdownPromise> EncoderAgent::Shutdown() {
295295
return encoder->Shutdown();
296296
}
297297

298-
RefPtr<EncoderAgent::EncodePromise> EncoderAgent::Encode(MediaData* aInput) {
298+
RefPtr<EncoderAgent::EncodePromise> EncoderAgent::Encode(
299+
nsTArray<RefPtr<MediaData>>&& aInputs) {
299300
MOZ_ASSERT(mOwnerThread->IsOnCurrentThread());
300-
MOZ_ASSERT(aInput);
301+
MOZ_ASSERT(!aInputs.IsEmpty());
301302
MOZ_ASSERT(mState == State::Configured || mState == State::Error);
302303
MOZ_ASSERT(mEncodePromise.IsEmpty());
303304
MOZ_ASSERT(!mEncodeRequest.Exists());
@@ -316,19 +317,20 @@ RefPtr<EncoderAgent::EncodePromise> EncoderAgent::Encode(MediaData* aInput) {
316317

317318
RefPtr<EncodePromise> p = mEncodePromise.Ensure(__func__);
318319

319-
mEncoder->Encode(aInput)
320+
LOGV("EncoderAgent #%zu (%p) is encoding %zu samples", mId, this, aInputs.Length());
321+
mEncoder->Encode(std::move(aInputs))
320322
->Then(
321323
mOwnerThread, __func__,
322324
[self = RefPtr{this}](MediaDataEncoder::EncodedData&& aData) {
323325
self->mEncodeRequest.Complete();
324-
LOGV("EncoderAgent #%zu (%p) encode successful", self->mId,
326+
LOGV("EncoderAgent #%zu (%p) encode a batch successful", self->mId,
325327
self.get());
326328
self->SetState(State::Configured);
327329
self->mEncodePromise.Resolve(std::move(aData), __func__);
328330
},
329331
[self = RefPtr{this}](const MediaResult& aError) {
330332
self->mEncodeRequest.Complete();
331-
LOGV("EncoderAgent #%zu (%p) failed to encode", self->mId,
333+
LOGV("EncoderAgent #%zu (%p) failed to encode a batch", self->mId,
332334
self.get());
333335
self->SetState(State::Error);
334336
self->mEncodePromise.Reject(aError, __func__);

dom/media/webcodecs/EncoderAgent.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class EncoderAgent final {
5555
const RefPtr<const EncoderConfigurationChangeList>& aConfigChange);
5656
RefPtr<ShutdownPromise> Shutdown();
5757
using EncodePromise = MediaDataEncoder::EncodePromise;
58-
RefPtr<EncodePromise> Encode(MediaData* aInput);
58+
RefPtr<EncodePromise> Encode(nsTArray<RefPtr<MediaData>>&& aInputs);
5959
// WebCodecs's flush() flushes out all the pending encoded data in the
6060
// encoder. It's called Drain internally.
6161
RefPtr<EncodePromise> Drain();

dom/media/webcodecs/EncoderTemplate.cpp

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66

77
#include "EncoderTemplate.h"
88

9+
#include <algorithm>
910
#include <type_traits>
1011

1112
#include "EncoderTypes.h"
1213
#include "WebCodecsUtils.h"
1314
#include "mozilla/ScopeExit.h"
15+
#include "mozilla/StaticPrefs_dom.h"
1416
#include "mozilla/Try.h"
1517
#include "mozilla/Unused.h"
1618
#include "mozilla/dom/BindingDeclarations.h"
@@ -73,9 +75,11 @@ EncoderTemplate<EncoderType>::ConfigureMessage::ConfigureMessage(
7375

7476
template <typename EncoderType>
7577
EncoderTemplate<EncoderType>::EncodeMessage::EncodeMessage(
76-
WebCodecsId aConfigureId, RefPtr<InputTypeInternal>&& aData,
78+
WebCodecsId aConfigureId, already_AddRefed<InputTypeInternal> aData,
7779
Maybe<VideoEncoderEncodeOptions>&& aOptions)
78-
: ControlMessage(aConfigureId), mData(aData) {}
80+
: ControlMessage(aConfigureId) {
81+
PushData(std::move(aData), std::move(aOptions));
82+
}
7983

8084
template <typename EncoderType>
8185
EncoderTemplate<EncoderType>::FlushMessage::FlushMessage(
@@ -167,13 +171,10 @@ void EncoderTemplate<EncoderType>::EncodeAudioData(InputType& aInput,
167171
mAsyncDurationTracker.Start(
168172
aInput.Timestamp(),
169173
AutoWebCodecsMarker(EncoderType::Name.get(), ".encode-duration-a"));
170-
mEncodeQueueSize += 1;
171174
// Dummy options here as a shortcut
172-
mControlMessageQueue.push(MakeRefPtr<EncodeMessage>(
175+
PushEncodeRequest(
173176
mLatestConfigureId,
174-
EncoderType::CreateInputInternal(aInput, VideoEncoderEncodeOptions())));
175-
LOGV("%s %p enqueues %s", EncoderType::Name.get(), this,
176-
mControlMessageQueue.back()->ToString().get());
177+
EncoderType::CreateInputInternal(aInput, VideoEncoderEncodeOptions()));
177178
ProcessControlMessageQueue();
178179
}
179180

@@ -199,12 +200,9 @@ void EncoderTemplate<EncoderType>::EncodeVideoFrame(
199200
mAsyncDurationTracker.Start(
200201
aInput.Timestamp(),
201202
AutoWebCodecsMarker(EncoderType::Name.get(), ".encode-duration-v"));
202-
mEncodeQueueSize += 1;
203-
mControlMessageQueue.push(MakeRefPtr<EncodeMessage>(
204-
mLatestConfigureId, EncoderType::CreateInputInternal(aInput, aOptions),
205-
Some(aOptions)));
206-
LOGV("%s %p enqueues %s", EncoderType::Name.get(), this,
207-
mControlMessageQueue.back()->ToString().get());
203+
PushEncodeRequest(mLatestConfigureId,
204+
EncoderType::CreateInputInternal(aInput, aOptions),
205+
Some(aOptions));
208206
ProcessControlMessageQueue();
209207
}
210208

@@ -854,6 +852,7 @@ MessageProcessedResult EncoderTemplate<EncoderType>::ProcessEncodeMessage(
854852
AssertIsOnOwningThread();
855853
MOZ_ASSERT(mState == CodecState::Configured);
856854
MOZ_ASSERT(aMessage->AsEncodeMessage());
855+
MOZ_ASSERT(mEncodeQueueSize > 0);
857856

858857
AUTO_ENCODER_MARKER(marker, ".encode-process");
859858

@@ -867,7 +866,8 @@ MessageProcessedResult EncoderTemplate<EncoderType>::ProcessEncodeMessage(
867866
LOGV("%s %p processing %s", EncoderType::Name.get(), this,
868867
aMessage->ToString().get());
869868

870-
mEncodeQueueSize -= 1;
869+
MOZ_ASSERT(AssertedCast<uint32_t>(aMessage->BatchSize()) <= mEncodeQueueSize);
870+
mEncodeQueueSize -= AssertedCast<uint32_t>(aMessage->BatchSize());
871871
ScheduleDequeueEvent();
872872

873873
// Treat it like decode error if no EncoderAgent is available or the encoded
@@ -888,17 +888,15 @@ MessageProcessedResult EncoderTemplate<EncoderType>::ProcessEncodeMessage(
888888
}
889889

890890
MOZ_ASSERT(mActiveConfig);
891-
RefPtr<InputTypeInternal> data = aMessage->mData;
892-
if (!data) {
893-
LOGE("%s %p, data for %s is empty or invalid", EncoderType::Name.get(),
894-
this, aMessage->ToString().get());
891+
if (!aMessage->IsValid()) {
892+
LOGE("%s %p, %s has empty data", EncoderType::Name.get(), this,
893+
aMessage->ToString().get());
895894
return closeOnError();
896895
}
897896

898-
mAgent->Encode(data.get())
897+
mAgent->Encode(aMessage->TakeData())
899898
->Then(GetCurrentSerialEventTarget(), __func__,
900-
[self = RefPtr{this}, id = mAgent->mId, aMessage,
901-
m = std::move(marker)](
899+
[self = RefPtr{this}, id = mAgent->mId, m = std::move(marker)](
902900
EncoderAgent::EncodePromise::ResolveOrRejectValue&&
903901
aResult) mutable {
904902
MOZ_ASSERT(self->mProcessingMessage);
@@ -908,9 +906,11 @@ MessageProcessedResult EncoderTemplate<EncoderType>::ProcessEncodeMessage(
908906
MOZ_ASSERT(id == self->mAgent->mId);
909907
MOZ_ASSERT(self->mActiveConfig);
910908

911-
nsCString msgStr = aMessage->ToString();
909+
RefPtr<EncodeMessage> msg =
910+
self->mProcessingMessage->AsEncodeMessage();
911+
nsCString msgStr = msg->ToString();
912912

913-
aMessage->Complete();
913+
msg->Complete();
914914
self->mProcessingMessage = nullptr;
915915

916916
if (aResult.IsReject()) {
@@ -1208,6 +1208,40 @@ void EncoderTemplate<EncoderType>::DestroyEncoderAgentIfAny() {
12081208
});
12091209
}
12101210

1211+
template <typename EncoderType>
1212+
void EncoderTemplate<EncoderType>::PushEncodeRequest(
1213+
WebCodecsId aConfigureId, RefPtr<InputTypeInternal>&& aData,
1214+
Maybe<VideoEncoderEncodeOptions>&& aOptions) {
1215+
AssertIsOnOwningThread();
1216+
MOZ_ASSERT(mState == CodecState::Configured);
1217+
1218+
const size_t batchSize = std::max<size_t>(
1219+
StaticPrefs::dom_media_webcodecs_batch_encoding_size(), 1);
1220+
1221+
RefPtr<EncodeMessage> msg;
1222+
if (!mControlMessageQueue.empty()) {
1223+
msg = mControlMessageQueue.back()->AsEncodeMessage();
1224+
if (msg &&
1225+
(msg->mConfigureId != aConfigureId || msg->BatchSize() >= batchSize)) {
1226+
msg = nullptr;
1227+
}
1228+
}
1229+
1230+
const bool isNewMessage = !msg;
1231+
if (isNewMessage) {
1232+
msg = MakeRefPtr<EncodeMessage>(aConfigureId, aData.forget(),
1233+
std::move(aOptions));
1234+
mControlMessageQueue.push(msg);
1235+
} else {
1236+
msg->PushData(aData.forget(), std::move(aOptions));
1237+
}
1238+
1239+
mEncodeQueueSize += 1;
1240+
LOGV("%s %p %s %s, encode queue size: %u", EncoderType::Name.get(), this,
1241+
isNewMessage ? "queued a new" : "appended data to",
1242+
msg->ToString().get(), mEncodeQueueSize);
1243+
}
1244+
12111245
template class EncoderTemplate<VideoEncoderTraits>;
12121246
template class EncoderTemplate<AudioEncoderTraits>;
12131247

dom/media/webcodecs/EncoderTemplate.h

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#ifndef mozilla_dom_EncoderTemplate_h
88
#define mozilla_dom_EncoderTemplate_h
99

10+
#include <limits>
1011
#include <queue>
1112

1213
#include "EncoderAgent.h"
@@ -92,25 +93,50 @@ class EncoderTemplate : public DOMEventTargetHelper {
9293
const RefPtr<ConfigTypeInternal> mConfig;
9394
};
9495

96+
// This stores batch encoding requests, created by grouping multiple encode()
97+
// calls. See PushEncodeRequest() for further details.
9598
class EncodeMessage final
9699
: public ControlMessage,
97100
public MessageRequestHolder<EncoderAgent::EncodePromise> {
98101
public:
99-
EncodeMessage(WebCodecsId aConfigureId, RefPtr<InputTypeInternal>&& aData,
102+
EncodeMessage(WebCodecsId aConfigureId,
103+
already_AddRefed<InputTypeInternal> aData,
100104
Maybe<VideoEncoderEncodeOptions>&& aOptions = Nothing());
101105
nsCString ToString() const override {
102106
nsCString rv;
103-
bool isKeyFrame = mOptions.isSome() && mOptions.ref().mKeyFrame;
104-
rv.AppendPrintf("EncodeMessage(#%zu, #%zu): %s%s", this->mConfigureId,
105-
this->mMessageId, mData->ToString().get(),
106-
isKeyFrame ? " (kf)" : "");
107+
rv.AppendPrintf(
108+
"EncodeMessage(#%zu, #%zu): %zu frames (%zu kfs, %zu held)",
109+
this->mConfigureId, this->mMessageId, mFrames, mKeyFrames,
110+
mData.Length());
107111
return rv;
108112
}
113+
bool IsValid() const { return !mHasEmptyData && !mData.IsEmpty(); }
114+
size_t BatchSize() const { return mData.Length(); }
115+
void PushData(already_AddRefed<InputTypeInternal> aData,
116+
Maybe<VideoEncoderEncodeOptions>&& aOptions = Nothing()) {
117+
mFrames += 1;
118+
RefPtr<InputTypeInternal> data = aData;
119+
if (!data) {
120+
mHasEmptyData = true;
121+
}
122+
MOZ_ASSERT_IF(aOptions.isSome() && aOptions->mKeyFrame, data->mKeyframe);
123+
mKeyFrames += data->mKeyframe ? 1 : 0;
124+
mData.AppendElement(data.forget());
125+
}
126+
nsTArray<RefPtr<MediaData>>&& TakeData() {
127+
return std::move(mData);
128+
}
109129
virtual void Cancel() override { Disconnect(); }
110130
virtual bool IsProcessing() override { return Exists(); };
111131
virtual RefPtr<EncodeMessage> AsEncodeMessage() override { return this; }
112-
RefPtr<InputTypeInternal> mData;
113-
Maybe<VideoEncoderEncodeOptions> mOptions;
132+
133+
private:
134+
// Stores data in MediaData rather than InputTypeInternal, as
135+
// MediaDataEncoder::EncodeBatch expects an array of MediaData.
136+
nsTArray<RefPtr<MediaData>> mData;
137+
size_t mFrames = 0;
138+
size_t mKeyFrames = 0;
139+
bool mHasEmptyData = false;
114140
};
115141

116142
class FlushMessage final
@@ -221,6 +247,10 @@ class EncoderTemplate : public DOMEventTargetHelper {
221247
bool CreateEncoderAgent(WebCodecsId aId, RefPtr<ConfigTypeInternal> aConfig);
222248
void DestroyEncoderAgentIfAny();
223249

250+
void PushEncodeRequest(
251+
WebCodecsId aConfigureId, RefPtr<InputTypeInternal>&& aData,
252+
Maybe<VideoEncoderEncodeOptions>&& aOptions = Nothing());
253+
224254
// Constant in practice, only set in ctor.
225255
RefPtr<WebCodecsErrorCallback> mErrorCallback;
226256
RefPtr<OutputCallbackType> mOutputCallback;

modules/libpref/init/StaticPrefList.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3586,6 +3586,17 @@
35863586
value: @IS_NIGHTLY_BUILD@
35873587
mirror: always
35883588

3589+
# WebCodecs API - Batch encoding size
3590+
- name: dom.media.webcodecs.batch-encoding-size
3591+
type: RelaxedAtomicUint32
3592+
#if defined(XP_WIN)
3593+
value: 4294967295 # max value of uint32_t
3594+
#else
3595+
# Bug 1984936: Batch encoding is not optimized on non-Windows platforms yet.
3596+
value: 1
3597+
#endif
3598+
mirror: always
3599+
35893600
# Number of seconds of very quiet or silent audio before considering the audio
35903601
# inaudible.
35913602
- name: dom.media.silence_duration_for_audibility

0 commit comments

Comments
 (0)