content/media/webaudio/blink/ReverbConvolver.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 /*
     2  * Copyright (C) 2010 Google Inc. All rights reserved.
     3  *
     4  * Redistribution and use in source and binary forms, with or without
     5  * modification, are permitted provided that the following conditions
     6  * are met:
     7  *
     8  * 1.  Redistributions of source code must retain the above copyright
     9  *     notice, this list of conditions and the following disclaimer.
    10  * 2.  Redistributions in binary form must reproduce the above copyright
    11  *     notice, this list of conditions and the following disclaimer in the
    12  *     documentation and/or other materials provided with the distribution.
    13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
    14  *     its contributors may be used to endorse or promote products derived
    15  *     from this software without specific prior written permission.
    16  *
    17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
    18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
    21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    27  */
    29 #include "ReverbConvolver.h"
    30 #include "ReverbConvolverStage.h"
    32 using namespace mozilla;
    34 template<>
    35 struct RunnableMethodTraits<WebCore::ReverbConvolver>
    36 {
    37   static void RetainCallee(WebCore::ReverbConvolver* obj) {}
    38   static void ReleaseCallee(WebCore::ReverbConvolver* obj) {}
    39 };
    41 namespace WebCore {
    43 const int InputBufferSize = 8 * 16384;
    45 // We only process the leading portion of the impulse response in the real-time thread.  We don't exceed this length.
    46 // It turns out then, that the background thread has about 278msec of scheduling slop.
    47 // Empirically, this has been found to be a good compromise between giving enough time for scheduling slop,
    48 // while still minimizing the amount of processing done in the primary (high-priority) thread.
    49 // This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming
    50 // the very rough scheduling latencies are similar on these time-scales.  Of course, this code may need to be
    51 // tuned for individual platforms if this assumption is found to be incorrect.
    52 const size_t RealtimeFrameLimit = 8192  + 4096; // ~278msec @ 44.1KHz
    54 const size_t MinFFTSize = 128;
    55 const size_t MaxRealtimeFFTSize = 2048;
    57 ReverbConvolver::ReverbConvolver(const float* impulseResponseData, size_t impulseResponseLength, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads)
    58     : m_impulseResponseLength(impulseResponseLength)
    59     , m_accumulationBuffer(impulseResponseLength + renderSliceSize)
    60     , m_inputBuffer(InputBufferSize)
    61     , m_minFFTSize(MinFFTSize) // First stage will have this size - successive stages will double in size each time
    62     , m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize
    63     , m_backgroundThread("ConvolverWorker")
    64     , m_backgroundThreadCondition(&m_backgroundThreadLock)
    65     , m_useBackgroundThreads(useBackgroundThreads)
    66     , m_wantsToExit(false)
    67     , m_moreInputBuffered(false)
    68 {
    69     // If we are using background threads then don't exceed this FFT size for the
    70     // stages which run in the real-time thread.  This avoids having only one or two
    71     // large stages (size 16384 or so) at the end which take a lot of time every several
    72     // processing slices.  This way we amortize the cost over more processing slices.
    73     m_maxRealtimeFFTSize = MaxRealtimeFFTSize;
    75     // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads.
    76     // Otherwise, assume we're being run from a command-line tool.
    77     bool hasRealtimeConstraint = useBackgroundThreads;
    79     const float* response = impulseResponseData;
    80     size_t totalResponseLength = impulseResponseLength;
    82     // The total latency is zero because the direct-convolution is used in the leading portion.
    83     size_t reverbTotalLatency = 0;
    85     size_t stageOffset = 0;
    86     int i = 0;
    87     size_t fftSize = m_minFFTSize;
    88     while (stageOffset < totalResponseLength) {
    89         size_t stageSize = fftSize / 2;
    91         // For the last stage, it's possible that stageOffset is such that we're straddling the end
    92         // of the impulse response buffer (if we use stageSize), so reduce the last stage's length...
    93         if (stageSize + stageOffset > totalResponseLength)
    94             stageSize = totalResponseLength - stageOffset;
    96         // This "staggers" the time when each FFT happens so they don't all happen at the same time
    97         int renderPhase = convolverRenderPhase + i * renderSliceSize;
    99         bool useDirectConvolver = !stageOffset;
   101         nsAutoPtr<ReverbConvolverStage> stage(new ReverbConvolverStage(response, totalResponseLength, reverbTotalLatency, stageOffset, stageSize, fftSize, renderPhase, renderSliceSize, &m_accumulationBuffer, useDirectConvolver));
   103         bool isBackgroundStage = false;
   105         if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
   106             m_backgroundStages.AppendElement(stage.forget());
   107             isBackgroundStage = true;
   108         } else
   109             m_stages.AppendElement(stage.forget());
   111         stageOffset += stageSize;
   112         ++i;
   114         if (!useDirectConvolver) {
   115             // Figure out next FFT size
   116             fftSize *= 2;
   117         }
   119         if (hasRealtimeConstraint && !isBackgroundStage && fftSize > m_maxRealtimeFFTSize)
   120             fftSize = m_maxRealtimeFFTSize;
   121         if (fftSize > m_maxFFTSize)
   122             fftSize = m_maxFFTSize;
   123     }
   125     // Start up background thread
   126     // FIXME: would be better to up the thread priority here.  It doesn't need to be real-time, but higher than the default...
   127     if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
   128         if (!m_backgroundThread.Start()) {
   129           NS_WARNING("Cannot start convolver thread.");
   130           return;
   131         }
   132         CancelableTask* task = NewRunnableMethod(this, &ReverbConvolver::backgroundThreadEntry);
   133         m_backgroundThread.message_loop()->PostTask(FROM_HERE, task);
   134     }
   135 }
   137 ReverbConvolver::~ReverbConvolver()
   138 {
   139     // Wait for background thread to stop
   140     if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
   141         m_wantsToExit = true;
   143         // Wake up thread so it can return
   144         {
   145             AutoLock locker(m_backgroundThreadLock);
   146             m_moreInputBuffered = true;
   147             m_backgroundThreadCondition.Signal();
   148         }
   150         m_backgroundThread.Stop();
   151     }
   152 }
   154 size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   155 {
   156     size_t amount = aMallocSizeOf(this);
   157     amount += m_stages.SizeOfExcludingThis(aMallocSizeOf);
   158     for (size_t i = 0; i < m_stages.Length(); i++) {
   159         if (m_stages[i]) {
   160             amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
   161         }
   162     }
   164     amount += m_backgroundStages.SizeOfExcludingThis(aMallocSizeOf);
   165     for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
   166         if (m_backgroundStages[i]) {
   167             amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
   168         }
   169     }
   171     // NB: The buffer sizes are static, so even though they might be accessed
   172     //     in another thread it's safe to measure them.
   173     amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
   174     amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
   176     // Possible future measurements:
   177     // - m_backgroundThread
   178     // - m_backgroundThreadLock
   179     // - m_backgroundThreadCondition
   180     return amount;
   181 }
   183 void ReverbConvolver::backgroundThreadEntry()
   184 {
   185     while (!m_wantsToExit) {
   186         // Wait for realtime thread to give us more input
   187         m_moreInputBuffered = false;
   188         {
   189             AutoLock locker(m_backgroundThreadLock);
   190             while (!m_moreInputBuffered && !m_wantsToExit)
   191                 m_backgroundThreadCondition.Wait();
   192         }
   194         // Process all of the stages until their read indices reach the input buffer's write index
   195         int writeIndex = m_inputBuffer.writeIndex();
   197         // Even though it doesn't seem like every stage needs to maintain its own version of readIndex 
   198         // we do this in case we want to run in more than one background thread.
   199         int readIndex;
   201         while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
   202             // The ReverbConvolverStages need to process in amounts which evenly divide half the FFT size
   203             const int SliceSize = MinFFTSize / 2;
   205             // Accumulate contributions from each stage
   206             for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
   207                 m_backgroundStages[i]->processInBackground(this, SliceSize);
   208         }
   209     }
   210 }
   212 void ReverbConvolver::process(const float* sourceChannelData, size_t sourceChannelLength,
   213                               float* destinationChannelData, size_t destinationChannelLength,
   214                               size_t framesToProcess)
   215 {
   216     bool isSafe = sourceChannelData && destinationChannelData && sourceChannelLength >= framesToProcess && destinationChannelLength >= framesToProcess;
   217     MOZ_ASSERT(isSafe);
   218     if (!isSafe)
   219         return;
   221     const float* source = sourceChannelData;
   222     float* destination = destinationChannelData;
   223     bool isDataSafe = source && destination;
   224     MOZ_ASSERT(isDataSafe);
   225     if (!isDataSafe)
   226         return;
   228     // Feed input buffer (read by all threads)
   229     m_inputBuffer.write(source, framesToProcess);
   231     // Accumulate contributions from each stage
   232     for (size_t i = 0; i < m_stages.Length(); ++i)
   233         m_stages[i]->process(source, framesToProcess);
   235     // Finally read from accumulation buffer
   236     m_accumulationBuffer.readAndClear(destination, framesToProcess);
   238     // Now that we've buffered more input, wake up our background thread.
   240     // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time
   241     // thread where it is a disaster for the lock to be contended (causes audio glitching).  It's OK if we fail to
   242     // signal from time to time, since we'll get to it the next time we're called.  We're called repeatedly
   243     // and frequently (around every 3ms).  The background thread is processing well into the future and has a considerable amount of 
   244     // leeway here...
   245     if (m_backgroundThreadLock.Try()) {
   246         m_moreInputBuffered = true;
   247         m_backgroundThreadCondition.Signal();
   248         m_backgroundThreadLock.Release();
   249     }
   250 }
   252 void ReverbConvolver::reset()
   253 {
   254     for (size_t i = 0; i < m_stages.Length(); ++i)
   255         m_stages[i]->reset();
   257     for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
   258         m_backgroundStages[i]->reset();
   260     m_accumulationBuffer.reset();
   261     m_inputBuffer.reset();
   262 }
   264 size_t ReverbConvolver::latencyFrames() const
   265 {
   266     return 0;
   267 }
   269 } // namespace WebCore

mercurial