content/media/webaudio/MediaBufferDecoder.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:74af87de688a
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 "BufferDecoder.h"
9 #include "mozilla/dom/AudioContextBinding.h"
10 #include <speex/speex_resampler.h>
11 #include "nsXPCOMCIDInternal.h"
12 #include "nsComponentManagerUtils.h"
13 #include "MediaDecoderReader.h"
14 #include "BufferMediaResource.h"
15 #include "DecoderTraits.h"
16 #include "AudioContext.h"
17 #include "AudioBuffer.h"
18 #include "nsIScriptObjectPrincipal.h"
19 #include "nsIScriptError.h"
20 #include "nsMimeTypes.h"
21 #include "nsCxPusher.h"
22 #include "WebAudioUtils.h"
23
24 namespace mozilla {
25
26 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAudioDecodeJob)
27
28 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebAudioDecodeJob)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
30 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutput)
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuccessCallback)
32 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFailureCallback)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
34
35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebAudioDecodeJob)
36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutput)
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuccessCallback)
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFailureCallback)
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
42
43 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WebAudioDecodeJob)
44 NS_IMPL_CYCLE_COLLECTION_TRACE_END
45 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebAudioDecodeJob, AddRef)
46 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebAudioDecodeJob, Release)
47
48 using namespace dom;
49
50 class ReportResultTask : public nsRunnable
51 {
52 public:
53 ReportResultTask(WebAudioDecodeJob& aDecodeJob,
54 WebAudioDecodeJob::ResultFn aFunction,
55 WebAudioDecodeJob::ErrorCode aErrorCode)
56 : mDecodeJob(aDecodeJob)
57 , mFunction(aFunction)
58 , mErrorCode(aErrorCode)
59 {
60 MOZ_ASSERT(aFunction);
61 }
62
63 NS_IMETHOD Run()
64 {
65 MOZ_ASSERT(NS_IsMainThread());
66
67 (mDecodeJob.*mFunction)(mErrorCode);
68
69 return NS_OK;
70 }
71
72 private:
73 // Note that the mDecodeJob member will probably die when mFunction is run.
74 // Therefore, it is not safe to do anything fancy with it in this class.
75 // Really, this class is only used because nsRunnableMethod doesn't support
76 // methods accepting arguments.
77 WebAudioDecodeJob& mDecodeJob;
78 WebAudioDecodeJob::ResultFn mFunction;
79 WebAudioDecodeJob::ErrorCode mErrorCode;
80 };
81
82 MOZ_BEGIN_ENUM_CLASS(PhaseEnum, int)
83 Decode,
84 AllocateBuffer,
85 Done
86 MOZ_END_ENUM_CLASS(PhaseEnum)
87
88 class MediaDecodeTask : public nsRunnable
89 {
90 public:
91 MediaDecodeTask(const char* aContentType, uint8_t* aBuffer,
92 uint32_t aLength,
93 WebAudioDecodeJob& aDecodeJob,
94 nsIThreadPool* aThreadPool)
95 : mContentType(aContentType)
96 , mBuffer(aBuffer)
97 , mLength(aLength)
98 , mDecodeJob(aDecodeJob)
99 , mPhase(PhaseEnum::Decode)
100 , mThreadPool(aThreadPool)
101 {
102 MOZ_ASSERT(aBuffer);
103 MOZ_ASSERT(NS_IsMainThread());
104
105 nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(mDecodeJob.mContext->GetParentObject());
106 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
107 do_QueryInterface(pWindow);
108 if (scriptPrincipal) {
109 mPrincipal = scriptPrincipal->GetPrincipal();
110 }
111 }
112
113 NS_IMETHOD Run();
114 bool CreateReader();
115
116 private:
117 void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
118 if (NS_IsMainThread()) {
119 Cleanup();
120 mDecodeJob.OnFailure(aErrorCode);
121 } else {
122 // Take extra care to cleanup on the main thread
123 NS_DispatchToMainThread(NS_NewRunnableMethod(this, &MediaDecodeTask::Cleanup),
124 NS_DISPATCH_NORMAL);
125
126 nsCOMPtr<nsIRunnable> event =
127 new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
128 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
129 }
130 }
131
132 void Decode();
133 void AllocateBuffer();
134 void CallbackTheResult();
135
136 void Cleanup()
137 {
138 MOZ_ASSERT(NS_IsMainThread());
139 // MediaDecoderReader expects that BufferDecoder is alive.
140 // Destruct MediaDecoderReader first.
141 mDecoderReader = nullptr;
142 mBufferDecoder = nullptr;
143 JS_free(nullptr, mBuffer);
144 }
145
146 private:
147 nsCString mContentType;
148 uint8_t* mBuffer;
149 uint32_t mLength;
150 WebAudioDecodeJob& mDecodeJob;
151 PhaseEnum mPhase;
152 nsCOMPtr<nsIThreadPool> mThreadPool;
153 nsCOMPtr<nsIPrincipal> mPrincipal;
154 nsRefPtr<BufferDecoder> mBufferDecoder;
155 nsAutoPtr<MediaDecoderReader> mDecoderReader;
156 };
157
158 NS_IMETHODIMP
159 MediaDecodeTask::Run()
160 {
161 MOZ_ASSERT(mBufferDecoder);
162 MOZ_ASSERT(mDecoderReader);
163 switch (mPhase) {
164 case PhaseEnum::Decode:
165 Decode();
166 break;
167 case PhaseEnum::AllocateBuffer:
168 AllocateBuffer();
169 break;
170 case PhaseEnum::Done:
171 break;
172 }
173
174 return NS_OK;
175 }
176
177 bool
178 MediaDecodeTask::CreateReader()
179 {
180 MOZ_ASSERT(NS_IsMainThread());
181
182 nsRefPtr<BufferMediaResource> resource =
183 new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
184 mLength, mPrincipal, mContentType);
185
186 MOZ_ASSERT(!mBufferDecoder);
187 mBufferDecoder = new BufferDecoder(resource);
188
189 // If you change this list to add support for new decoders, please consider
190 // updating HTMLMediaElement::CreateDecoder as well.
191
192 mDecoderReader = DecoderTraits::CreateReader(mContentType, mBufferDecoder);
193
194 if (!mDecoderReader) {
195 return false;
196 }
197
198 nsresult rv = mDecoderReader->Init(nullptr);
199 if (NS_FAILED(rv)) {
200 return false;
201 }
202
203 return true;
204 }
205
206 class AutoResampler {
207 public:
208 AutoResampler()
209 : mResampler(nullptr)
210 {}
211 ~AutoResampler()
212 {
213 if (mResampler) {
214 speex_resampler_destroy(mResampler);
215 }
216 }
217 operator SpeexResamplerState*() const
218 {
219 MOZ_ASSERT(mResampler);
220 return mResampler;
221 }
222 void operator=(SpeexResamplerState* aResampler)
223 {
224 mResampler = aResampler;
225 }
226
227 private:
228 SpeexResamplerState* mResampler;
229 };
230
231 void
232 MediaDecodeTask::Decode()
233 {
234 MOZ_ASSERT(!NS_IsMainThread());
235
236 mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
237
238 // Tell the decoder reader that we are not going to play the data directly,
239 // and that we should not reject files with more channels than the audio
240 // bakend support.
241 mDecoderReader->SetIgnoreAudioOutputFormat();
242
243 MediaInfo mediaInfo;
244 nsAutoPtr<MetadataTags> tags;
245 nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags));
246 if (NS_FAILED(rv)) {
247 ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
248 return;
249 }
250
251 if (!mDecoderReader->HasAudio()) {
252 ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
253 return;
254 }
255
256 while (mDecoderReader->DecodeAudioData()) {
257 // consume all of the buffer
258 continue;
259 }
260
261 MediaQueue<AudioData>& audioQueue = mDecoderReader->AudioQueue();
262 uint32_t frameCount = audioQueue.FrameCount();
263 uint32_t channelCount = mediaInfo.mAudio.mChannels;
264 uint32_t sampleRate = mediaInfo.mAudio.mRate;
265
266 if (!frameCount || !channelCount || !sampleRate) {
267 ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
268 return;
269 }
270
271 const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
272 AutoResampler resampler;
273
274 uint32_t resampledFrames = frameCount;
275 if (sampleRate != destSampleRate) {
276 resampledFrames = static_cast<uint32_t>(
277 static_cast<uint64_t>(destSampleRate) *
278 static_cast<uint64_t>(frameCount) /
279 static_cast<uint64_t>(sampleRate)
280 );
281
282 resampler = speex_resampler_init(channelCount,
283 sampleRate,
284 destSampleRate,
285 SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr);
286 speex_resampler_skip_zeros(resampler);
287 resampledFrames += speex_resampler_get_output_latency(resampler);
288 }
289
290 // Allocate the channel buffers. Note that if we end up resampling, we may
291 // write fewer bytes than mResampledFrames to the output buffer, in which
292 // case mWriteIndex will tell us how many valid samples we have.
293 static const fallible_t fallible = fallible_t();
294 bool memoryAllocationSuccess = true;
295 if (!mDecodeJob.mChannelBuffers.SetLength(channelCount)) {
296 memoryAllocationSuccess = false;
297 } else {
298 for (uint32_t i = 0; i < channelCount; ++i) {
299 mDecodeJob.mChannelBuffers[i] = new(fallible) float[resampledFrames];
300 if (!mDecodeJob.mChannelBuffers[i]) {
301 memoryAllocationSuccess = false;
302 break;
303 }
304 }
305 }
306 if (!memoryAllocationSuccess) {
307 ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
308 return;
309 }
310
311 nsAutoPtr<AudioData> audioData;
312 while ((audioData = audioQueue.PopFront())) {
313 audioData->EnsureAudioBuffer(); // could lead to a copy :(
314 AudioDataValue* bufferData = static_cast<AudioDataValue*>
315 (audioData->mAudioBuffer->Data());
316
317 if (sampleRate != destSampleRate) {
318 const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
319
320 for (uint32_t i = 0; i < audioData->mChannels; ++i) {
321 uint32_t inSamples = audioData->mFrames;
322 uint32_t outSamples = maxOutSamples;
323
324 WebAudioUtils::SpeexResamplerProcess(
325 resampler, i, &bufferData[i * audioData->mFrames], &inSamples,
326 mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex,
327 &outSamples);
328
329 if (i == audioData->mChannels - 1) {
330 mDecodeJob.mWriteIndex += outSamples;
331 MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
332 MOZ_ASSERT(inSamples == audioData->mFrames);
333 }
334 }
335 } else {
336 for (uint32_t i = 0; i < audioData->mChannels; ++i) {
337 ConvertAudioSamples(&bufferData[i * audioData->mFrames],
338 mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex,
339 audioData->mFrames);
340
341 if (i == audioData->mChannels - 1) {
342 mDecodeJob.mWriteIndex += audioData->mFrames;
343 }
344 }
345 }
346 }
347
348 if (sampleRate != destSampleRate) {
349 uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
350 const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
351 for (uint32_t i = 0; i < channelCount; ++i) {
352 uint32_t inSamples = inputLatency;
353 uint32_t outSamples = maxOutSamples;
354
355 WebAudioUtils::SpeexResamplerProcess(
356 resampler, i, (AudioDataValue*)nullptr, &inSamples,
357 mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex,
358 &outSamples);
359
360 if (i == channelCount - 1) {
361 mDecodeJob.mWriteIndex += outSamples;
362 MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
363 MOZ_ASSERT(inSamples == inputLatency);
364 }
365 }
366 }
367
368 mPhase = PhaseEnum::AllocateBuffer;
369 NS_DispatchToMainThread(this);
370 }
371
372 void
373 MediaDecodeTask::AllocateBuffer()
374 {
375 MOZ_ASSERT(NS_IsMainThread());
376
377 if (!mDecodeJob.AllocateBuffer()) {
378 ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
379 return;
380 }
381
382 mPhase = PhaseEnum::Done;
383 CallbackTheResult();
384 }
385
386 void
387 MediaDecodeTask::CallbackTheResult()
388 {
389 MOZ_ASSERT(NS_IsMainThread());
390
391 Cleanup();
392
393 // Now, we're ready to call the script back with the resulting buffer
394 mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
395 }
396
397 bool
398 WebAudioDecodeJob::AllocateBuffer()
399 {
400 MOZ_ASSERT(!mOutput);
401 MOZ_ASSERT(NS_IsMainThread());
402
403 // First, get a JSContext
404 AutoPushJSContext cx(mContext->GetJSContext());
405 if (!cx) {
406 return false;
407 }
408 // Now create the AudioBuffer
409 ErrorResult rv;
410 mOutput = AudioBuffer::Create(mContext, mChannelBuffers.Length(),
411 mWriteIndex, mContext->SampleRate(), cx, rv);
412 if (rv.Failed()) {
413 return false;
414 }
415
416 for (uint32_t i = 0; i < mChannelBuffers.Length(); ++i) {
417 mOutput->SetRawChannelContents(cx, i, mChannelBuffers[i]);
418 }
419
420 return true;
421 }
422
423 void
424 MediaBufferDecoder::AsyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
425 uint32_t aLength,
426 WebAudioDecodeJob& aDecodeJob)
427 {
428 // Do not attempt to decode the media if we were not successful at sniffing
429 // the content type.
430 if (!*aContentType ||
431 strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0) {
432 nsCOMPtr<nsIRunnable> event =
433 new ReportResultTask(aDecodeJob,
434 &WebAudioDecodeJob::OnFailure,
435 WebAudioDecodeJob::UnknownContent);
436 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
437 return;
438 }
439
440 if (!EnsureThreadPoolInitialized()) {
441 nsCOMPtr<nsIRunnable> event =
442 new ReportResultTask(aDecodeJob,
443 &WebAudioDecodeJob::OnFailure,
444 WebAudioDecodeJob::UnknownError);
445 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
446 return;
447 }
448
449 MOZ_ASSERT(mThreadPool);
450
451 nsRefPtr<MediaDecodeTask> task =
452 new MediaDecodeTask(aContentType, aBuffer, aLength, aDecodeJob, mThreadPool);
453 if (!task->CreateReader()) {
454 nsCOMPtr<nsIRunnable> event =
455 new ReportResultTask(aDecodeJob,
456 &WebAudioDecodeJob::OnFailure,
457 WebAudioDecodeJob::UnknownError);
458 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
459 } else {
460 mThreadPool->Dispatch(task, nsIThreadPool::DISPATCH_NORMAL);
461 }
462 }
463
464 bool
465 MediaBufferDecoder::EnsureThreadPoolInitialized()
466 {
467 if (!mThreadPool) {
468 mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
469 if (!mThreadPool) {
470 return false;
471 }
472 mThreadPool->SetName(NS_LITERAL_CSTRING("MediaBufferDecoder"));
473 }
474 return true;
475 }
476
477 void
478 MediaBufferDecoder::Shutdown() {
479 if (mThreadPool) {
480 // Setting threadLimit to 0 causes threads to exit when all events have
481 // been run, like nsIThreadPool::Shutdown(), but doesn't run a nested event
482 // loop nor wait until this has happened.
483 mThreadPool->SetThreadLimit(0);
484 mThreadPool = nullptr;
485 }
486 }
487
488 WebAudioDecodeJob::WebAudioDecodeJob(const nsACString& aContentType,
489 AudioContext* aContext,
490 DecodeSuccessCallback* aSuccessCallback,
491 DecodeErrorCallback* aFailureCallback)
492 : mContentType(aContentType)
493 , mWriteIndex(0)
494 , mContext(aContext)
495 , mSuccessCallback(aSuccessCallback)
496 , mFailureCallback(aFailureCallback)
497 {
498 MOZ_ASSERT(aContext);
499 MOZ_ASSERT(aSuccessCallback);
500 MOZ_ASSERT(NS_IsMainThread());
501 MOZ_COUNT_CTOR(WebAudioDecodeJob);
502 }
503
504 WebAudioDecodeJob::~WebAudioDecodeJob()
505 {
506 MOZ_ASSERT(NS_IsMainThread());
507 MOZ_COUNT_DTOR(WebAudioDecodeJob);
508 }
509
510 void
511 WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode)
512 {
513 MOZ_ASSERT(NS_IsMainThread());
514 MOZ_ASSERT(aErrorCode == NoError);
515
516 // Ignore errors in calling the callback, since there is not much that we can
517 // do about it here.
518 ErrorResult rv;
519 mSuccessCallback->Call(*mOutput, rv);
520
521 mContext->RemoveFromDecodeQueue(this);
522 }
523
524 void
525 WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
526 {
527 MOZ_ASSERT(NS_IsMainThread());
528
529 const char* errorMessage;
530 switch (aErrorCode) {
531 case NoError:
532 MOZ_ASSERT(false, "Who passed NoError to OnFailure?");
533 // Fall through to get some sort of a sane error message if this actually
534 // happens at runtime.
535 case UnknownError:
536 errorMessage = "MediaDecodeAudioDataUnknownError";
537 break;
538 case UnknownContent:
539 errorMessage = "MediaDecodeAudioDataUnknownContentType";
540 break;
541 case InvalidContent:
542 errorMessage = "MediaDecodeAudioDataInvalidContent";
543 break;
544 case NoAudio:
545 errorMessage = "MediaDecodeAudioDataNoAudio";
546 break;
547 }
548
549 nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(mContext->GetParentObject());
550 nsIDocument* doc = nullptr;
551 if (pWindow) {
552 doc = pWindow->GetExtantDoc();
553 }
554 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
555 NS_LITERAL_CSTRING("Media"),
556 doc,
557 nsContentUtils::eDOM_PROPERTIES,
558 errorMessage);
559
560 // Ignore errors in calling the callback, since there is not much that we can
561 // do about it here.
562 if (mFailureCallback) {
563 ErrorResult rv;
564 mFailureCallback->Call(rv);
565 }
566
567 mContext->RemoveFromDecodeQueue(this);
568 }
569
570 size_t
571 WebAudioDecodeJob::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
572 {
573 size_t amount = 0;
574 amount += mContentType.SizeOfExcludingThisMustBeUnshared(aMallocSizeOf);
575 if (mSuccessCallback) {
576 amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
577 }
578 if (mFailureCallback) {
579 amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
580 }
581 if (mOutput) {
582 amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
583 }
584 amount += mChannelBuffers.SizeOfExcludingThis(aMallocSizeOf);
585 for (uint32_t i = 0; i < mChannelBuffers.Length(); ++i) {
586 amount += mChannelBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
587 }
588 return amount;
589 }
590
591 }
592

mercurial