/src/mozilla-central/dom/media/webaudio/DelayBuffer.cpp
Line | Count | Source (jump to first uncovered line) |
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 "DelayBuffer.h" |
8 | | |
9 | | #include "mozilla/PodOperations.h" |
10 | | #include "AudioChannelFormat.h" |
11 | | #include "AudioNodeEngine.h" |
12 | | |
13 | | namespace mozilla { |
14 | | |
15 | | size_t |
16 | | DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
17 | 0 | { |
18 | 0 | size_t amount = 0; |
19 | 0 | amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf); |
20 | 0 | for (size_t i = 0; i < mChunks.Length(); i++) { |
21 | 0 | amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false); |
22 | 0 | } |
23 | 0 |
|
24 | 0 | amount += mUpmixChannels.ShallowSizeOfExcludingThis(aMallocSizeOf); |
25 | 0 | return amount; |
26 | 0 | } |
27 | | |
28 | | void |
29 | | DelayBuffer::Write(const AudioBlock& aInputChunk) |
30 | 0 | { |
31 | 0 | // We must have a reference to the buffer if there are channels |
32 | 0 | MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.ChannelCount()); |
33 | | #ifdef DEBUG |
34 | | MOZ_ASSERT(!mHaveWrittenBlock); |
35 | | mHaveWrittenBlock = true; |
36 | | #endif |
37 | |
|
38 | 0 | if (!EnsureBuffer()) { |
39 | 0 | return; |
40 | 0 | } |
41 | 0 | |
42 | 0 | if (mCurrentChunk == mLastReadChunk) { |
43 | 0 | mLastReadChunk = -1; // invalidate cache |
44 | 0 | } |
45 | 0 | mChunks[mCurrentChunk] = aInputChunk.AsAudioChunk(); |
46 | 0 | } |
47 | | |
48 | | void |
49 | | DelayBuffer::Read(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], |
50 | | AudioBlock* aOutputChunk, |
51 | | ChannelInterpretation aChannelInterpretation) |
52 | 0 | { |
53 | 0 | int chunkCount = mChunks.Length(); |
54 | 0 | if (!chunkCount) { |
55 | 0 | aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); |
56 | 0 | return; |
57 | 0 | } |
58 | 0 | |
59 | 0 | // Find the maximum number of contributing channels to determine the output |
60 | 0 | // channel count that retains all signal information. Buffered blocks will |
61 | 0 | // be upmixed if necessary. |
62 | 0 | // |
63 | 0 | // First find the range of "delay" offsets backwards from the current |
64 | 0 | // position. Note that these may be negative for frames that are after the |
65 | 0 | // current position (including i). |
66 | 0 | float minDelay = aPerFrameDelays[0]; |
67 | 0 | float maxDelay = minDelay; |
68 | 0 | for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) { |
69 | 0 | minDelay = std::min(minDelay, aPerFrameDelays[i] - i); |
70 | 0 | maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i); |
71 | 0 | } |
72 | 0 |
|
73 | 0 | // Now find the chunks touched by this range and check their channel counts. |
74 | 0 | int oldestChunk = ChunkForDelay(std::ceil(maxDelay)); |
75 | 0 | int youngestChunk = ChunkForDelay(std::floor(minDelay)); |
76 | 0 |
|
77 | 0 | uint32_t channelCount = 0; |
78 | 0 | for (int i = oldestChunk; true; i = (i + 1) % chunkCount) { |
79 | 0 | channelCount = GetAudioChannelsSuperset(channelCount, |
80 | 0 | mChunks[i].ChannelCount()); |
81 | 0 | if (i == youngestChunk) { |
82 | 0 | break; |
83 | 0 | } |
84 | 0 | } |
85 | 0 |
|
86 | 0 | if (channelCount) { |
87 | 0 | aOutputChunk->AllocateChannels(channelCount); |
88 | 0 | ReadChannels(aPerFrameDelays, aOutputChunk, |
89 | 0 | 0, channelCount, aChannelInterpretation); |
90 | 0 | } else { |
91 | 0 | aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | void |
96 | | DelayBuffer::ReadChannel(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], |
97 | | AudioBlock* aOutputChunk, uint32_t aChannel, |
98 | | ChannelInterpretation aChannelInterpretation) |
99 | 0 | { |
100 | 0 | if (!mChunks.Length()) { |
101 | 0 | float* outputChannel = aOutputChunk->ChannelFloatsForWrite(aChannel); |
102 | 0 | PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE); |
103 | 0 | return; |
104 | 0 | } |
105 | 0 | |
106 | 0 | ReadChannels(aPerFrameDelays, aOutputChunk, |
107 | 0 | aChannel, 1, aChannelInterpretation); |
108 | 0 | } |
109 | | |
110 | | void |
111 | | DelayBuffer::ReadChannels(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], |
112 | | AudioBlock* aOutputChunk, |
113 | | uint32_t aFirstChannel, uint32_t aNumChannelsToRead, |
114 | | ChannelInterpretation aChannelInterpretation) |
115 | 0 | { |
116 | 0 | uint32_t totalChannelCount = aOutputChunk->ChannelCount(); |
117 | 0 | uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead; |
118 | 0 | MOZ_ASSERT(readChannelsEnd <= totalChannelCount); |
119 | 0 |
|
120 | 0 | if (mUpmixChannels.Length() != totalChannelCount) { |
121 | 0 | mLastReadChunk = -1; // invalidate cache |
122 | 0 | } |
123 | 0 |
|
124 | 0 | for (uint32_t channel = aFirstChannel; |
125 | 0 | channel < readChannelsEnd; ++channel) { |
126 | 0 | PodZero(aOutputChunk->ChannelFloatsForWrite(channel), WEBAUDIO_BLOCK_SIZE); |
127 | 0 | } |
128 | 0 |
|
129 | 0 | for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { |
130 | 0 | float currentDelay = aPerFrameDelays[i]; |
131 | 0 | MOZ_ASSERT(currentDelay >= 0.0f); |
132 | 0 | MOZ_ASSERT(currentDelay <= (mChunks.Length() - 1) * WEBAUDIO_BLOCK_SIZE); |
133 | 0 |
|
134 | 0 | // Interpolate two input frames in case the read position does not match |
135 | 0 | // an integer index. |
136 | 0 | // Use the larger delay, for the older frame, first, as this is more |
137 | 0 | // likely to use the cached upmixed channel arrays. |
138 | 0 | int floorDelay = int(currentDelay); |
139 | 0 | float interpolationFactor = currentDelay - floorDelay; |
140 | 0 | int positions[2]; |
141 | 0 | positions[1] = PositionForDelay(floorDelay) + i; |
142 | 0 | positions[0] = positions[1] - 1; |
143 | 0 |
|
144 | 0 | for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) { |
145 | 0 | int readChunk = ChunkForPosition(positions[tick]); |
146 | 0 | // The zero check on interpolationFactor is important because, when |
147 | 0 | // currentDelay is integer, positions[0] may be outside the range |
148 | 0 | // considered for determining totalChannelCount. |
149 | 0 | // mVolume is not set on default initialized chunks so also handle null |
150 | 0 | // chunks specially. |
151 | 0 | if (interpolationFactor != 0.0f && !mChunks[readChunk].IsNull()) { |
152 | 0 | int readOffset = OffsetForPosition(positions[tick]); |
153 | 0 | UpdateUpmixChannels(readChunk, totalChannelCount, |
154 | 0 | aChannelInterpretation); |
155 | 0 | float multiplier = interpolationFactor * mChunks[readChunk].mVolume; |
156 | 0 | for (uint32_t channel = aFirstChannel; |
157 | 0 | channel < readChannelsEnd; ++channel) { |
158 | 0 | aOutputChunk->ChannelFloatsForWrite(channel)[i] += multiplier * |
159 | 0 | mUpmixChannels[channel][readOffset]; |
160 | 0 | } |
161 | 0 | } |
162 | 0 |
|
163 | 0 | interpolationFactor = 1.0f - interpolationFactor; |
164 | 0 | } |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | | void |
169 | | DelayBuffer::Read(float aDelayTicks, AudioBlock* aOutputChunk, |
170 | | ChannelInterpretation aChannelInterpretation) |
171 | 0 | { |
172 | 0 | float computedDelay[WEBAUDIO_BLOCK_SIZE]; |
173 | 0 |
|
174 | 0 | for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { |
175 | 0 | computedDelay[i] = aDelayTicks; |
176 | 0 | } |
177 | 0 |
|
178 | 0 | Read(computedDelay, aOutputChunk, aChannelInterpretation); |
179 | 0 | } |
180 | | |
181 | | bool |
182 | | DelayBuffer::EnsureBuffer() |
183 | 0 | { |
184 | 0 | if (mChunks.Length() == 0) { |
185 | 0 | // The length of the buffer is at least one block greater than the maximum |
186 | 0 | // delay so that writing an input block does not overwrite the block that |
187 | 0 | // would subsequently be read at maximum delay. Also round up to the next |
188 | 0 | // block size, so that no block of writes will need to wrap. |
189 | 0 | const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >> |
190 | 0 | WEBAUDIO_BLOCK_SIZE_BITS; |
191 | 0 | if (!mChunks.SetLength(chunkCount, fallible)) { |
192 | 0 | return false; |
193 | 0 | } |
194 | 0 | |
195 | 0 | mLastReadChunk = -1; |
196 | 0 | } |
197 | 0 | return true; |
198 | 0 | } |
199 | | |
200 | | int |
201 | 0 | DelayBuffer::PositionForDelay(int aDelay) { |
202 | 0 | // Adding mChunks.Length() keeps integers positive for defined and |
203 | 0 | // appropriate bitshift, remainder, and bitwise operations. |
204 | 0 | return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay; |
205 | 0 | } |
206 | | |
207 | | int |
208 | | DelayBuffer::ChunkForPosition(int aPosition) |
209 | 0 | { |
210 | 0 | MOZ_ASSERT(aPosition >= 0); |
211 | 0 | return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length(); |
212 | 0 | } |
213 | | |
214 | | int |
215 | | DelayBuffer::OffsetForPosition(int aPosition) |
216 | 0 | { |
217 | 0 | MOZ_ASSERT(aPosition >= 0); |
218 | 0 | return aPosition & (WEBAUDIO_BLOCK_SIZE - 1); |
219 | 0 | } |
220 | | |
221 | | int |
222 | | DelayBuffer::ChunkForDelay(int aDelay) |
223 | 0 | { |
224 | 0 | return ChunkForPosition(PositionForDelay(aDelay)); |
225 | 0 | } |
226 | | |
227 | | void |
228 | | DelayBuffer::UpdateUpmixChannels(int aNewReadChunk, uint32_t aChannelCount, |
229 | | ChannelInterpretation aChannelInterpretation) |
230 | 0 | { |
231 | 0 | if (aNewReadChunk == mLastReadChunk) { |
232 | 0 | MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount); |
233 | 0 | return; |
234 | 0 | } |
235 | 0 |
|
236 | 0 | NS_WARNING_ASSERTION(mHaveWrittenBlock || aNewReadChunk != mCurrentChunk, |
237 | 0 | "Smoothing is making feedback delay too small."); |
238 | 0 |
|
239 | 0 | mLastReadChunk = aNewReadChunk; |
240 | 0 | mUpmixChannels = mChunks[aNewReadChunk].ChannelData<float>(); |
241 | 0 | MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount); |
242 | 0 | if (mUpmixChannels.Length() < aChannelCount) { |
243 | 0 | if (aChannelInterpretation == ChannelInterpretation::Speakers) { |
244 | 0 | AudioChannelsUpMix(&mUpmixChannels, |
245 | 0 | aChannelCount, SilentChannel::ZeroChannel<float>()); |
246 | 0 | MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount, |
247 | 0 | "We called GetAudioChannelsSuperset to avoid this"); |
248 | 0 | } else { |
249 | 0 | // Fill up the remaining channels with zeros |
250 | 0 | for (uint32_t channel = mUpmixChannels.Length(); |
251 | 0 | channel < aChannelCount; ++channel) { |
252 | 0 | mUpmixChannels.AppendElement(SilentChannel::ZeroChannel<float>()); |
253 | 0 | } |
254 | 0 | } |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | | } // namespace mozilla |