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/. */
6 
7 #include "MediaBufferDecoder.h"
8 #include "mozilla/dom/AudioContextBinding.h"
9 #include "mozilla/dom/BaseAudioContextBinding.h"
10 #include "mozilla/dom/DOMException.h"
11 #include "mozilla/dom/ScriptSettings.h"
12 #include "mozilla/AbstractThread.h"
13 #include <speex/speex_resampler.h>
14 #include "nsXPCOMCIDInternal.h"
15 #include "nsComponentManagerUtils.h"
16 #include "MediaFormatReader.h"
17 #include "MediaQueue.h"
18 #include "BufferMediaResource.h"
19 #include "DecoderTraits.h"
20 #include "AudioContext.h"
21 #include "AudioBuffer.h"
22 #include "MediaContainerType.h"
23 #include "nsContentUtils.h"
24 #include "nsIScriptObjectPrincipal.h"
25 #include "nsIScriptError.h"
26 #include "nsMimeTypes.h"
27 #include "VideoUtils.h"
28 #include "WebAudioUtils.h"
29 #include "mozilla/dom/Promise.h"
30 #include "mozilla/Telemetry.h"
31 #include "nsPrintfCString.h"
32 #include "AudioNodeEngine.h"
33 
34 namespace mozilla {
35 
36 extern LazyLogModule gMediaDecoderLog;
37 
38 using namespace dom;
39 
40 class ReportResultTask final : public Runnable {
41  public:
ReportResultTask(WebAudioDecodeJob & aDecodeJob,WebAudioDecodeJob::ResultFn aFunction,WebAudioDecodeJob::ErrorCode aErrorCode)42   ReportResultTask(WebAudioDecodeJob& aDecodeJob,
43                    WebAudioDecodeJob::ResultFn aFunction,
44                    WebAudioDecodeJob::ErrorCode aErrorCode)
45       : Runnable("ReportResultTask"),
46         mDecodeJob(aDecodeJob),
47         mFunction(aFunction),
48         mErrorCode(aErrorCode) {
49     MOZ_ASSERT(aFunction);
50   }
51 
Run()52   NS_IMETHOD Run() override {
53     MOZ_ASSERT(NS_IsMainThread());
54 
55     (mDecodeJob.*mFunction)(mErrorCode);
56 
57     return NS_OK;
58   }
59 
60  private:
61   // Note that the mDecodeJob member will probably die when mFunction is run.
62   // Therefore, it is not safe to do anything fancy with it in this class.
63   // Really, this class is only used because nsRunnableMethod doesn't support
64   // methods accepting arguments.
65   WebAudioDecodeJob& mDecodeJob;
66   WebAudioDecodeJob::ResultFn mFunction;
67   WebAudioDecodeJob::ErrorCode mErrorCode;
68 };
69 
70 enum class PhaseEnum : int { Decode, AllocateBuffer, Done };
71 
72 class MediaDecodeTask final : public Runnable {
73  public:
MediaDecodeTask(const MediaContainerType & aContainerType,uint8_t * aBuffer,uint32_t aLength,WebAudioDecodeJob & aDecodeJob)74   MediaDecodeTask(const MediaContainerType& aContainerType, uint8_t* aBuffer,
75                   uint32_t aLength, WebAudioDecodeJob& aDecodeJob)
76       : Runnable("MediaDecodeTask"),
77         mContainerType(aContainerType),
78         mBuffer(aBuffer),
79         mLength(aLength),
80         mDecodeJob(aDecodeJob),
81         mPhase(PhaseEnum::Decode),
82         mFirstFrameDecoded(false) {
83     MOZ_ASSERT(aBuffer);
84     MOZ_ASSERT(NS_IsMainThread());
85   }
86 
87   NS_IMETHOD Run() override;
88   bool CreateReader();
Reader()89   MediaFormatReader* Reader() {
90     MOZ_ASSERT(mDecoderReader);
91     return mDecoderReader;
92   }
93 
94  private:
ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode)95   void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
96     if (NS_IsMainThread()) {
97       Cleanup();
98       mDecodeJob.OnFailure(aErrorCode);
99     } else {
100       // Take extra care to cleanup on the main thread
101       mMainThread->Dispatch(NewRunnableMethod("MediaDecodeTask::Cleanup", this,
102                                               &MediaDecodeTask::Cleanup));
103 
104       nsCOMPtr<nsIRunnable> event = new ReportResultTask(
105           mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
106       mMainThread->Dispatch(event.forget());
107     }
108   }
109 
110   void Decode();
111   void OnMetadataRead(MetadataHolder&& aMetadata);
112   void OnMetadataNotRead(const MediaResult& aError);
113   void RequestSample();
114   void SampleDecoded(RefPtr<AudioData> aData);
115   void SampleNotDecoded(const MediaResult& aError);
116   void FinishDecode();
117   void AllocateBuffer();
118   void CallbackTheResult();
119 
Cleanup()120   void Cleanup() {
121     MOZ_ASSERT(NS_IsMainThread());
122     mDecoderReader = nullptr;
123     JS_free(nullptr, mBuffer);
124   }
125 
126  private:
127   MediaContainerType mContainerType;
128   uint8_t* mBuffer;
129   uint32_t mLength;
130   WebAudioDecodeJob& mDecodeJob;
131   PhaseEnum mPhase;
132   RefPtr<MediaFormatReader> mDecoderReader;
133   MediaInfo mMediaInfo;
134   MediaQueue<AudioData> mAudioQueue;
135   RefPtr<AbstractThread> mMainThread;
136   bool mFirstFrameDecoded;
137 };
138 
139 NS_IMETHODIMP
Run()140 MediaDecodeTask::Run() {
141   MOZ_ASSERT(mDecoderReader);
142   switch (mPhase) {
143     case PhaseEnum::Decode:
144       Decode();
145       break;
146     case PhaseEnum::AllocateBuffer:
147       AllocateBuffer();
148       break;
149     case PhaseEnum::Done:
150       break;
151   }
152 
153   return NS_OK;
154 }
155 
CreateReader()156 bool MediaDecodeTask::CreateReader() {
157   MOZ_ASSERT(NS_IsMainThread());
158 
159   RefPtr<BufferMediaResource> resource =
160       new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength);
161 
162   mMainThread = mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(
163       TaskCategory::Other);
164 
165   // If you change this list to add support for new decoders, please consider
166   // updating HTMLMediaElement::CreateDecoder as well.
167 
168   MediaFormatReaderInit init;
169   init.mResource = resource;
170   mDecoderReader = DecoderTraits::CreateReader(mContainerType, init);
171 
172   if (!mDecoderReader) {
173     return false;
174   }
175 
176   nsresult rv = mDecoderReader->Init();
177   if (NS_FAILED(rv)) {
178     return false;
179   }
180 
181   return true;
182 }
183 
184 class AutoResampler final {
185  public:
AutoResampler()186   AutoResampler() : mResampler(nullptr) {}
~AutoResampler()187   ~AutoResampler() {
188     if (mResampler) {
189       speex_resampler_destroy(mResampler);
190     }
191   }
operator SpeexResamplerState*() const192   operator SpeexResamplerState*() const {
193     MOZ_ASSERT(mResampler);
194     return mResampler;
195   }
operator =(SpeexResamplerState * aResampler)196   void operator=(SpeexResamplerState* aResampler) { mResampler = aResampler; }
197 
198  private:
199   SpeexResamplerState* mResampler;
200 };
201 
Decode()202 void MediaDecodeTask::Decode() {
203   MOZ_ASSERT(!NS_IsMainThread());
204 
205   mDecoderReader->AsyncReadMetadata()->Then(
206       mDecoderReader->OwnerThread(), __func__, this,
207       &MediaDecodeTask::OnMetadataRead, &MediaDecodeTask::OnMetadataNotRead);
208 }
209 
OnMetadataRead(MetadataHolder && aMetadata)210 void MediaDecodeTask::OnMetadataRead(MetadataHolder&& aMetadata) {
211   mMediaInfo = *aMetadata.mInfo;
212   if (!mMediaInfo.HasAudio()) {
213     mDecoderReader->Shutdown();
214     ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
215     return;
216   }
217 
218   nsCString codec;
219   if (!mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.IsEmpty()) {
220     codec = nsPrintfCString(
221         "webaudio; %s", mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.get());
222   } else {
223     codec = nsPrintfCString("webaudio;resource; %s",
224                             mContainerType.Type().AsString().Data());
225   }
226 
227   nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
228       "MediaDecodeTask::OnMetadataRead", [codec]() -> void {
229         MOZ_ASSERT(!codec.IsEmpty());
230         MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,
231                 ("Telemetry (WebAudio) MEDIA_CODEC_USED= '%s'", codec.get()));
232         Telemetry::Accumulate(Telemetry::HistogramID::MEDIA_CODEC_USED, codec);
233       });
234   SystemGroup::Dispatch(TaskCategory::Other, task.forget());
235 
236   RequestSample();
237 }
238 
OnMetadataNotRead(const MediaResult & aReason)239 void MediaDecodeTask::OnMetadataNotRead(const MediaResult& aReason) {
240   mDecoderReader->Shutdown();
241   ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
242 }
243 
RequestSample()244 void MediaDecodeTask::RequestSample() {
245   mDecoderReader->RequestAudioData()->Then(
246       mDecoderReader->OwnerThread(), __func__, this,
247       &MediaDecodeTask::SampleDecoded, &MediaDecodeTask::SampleNotDecoded);
248 }
249 
SampleDecoded(RefPtr<AudioData> aData)250 void MediaDecodeTask::SampleDecoded(RefPtr<AudioData> aData) {
251   MOZ_ASSERT(!NS_IsMainThread());
252   mAudioQueue.Push(aData);
253   if (!mFirstFrameDecoded) {
254     mDecoderReader->ReadUpdatedMetadata(&mMediaInfo);
255     mFirstFrameDecoded = true;
256   }
257   RequestSample();
258 }
259 
SampleNotDecoded(const MediaResult & aError)260 void MediaDecodeTask::SampleNotDecoded(const MediaResult& aError) {
261   MOZ_ASSERT(!NS_IsMainThread());
262   if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
263     FinishDecode();
264   } else {
265     mDecoderReader->Shutdown();
266     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
267   }
268 }
269 
FinishDecode()270 void MediaDecodeTask::FinishDecode() {
271   mDecoderReader->Shutdown();
272 
273   uint32_t frameCount = mAudioQueue.FrameCount();
274   uint32_t channelCount = mMediaInfo.mAudio.mChannels;
275   uint32_t sampleRate = mMediaInfo.mAudio.mRate;
276 
277   if (!frameCount || !channelCount || !sampleRate) {
278     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
279     return;
280   }
281 
282   const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
283   AutoResampler resampler;
284 
285   uint32_t resampledFrames = frameCount;
286   if (sampleRate != destSampleRate) {
287     resampledFrames = static_cast<uint32_t>(
288         static_cast<uint64_t>(destSampleRate) *
289         static_cast<uint64_t>(frameCount) / static_cast<uint64_t>(sampleRate));
290 
291     resampler = speex_resampler_init(channelCount, sampleRate, destSampleRate,
292                                      SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr);
293     speex_resampler_skip_zeros(resampler);
294     resampledFrames += speex_resampler_get_output_latency(resampler);
295   }
296 
297   // Allocate contiguous channel buffers.  Note that if we end up resampling,
298   // we may write fewer bytes than mResampledFrames to the output buffer, in
299   // which case writeIndex will tell us how many valid samples we have.
300   mDecodeJob.mBuffer.mChannelData.SetLength(channelCount);
301 #if AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32
302   // This buffer has separate channel arrays that could be transferred to
303   // JS_NewArrayBufferWithContents(), but AudioBuffer::RestoreJSChannelData()
304   // does not yet take advantage of this.
305   RefPtr<ThreadSharedFloatArrayBufferList> buffer =
306       ThreadSharedFloatArrayBufferList::Create(channelCount, resampledFrames,
307                                                fallible);
308   if (!buffer) {
309     ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
310     return;
311   }
312   for (uint32_t i = 0; i < channelCount; ++i) {
313     mDecodeJob.mBuffer.mChannelData[i] = buffer->GetData(i);
314   }
315 #else
316   RefPtr<SharedBuffer> buffer = SharedBuffer::Create(
317       sizeof(AudioDataValue) * resampledFrames * channelCount);
318   if (!buffer) {
319     ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
320     return;
321   }
322   auto data = static_cast<AudioDataValue*>(floatBuffer->Data());
323   for (uint32_t i = 0; i < channelCount; ++i) {
324     mDecodeJob.mBuffer.mChannelData[i] = data;
325     data += resampledFrames;
326   }
327 #endif
328   mDecodeJob.mBuffer.mBuffer = buffer.forget();
329   mDecodeJob.mBuffer.mVolume = 1.0f;
330   mDecodeJob.mBuffer.mBufferFormat = AUDIO_OUTPUT_FORMAT;
331 
332   uint32_t writeIndex = 0;
333   RefPtr<AudioData> audioData;
334   while ((audioData = mAudioQueue.PopFront())) {
335     audioData->EnsureAudioBuffer();  // could lead to a copy :(
336     const AudioDataValue* bufferData =
337         static_cast<AudioDataValue*>(audioData->mAudioBuffer->Data());
338 
339     if (sampleRate != destSampleRate) {
340       const uint32_t maxOutSamples = resampledFrames - writeIndex;
341 
342       for (uint32_t i = 0; i < audioData->mChannels; ++i) {
343         uint32_t inSamples = audioData->mFrames;
344         uint32_t outSamples = maxOutSamples;
345         AudioDataValue* outData =
346             mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
347             writeIndex;
348 
349         WebAudioUtils::SpeexResamplerProcess(
350             resampler, i, &bufferData[i * audioData->mFrames], &inSamples,
351             outData, &outSamples);
352 
353         if (i == audioData->mChannels - 1) {
354           writeIndex += outSamples;
355           MOZ_ASSERT(writeIndex <= resampledFrames);
356           MOZ_ASSERT(inSamples == audioData->mFrames);
357         }
358       }
359     } else {
360       for (uint32_t i = 0; i < audioData->mChannels; ++i) {
361         AudioDataValue* outData =
362             mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
363             writeIndex;
364         PodCopy(outData, &bufferData[i * audioData->mFrames],
365                 audioData->mFrames);
366 
367         if (i == audioData->mChannels - 1) {
368           writeIndex += audioData->mFrames;
369         }
370       }
371     }
372   }
373 
374   if (sampleRate != destSampleRate) {
375     uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
376     const uint32_t maxOutSamples = resampledFrames - writeIndex;
377     for (uint32_t i = 0; i < channelCount; ++i) {
378       uint32_t inSamples = inputLatency;
379       uint32_t outSamples = maxOutSamples;
380       AudioDataValue* outData =
381           mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
382           writeIndex;
383 
384       WebAudioUtils::SpeexResamplerProcess(resampler, i,
385                                            (AudioDataValue*)nullptr, &inSamples,
386                                            outData, &outSamples);
387 
388       if (i == channelCount - 1) {
389         writeIndex += outSamples;
390         MOZ_ASSERT(writeIndex <= resampledFrames);
391         MOZ_ASSERT(inSamples == inputLatency);
392       }
393     }
394   }
395 
396   mDecodeJob.mBuffer.mDuration = writeIndex;
397   mPhase = PhaseEnum::AllocateBuffer;
398   mMainThread->Dispatch(do_AddRef(this));
399 }
400 
AllocateBuffer()401 void MediaDecodeTask::AllocateBuffer() {
402   MOZ_ASSERT(NS_IsMainThread());
403 
404   if (!mDecodeJob.AllocateBuffer()) {
405     ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
406     return;
407   }
408 
409   mPhase = PhaseEnum::Done;
410   CallbackTheResult();
411 }
412 
CallbackTheResult()413 void MediaDecodeTask::CallbackTheResult() {
414   MOZ_ASSERT(NS_IsMainThread());
415 
416   Cleanup();
417 
418   // Now, we're ready to call the script back with the resulting buffer
419   mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
420 }
421 
AllocateBuffer()422 bool WebAudioDecodeJob::AllocateBuffer() {
423   MOZ_ASSERT(!mOutput);
424   MOZ_ASSERT(NS_IsMainThread());
425 
426   // Now create the AudioBuffer
427   mOutput = AudioBuffer::Create(mContext->GetOwner(), mContext->SampleRate(),
428                                 Move(mBuffer));
429   return mOutput != nullptr;
430 }
431 
AsyncDecodeWebAudio(const char * aContentType,uint8_t * aBuffer,uint32_t aLength,WebAudioDecodeJob & aDecodeJob)432 void AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
433                          uint32_t aLength, WebAudioDecodeJob& aDecodeJob) {
434   Maybe<MediaContainerType> containerType =
435       MakeMediaContainerType(aContentType);
436   // Do not attempt to decode the media if we were not successful at sniffing
437   // the container type.
438   if (!*aContentType || strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0 ||
439       !containerType) {
440     nsCOMPtr<nsIRunnable> event =
441         new ReportResultTask(aDecodeJob, &WebAudioDecodeJob::OnFailure,
442                              WebAudioDecodeJob::UnknownContent);
443     JS_free(nullptr, aBuffer);
444     aDecodeJob.mContext->Dispatch(event.forget());
445     return;
446   }
447 
448   RefPtr<MediaDecodeTask> task =
449       new MediaDecodeTask(*containerType, aBuffer, aLength, aDecodeJob);
450   if (!task->CreateReader()) {
451     nsCOMPtr<nsIRunnable> event =
452         new ReportResultTask(aDecodeJob, &WebAudioDecodeJob::OnFailure,
453                              WebAudioDecodeJob::UnknownError);
454     aDecodeJob.mContext->Dispatch(event.forget());
455   } else {
456     // If we did this without a temporary:
457     //   task->Reader()->OwnerThread()->Dispatch(task.forget())
458     // we might evaluate the task.forget() before calling Reader(). Enforce
459     // a non-crashy order-of-operations.
460     TaskQueue* taskQueue = task->Reader()->OwnerThread();
461     nsresult rv = taskQueue->Dispatch(task.forget());
462     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
463     Unused << rv;
464   }
465 }
466 
WebAudioDecodeJob(AudioContext * aContext,Promise * aPromise,DecodeSuccessCallback * aSuccessCallback,DecodeErrorCallback * aFailureCallback)467 WebAudioDecodeJob::WebAudioDecodeJob(AudioContext* aContext, Promise* aPromise,
468                                      DecodeSuccessCallback* aSuccessCallback,
469                                      DecodeErrorCallback* aFailureCallback)
470     : mContext(aContext),
471       mPromise(aPromise),
472       mSuccessCallback(aSuccessCallback),
473       mFailureCallback(aFailureCallback) {
474   MOZ_ASSERT(aContext);
475   MOZ_ASSERT(NS_IsMainThread());
476   MOZ_COUNT_CTOR(WebAudioDecodeJob);
477 }
478 
~WebAudioDecodeJob()479 WebAudioDecodeJob::~WebAudioDecodeJob() {
480   MOZ_ASSERT(NS_IsMainThread());
481   MOZ_COUNT_DTOR(WebAudioDecodeJob);
482 }
483 
OnSuccess(ErrorCode aErrorCode)484 void WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode) {
485   MOZ_ASSERT(NS_IsMainThread());
486   MOZ_ASSERT(aErrorCode == NoError);
487 
488   if (mSuccessCallback) {
489     ErrorResult rv;
490     mSuccessCallback->Call(*mOutput, rv);
491     // Ignore errors in calling the callback, since there is not much that we
492     // can do about it here.
493     rv.SuppressException();
494   }
495   mPromise->MaybeResolve(mOutput);
496 
497   mContext->RemoveFromDecodeQueue(this);
498 }
499 
OnFailure(ErrorCode aErrorCode)500 void WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode) {
501   MOZ_ASSERT(NS_IsMainThread());
502 
503   const char* errorMessage;
504   switch (aErrorCode) {
505     case NoError:
506       MOZ_FALLTHROUGH_ASSERT("Who passed NoError to OnFailure?");
507       // Fall through to get some sort of a sane error message if this actually
508       // happens at runtime.
509     case UnknownError:
510       errorMessage = "MediaDecodeAudioDataUnknownError";
511       break;
512     case UnknownContent:
513       errorMessage = "MediaDecodeAudioDataUnknownContentType";
514       break;
515     case InvalidContent:
516       errorMessage = "MediaDecodeAudioDataInvalidContent";
517       break;
518     case NoAudio:
519       errorMessage = "MediaDecodeAudioDataNoAudio";
520       break;
521   }
522 
523   nsIDocument* doc = nullptr;
524   if (nsPIDOMWindowInner* pWindow = mContext->GetParentObject()) {
525     doc = pWindow->GetExtantDoc();
526   }
527   nsContentUtils::ReportToConsole(
528       nsIScriptError::errorFlag, NS_LITERAL_CSTRING("Media"), doc,
529       nsContentUtils::eDOM_PROPERTIES, errorMessage);
530 
531   // Ignore errors in calling the callback, since there is not much that we can
532   // do about it here.
533   if (mFailureCallback) {
534     nsAutoCString errorString(errorMessage);
535     RefPtr<DOMException> exception = DOMException::Create(
536         NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR, errorString);
537     mFailureCallback->Call(*exception);
538   }
539 
540   mPromise->MaybeReject(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
541 
542   mContext->RemoveFromDecodeQueue(this);
543 }
544 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const545 size_t WebAudioDecodeJob::SizeOfExcludingThis(
546     MallocSizeOf aMallocSizeOf) const {
547   size_t amount = 0;
548   if (mSuccessCallback) {
549     amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
550   }
551   if (mFailureCallback) {
552     amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
553   }
554   if (mOutput) {
555     amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
556   }
557   amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf, false);
558   return amount;
559 }
560 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const561 size_t WebAudioDecodeJob::SizeOfIncludingThis(
562     MallocSizeOf aMallocSizeOf) const {
563   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
564 }
565 
566 }  // namespace mozilla
567