content/media/webaudio/MediaBufferDecoder.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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 "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"
    24 namespace mozilla {
    26 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAudioDecodeJob)
    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
    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
    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)
    48 using namespace dom;
    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   }
    63   NS_IMETHOD Run()
    64   {
    65     MOZ_ASSERT(NS_IsMainThread());
    67     (mDecodeJob.*mFunction)(mErrorCode);
    69     return NS_OK;
    70   }
    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 };
    82 MOZ_BEGIN_ENUM_CLASS(PhaseEnum, int)
    83   Decode,
    84   AllocateBuffer,
    85   Done
    86 MOZ_END_ENUM_CLASS(PhaseEnum)
    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());
   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   }
   113   NS_IMETHOD Run();
   114   bool CreateReader();
   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);
   126       nsCOMPtr<nsIRunnable> event =
   127         new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
   128       NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   129     }
   130   }
   132   void Decode();
   133   void AllocateBuffer();
   134   void CallbackTheResult();
   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   }
   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 };
   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   }
   174   return NS_OK;
   175 }
   177 bool
   178 MediaDecodeTask::CreateReader()
   179 {
   180   MOZ_ASSERT(NS_IsMainThread());
   182   nsRefPtr<BufferMediaResource> resource =
   183     new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
   184                             mLength, mPrincipal, mContentType);
   186   MOZ_ASSERT(!mBufferDecoder);
   187   mBufferDecoder = new BufferDecoder(resource);
   189   // If you change this list to add support for new decoders, please consider
   190   // updating HTMLMediaElement::CreateDecoder as well.
   192   mDecoderReader = DecoderTraits::CreateReader(mContentType, mBufferDecoder);
   194   if (!mDecoderReader) {
   195     return false;
   196   }
   198   nsresult rv = mDecoderReader->Init(nullptr);
   199   if (NS_FAILED(rv)) {
   200     return false;
   201   }
   203   return true;
   204 }
   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   }
   227 private:
   228   SpeexResamplerState* mResampler;
   229 };
   231 void
   232 MediaDecodeTask::Decode()
   233 {
   234   MOZ_ASSERT(!NS_IsMainThread());
   236   mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
   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();
   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   }
   251   if (!mDecoderReader->HasAudio()) {
   252     ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
   253     return;
   254   }
   256   while (mDecoderReader->DecodeAudioData()) {
   257     // consume all of the buffer
   258     continue;
   259   }
   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;
   266   if (!frameCount || !channelCount || !sampleRate) {
   267     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
   268     return;
   269   }
   271   const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
   272   AutoResampler resampler;
   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       );
   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   }
   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   }
   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());
   317     if (sampleRate != destSampleRate) {
   318       const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
   320       for (uint32_t i = 0; i < audioData->mChannels; ++i) {
   321         uint32_t inSamples = audioData->mFrames;
   322         uint32_t outSamples = maxOutSamples;
   324         WebAudioUtils::SpeexResamplerProcess(
   325             resampler, i, &bufferData[i * audioData->mFrames], &inSamples,
   326             mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex,
   327             &outSamples);
   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);
   341         if (i == audioData->mChannels - 1) {
   342           mDecodeJob.mWriteIndex += audioData->mFrames;
   343         }
   344       }
   345     }
   346   }
   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;
   355       WebAudioUtils::SpeexResamplerProcess(
   356           resampler, i, (AudioDataValue*)nullptr, &inSamples,
   357           mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex,
   358           &outSamples);
   360       if (i == channelCount - 1) {
   361         mDecodeJob.mWriteIndex += outSamples;
   362         MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
   363         MOZ_ASSERT(inSamples == inputLatency);
   364       }
   365     }
   366   }
   368   mPhase = PhaseEnum::AllocateBuffer;
   369   NS_DispatchToMainThread(this);
   370 }
   372 void
   373 MediaDecodeTask::AllocateBuffer()
   374 {
   375   MOZ_ASSERT(NS_IsMainThread());
   377   if (!mDecodeJob.AllocateBuffer()) {
   378     ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
   379     return;
   380   }
   382   mPhase = PhaseEnum::Done;
   383   CallbackTheResult();
   384 }
   386 void
   387 MediaDecodeTask::CallbackTheResult()
   388 {
   389   MOZ_ASSERT(NS_IsMainThread());
   391   Cleanup();
   393   // Now, we're ready to call the script back with the resulting buffer
   394   mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
   395 }
   397 bool
   398 WebAudioDecodeJob::AllocateBuffer()
   399 {
   400   MOZ_ASSERT(!mOutput);
   401   MOZ_ASSERT(NS_IsMainThread());
   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   }
   416   for (uint32_t i = 0; i < mChannelBuffers.Length(); ++i) {
   417     mOutput->SetRawChannelContents(cx, i, mChannelBuffers[i]);
   418   }
   420   return true;
   421 }
   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   }
   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   }
   449   MOZ_ASSERT(mThreadPool);
   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 }
   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 }
   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 }
   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 }
   504 WebAudioDecodeJob::~WebAudioDecodeJob()
   505 {
   506   MOZ_ASSERT(NS_IsMainThread());
   507   MOZ_COUNT_DTOR(WebAudioDecodeJob);
   508 }
   510 void
   511 WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode)
   512 {
   513   MOZ_ASSERT(NS_IsMainThread());
   514   MOZ_ASSERT(aErrorCode == NoError);
   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);
   521   mContext->RemoveFromDecodeQueue(this);
   522 }
   524 void
   525 WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
   526 {
   527   MOZ_ASSERT(NS_IsMainThread());
   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   }
   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);
   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   }
   567   mContext->RemoveFromDecodeQueue(this);
   568 }
   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 }
   591 }

mercurial