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