Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/webaudio/blink/ReverbConvolver.cpp
Line
Count
Source (jump to first uncovered line)
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
ReverbConvolver::ReverbConvolver(const float* impulseResponseData,
58
                                 size_t impulseResponseLength,
59
                                 size_t maxFFTSize,
60
                                 size_t convolverRenderPhase,
61
                                 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
    , m_moreInputBuffered(false)
70
0
{
71
0
    // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads.
72
0
    // Otherwise, assume we're being run from a command-line tool.
73
0
    bool hasRealtimeConstraint = useBackgroundThreads;
74
0
75
0
    const float* response = impulseResponseData;
76
0
    size_t totalResponseLength = impulseResponseLength;
77
0
78
0
    // The total latency is zero because the first FFT stage is small enough
79
0
    // to return output in the first block.
80
0
    size_t reverbTotalLatency = 0;
81
0
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
0
88
0
        // For the last stage, it's possible that stageOffset is such that we're straddling the end
89
0
        // 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
0
            // 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
0
            }
97
0
        }
98
0
99
0
        // 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
0
102
0
        nsAutoPtr<ReverbConvolverStage> stage
103
0
          (new ReverbConvolverStage(response, totalResponseLength,
104
0
                                    reverbTotalLatency, stageOffset, stageSize,
105
0
                                    fftSize, renderPhase,
106
0
                                    &m_accumulationBuffer));
107
0
108
0
        bool isBackgroundStage = false;
109
0
110
0
        if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
111
0
            m_backgroundStages.AppendElement(stage.forget());
112
0
            isBackgroundStage = true;
113
0
        } else
114
0
            m_stages.AppendElement(stage.forget());
115
0
116
0
        // Figure out next FFT size
117
0
        fftSize *= 2;
118
0
119
0
        stageOffset += stageSize;
120
0
121
0
        if (hasRealtimeConstraint && !isBackgroundStage &&
122
0
            fftSize > MaxRealtimeFFTSize) {
123
0
          fftSize = MaxRealtimeFFTSize;
124
0
          // Custom phase positions for all but the first of the realtime
125
0
          // stages of largest size.  These spread out the work of the
126
0
          // larger realtime stages.  None of the FFTs of size 1024, 2048 or
127
0
          // 4096 are performed when processing the same block.  The first
128
0
          // MaxRealtimeFFTSize = 4096 stage, at the end of the doubling,
129
0
          // performs its FFT at block 7.  The FFTs of size 2048 are
130
0
          // 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 =
133
0
            WEBAUDIO_BLOCK_SIZE *
134
0
            phaseLookup[m_stages.Length() % ArrayLength(phaseLookup)];
135
0
        } else if (fftSize > maxFFTSize) {
136
0
            fftSize = maxFFTSize;
137
0
            // A prime offset spreads out FFTs in a way that all
138
0
            // available phase positions will be used if there are sufficient
139
0
            // stages.
140
0
            stagePhase += 5 * WEBAUDIO_BLOCK_SIZE;
141
0
        } else if (stageSize > WEBAUDIO_BLOCK_SIZE) {
142
0
            // As the stages are doubling in size, the next FFT will occur
143
0
            // mid-way between FFTs for this stage.
144
0
            stagePhase = stageSize - WEBAUDIO_BLOCK_SIZE;
145
0
        }
146
0
    }
147
0
148
0
    // Start up background thread
149
0
    // FIXME: would be better to up the thread priority here.  It doesn't need to be real-time, but higher than the default...
150
0
    if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
151
0
        if (!m_backgroundThread.Start()) {
152
0
          NS_WARNING("Cannot start convolver thread.");
153
0
          return;
154
0
        }
155
0
        m_backgroundThread.message_loop()->PostTask(NewNonOwningRunnableMethod(
156
0
          "WebCore::ReverbConvolver::backgroundThreadEntry",
157
0
          this,
158
0
          &ReverbConvolver::backgroundThreadEntry));
159
0
    }
160
0
}
161
162
ReverbConvolver::~ReverbConvolver()
163
0
{
164
0
    // Wait for background thread to stop
165
0
    if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
166
0
        m_wantsToExit = true;
167
0
168
0
        // Wake up thread so it can return
169
0
        {
170
0
            AutoLock locker(m_backgroundThreadLock);
171
0
            m_moreInputBuffered = true;
172
0
            m_backgroundThreadCondition.Signal();
173
0
        }
174
0
175
0
        m_backgroundThread.Stop();
176
0
    }
177
0
}
178
179
size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
180
0
{
181
0
    size_t amount = aMallocSizeOf(this);
182
0
    amount += m_stages.ShallowSizeOfExcludingThis(aMallocSizeOf);
183
0
    for (size_t i = 0; i < m_stages.Length(); i++) {
184
0
        if (m_stages[i]) {
185
0
            amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
186
0
        }
187
0
    }
188
0
189
0
    amount += m_backgroundStages.ShallowSizeOfExcludingThis(aMallocSizeOf);
190
0
    for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
191
0
        if (m_backgroundStages[i]) {
192
0
            amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
193
0
        }
194
0
    }
195
0
196
0
    // NB: The buffer sizes are static, so even though they might be accessed
197
0
    //     in another thread it's safe to measure them.
198
0
    amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
199
0
    amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
200
0
201
0
    // Possible future measurements:
202
0
    // - m_backgroundThread
203
0
    // - m_backgroundThreadLock
204
0
    // - m_backgroundThreadCondition
205
0
    return amount;
206
0
}
207
208
void ReverbConvolver::backgroundThreadEntry()
209
0
{
210
0
    while (!m_wantsToExit) {
211
0
        // Wait for realtime thread to give us more input
212
0
        m_moreInputBuffered = false;
213
0
        {
214
0
            AutoLock locker(m_backgroundThreadLock);
215
0
            while (!m_moreInputBuffered && !m_wantsToExit)
216
0
                m_backgroundThreadCondition.Wait();
217
0
        }
218
0
219
0
        // Process all of the stages until their read indices reach the input buffer's write index
220
0
        int writeIndex = m_inputBuffer.writeIndex();
221
0
222
0
        // Even though it doesn't seem like every stage needs to maintain its own version of readIndex
223
0
        // we do this in case we want to run in more than one background thread.
224
0
        int readIndex;
225
0
226
0
        while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun...
227
0
            // Accumulate contributions from each stage
228
0
            for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
229
0
                m_backgroundStages[i]->processInBackground(this);
230
0
        }
231
0
    }
232
0
}
233
234
void ReverbConvolver::process(const float* sourceChannelData,
235
                              float* destinationChannelData)
236
0
{
237
0
    const float* source = sourceChannelData;
238
0
    float* destination = destinationChannelData;
239
0
    bool isDataSafe = source && destination;
240
0
    MOZ_ASSERT(isDataSafe);
241
0
    if (!isDataSafe)
242
0
        return;
243
0
244
0
    // Feed input buffer (read by all threads)
245
0
    m_inputBuffer.write(source, WEBAUDIO_BLOCK_SIZE);
246
0
247
0
    // Accumulate contributions from each stage
248
0
    for (size_t i = 0; i < m_stages.Length(); ++i)
249
0
        m_stages[i]->process(source);
250
0
251
0
    // Finally read from accumulation buffer
252
0
    m_accumulationBuffer.readAndClear(destination, WEBAUDIO_BLOCK_SIZE);
253
0
254
0
    // Now that we've buffered more input, wake up our background thread.
255
0
256
0
    // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time
257
0
    // thread where it is a disaster for the lock to be contended (causes audio glitching).  It's OK if we fail to
258
0
    // signal from time to time, since we'll get to it the next time we're called.  We're called repeatedly
259
0
    // and frequently (around every 3ms).  The background thread is processing well into the future and has a considerable amount of
260
0
    // leeway here...
261
0
    if (m_backgroundThreadLock.Try()) {
262
0
        m_moreInputBuffered = true;
263
0
        m_backgroundThreadCondition.Signal();
264
0
        m_backgroundThreadLock.Release();
265
0
    }
266
0
}
267
268
} // namespace WebCore