LCOV - code coverage report
Current view: top level - dom/media/webaudio/blink - ReverbConvolver.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 90 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 5 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             :  */
      28             : 
      29             : #include "ReverbConvolver.h"
      30             : #include "ReverbConvolverStage.h"
      31             : 
      32             : using namespace mozilla;
      33             : 
      34             : namespace WebCore {
      35             : 
      36             : const int InputBufferSize = 8 * 16384;
      37             : 
      38             : // We only process the leading portion of the impulse response in the real-time thread.  We don't exceed this length.
      39             : // It turns out then, that the background thread has about 278msec of scheduling slop.
      40             : // Empirically, this has been found to be a good compromise between giving enough time for scheduling slop,
      41             : // while still minimizing the amount of processing done in the primary (high-priority) thread.
      42             : // This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming
      43             : // the very rough scheduling latencies are similar on these time-scales.  Of course, this code may need to be
      44             : // tuned for individual platforms if this assumption is found to be incorrect.
      45             : const size_t RealtimeFrameLimit = 8192 + 4096 // ~278msec @ 44.1KHz
      46             :                                   - WEBAUDIO_BLOCK_SIZE;
      47             : // First stage will have size MinFFTSize - successive stages will double in
      48             : // size each time until we hit the maximum size.
      49             : const size_t MinFFTSize = 256;
      50             : // If we are using background threads then don't exceed this FFT size for the
      51             : // stages which run in the real-time thread.  This avoids having only one or
      52             : // two large stages (size 16384 or so) at the end which take a lot of time
      53             : // every several processing slices.  This way we amortize the cost over more
      54             : // processing slices.
      55             : const size_t MaxRealtimeFFTSize = 4096;
      56             : 
      57           0 : ReverbConvolver::ReverbConvolver(const float* impulseResponseData,
      58             :                                  size_t impulseResponseLength,
      59             :                                  size_t maxFFTSize,
      60             :                                  size_t convolverRenderPhase,
      61           0 :                                  bool useBackgroundThreads)
      62             :     : m_impulseResponseLength(impulseResponseLength)
      63             :     , m_accumulationBuffer(impulseResponseLength + WEBAUDIO_BLOCK_SIZE)
      64             :     , m_inputBuffer(InputBufferSize)
      65             :     , m_backgroundThread("ConvolverWorker")
      66             :     , m_backgroundThreadCondition(&m_backgroundThreadLock)
      67             :     , m_useBackgroundThreads(useBackgroundThreads)
      68             :     , m_wantsToExit(false)
      69           0 :     , m_moreInputBuffered(false)
      70             : {
      71             :     // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads.
      72             :     // Otherwise, assume we're being run from a command-line tool.
      73           0 :     bool hasRealtimeConstraint = useBackgroundThreads;
      74             : 
      75           0 :     const float* response = impulseResponseData;
      76           0 :     size_t totalResponseLength = impulseResponseLength;
      77             : 
      78             :     // The total latency is zero because the first FFT stage is small enough
      79             :     // to return output in the first block.
      80           0 :     size_t reverbTotalLatency = 0;
      81             : 
      82           0 :     size_t stageOffset = 0;
      83           0 :     size_t stagePhase = 0;
      84           0 :     size_t fftSize = MinFFTSize;
      85           0 :     while (stageOffset < totalResponseLength) {
      86           0 :         size_t stageSize = fftSize / 2;
      87             : 
      88             :         // For the last stage, it's possible that stageOffset is such that we're straddling the end
      89             :         // of the impulse response buffer (if we use stageSize), so reduce the last stage's length...
      90           0 :         if (stageSize + stageOffset > totalResponseLength) {
      91           0 :             stageSize = totalResponseLength - stageOffset;
      92             :             // Use smallest FFT that is large enough to cover the last stage.
      93           0 :             fftSize = MinFFTSize;
      94           0 :             while (stageSize * 2 > fftSize) {
      95           0 :               fftSize *= 2;
      96             :             }
      97             :         }
      98             : 
      99             :         // This "staggers" the time when each FFT happens so they don't all happen at the same time
     100           0 :         int renderPhase = convolverRenderPhase + stagePhase;
     101             : 
     102             :         nsAutoPtr<ReverbConvolverStage> stage
     103             :           (new ReverbConvolverStage(response, totalResponseLength,
     104             :                                     reverbTotalLatency, stageOffset, stageSize,
     105             :                                     fftSize, renderPhase,
     106           0 :                                     &m_accumulationBuffer));
     107             : 
     108           0 :         bool isBackgroundStage = false;
     109             : 
     110           0 :         if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
     111           0 :             m_backgroundStages.AppendElement(stage.forget());
     112           0 :             isBackgroundStage = true;
     113             :         } else
     114           0 :             m_stages.AppendElement(stage.forget());
     115             : 
     116             :         // Figure out next FFT size
     117           0 :         fftSize *= 2;
     118             : 
     119           0 :         stageOffset += stageSize;
     120             : 
     121           0 :         if (hasRealtimeConstraint && !isBackgroundStage
     122           0 :             && fftSize > MaxRealtimeFFTSize) {
     123           0 :             fftSize = MaxRealtimeFFTSize;
     124             :             // Custom phase positions for all but the first of the realtime
     125             :             // stages of largest size.  These spread out the work of the
     126             :             // larger realtime stages.  None of the FFTs of size 1024, 2048 or
     127             :             // 4096 are performed when processing the same block.  The first
     128             :             // MaxRealtimeFFTSize = 4096 stage, at the end of the doubling,
     129             :             // performs its FFT at block 7.  The FFTs of size 2048 are
     130             :             // performed in blocks 3 + 8 * n and size 1024 at 1 + 4 * n.
     131           0 :             const uint32_t phaseLookup[] = { 14, 0, 10, 4 };
     132           0 :             stagePhase = WEBAUDIO_BLOCK_SIZE *
     133           0 :                 phaseLookup[m_stages.Length() % ArrayLength(phaseLookup)];
     134           0 :         } else if (fftSize > maxFFTSize) {
     135           0 :             fftSize = maxFFTSize;
     136             :             // A prime offset spreads out FFTs in a way that all
     137             :             // available phase positions will be used if there are sufficient
     138             :             // stages.
     139           0 :             stagePhase += 5 * WEBAUDIO_BLOCK_SIZE;
     140           0 :         } else if (stageSize > WEBAUDIO_BLOCK_SIZE) {
     141             :             // As the stages are doubling in size, the next FFT will occur
     142             :             // mid-way between FFTs for this stage.
     143           0 :             stagePhase = stageSize - WEBAUDIO_BLOCK_SIZE;
     144             :         }
     145             :     }
     146             : 
     147             :     // Start up background thread
     148             :     // FIXME: would be better to up the thread priority here.  It doesn't need to be real-time, but higher than the default...
     149           0 :     if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
     150           0 :         if (!m_backgroundThread.Start()) {
     151           0 :           NS_WARNING("Cannot start convolver thread.");
     152           0 :           return;
     153             :         }
     154           0 :         m_backgroundThread.message_loop()->PostTask(NewNonOwningRunnableMethod(
     155             :           "WebCore::ReverbConvolver::backgroundThreadEntry",
     156             :           this,
     157           0 :           &ReverbConvolver::backgroundThreadEntry));
     158             :     }
     159             : }
     160             : 
     161           0 : ReverbConvolver::~ReverbConvolver()
     162             : {
     163             :     // Wait for background thread to stop
     164           0 :     if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
     165           0 :         m_wantsToExit = true;
     166             : 
     167             :         // Wake up thread so it can return
     168             :         {
     169           0 :             AutoLock locker(m_backgroundThreadLock);
     170           0 :             m_moreInputBuffered = true;
     171           0 :             m_backgroundThreadCondition.Signal();
     172             :         }
     173             : 
     174           0 :         m_backgroundThread.Stop();
     175             :     }
     176           0 : }
     177             : 
     178           0 : size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     179             : {
     180           0 :     size_t amount = aMallocSizeOf(this);
     181           0 :     amount += m_stages.ShallowSizeOfExcludingThis(aMallocSizeOf);
     182           0 :     for (size_t i = 0; i < m_stages.Length(); i++) {
     183           0 :         if (m_stages[i]) {
     184           0 :             amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
     185             :         }
     186             :     }
     187             : 
     188           0 :     amount += m_backgroundStages.ShallowSizeOfExcludingThis(aMallocSizeOf);
     189           0 :     for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
     190           0 :         if (m_backgroundStages[i]) {
     191           0 :             amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
     192             :         }
     193             :     }
     194             : 
     195             :     // NB: The buffer sizes are static, so even though they might be accessed
     196             :     //     in another thread it's safe to measure them.
     197           0 :     amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
     198           0 :     amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
     199             : 
     200             :     // Possible future measurements:
     201             :     // - m_backgroundThread
     202             :     // - m_backgroundThreadLock
     203             :     // - m_backgroundThreadCondition
     204           0 :     return amount;
     205             : }
     206             : 
     207           0 : void ReverbConvolver::backgroundThreadEntry()
     208             : {
     209           0 :     while (!m_wantsToExit) {
     210             :         // Wait for realtime thread to give us more input
     211           0 :         m_moreInputBuffered = false;
     212             :         {
     213           0 :             AutoLock locker(m_backgroundThreadLock);
     214           0 :             while (!m_moreInputBuffered && !m_wantsToExit)
     215           0 :                 m_backgroundThreadCondition.Wait();
     216             :         }
     217             : 
     218             :         // Process all of the stages until their read indices reach the input buffer's write index
     219           0 :         int writeIndex = m_inputBuffer.writeIndex();
     220             : 
     221             :         // Even though it doesn't seem like every stage needs to maintain its own version of readIndex
     222             :         // we do this in case we want to run in more than one background thread.
     223             :         int readIndex;
     224             : 
     225           0 :         while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
     226             :             // Accumulate contributions from each stage
     227           0 :             for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
     228           0 :                 m_backgroundStages[i]->processInBackground(this);
     229             :         }
     230             :     }
     231           0 : }
     232             : 
     233           0 : void ReverbConvolver::process(const float* sourceChannelData,
     234             :                               float* destinationChannelData)
     235             : {
     236           0 :     const float* source = sourceChannelData;
     237           0 :     float* destination = destinationChannelData;
     238           0 :     bool isDataSafe = source && destination;
     239           0 :     MOZ_ASSERT(isDataSafe);
     240           0 :     if (!isDataSafe)
     241           0 :         return;
     242             : 
     243             :     // Feed input buffer (read by all threads)
     244           0 :     m_inputBuffer.write(source, WEBAUDIO_BLOCK_SIZE);
     245             : 
     246             :     // Accumulate contributions from each stage
     247           0 :     for (size_t i = 0; i < m_stages.Length(); ++i)
     248           0 :         m_stages[i]->process(source);
     249             : 
     250             :     // Finally read from accumulation buffer
     251           0 :     m_accumulationBuffer.readAndClear(destination, WEBAUDIO_BLOCK_SIZE);
     252             : 
     253             :     // Now that we've buffered more input, wake up our background thread.
     254             : 
     255             :     // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time
     256             :     // thread where it is a disaster for the lock to be contended (causes audio glitching).  It's OK if we fail to
     257             :     // signal from time to time, since we'll get to it the next time we're called.  We're called repeatedly
     258             :     // and frequently (around every 3ms).  The background thread is processing well into the future and has a considerable amount of
     259             :     // leeway here...
     260           0 :     if (m_backgroundThreadLock.Try()) {
     261           0 :         m_moreInputBuffered = true;
     262           0 :         m_backgroundThreadCondition.Signal();
     263           0 :         m_backgroundThreadLock.Release();
     264             :     }
     265             : }
     266             : 
     267             : } // namespace WebCore

Generated by: LCOV version 1.13