1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/DelayBuffer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,269 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "DelayBuffer.h" 1.11 + 1.12 +#include "mozilla/PodOperations.h" 1.13 +#include "AudioChannelFormat.h" 1.14 +#include "AudioNodeEngine.h" 1.15 + 1.16 +namespace mozilla { 1.17 + 1.18 +size_t 1.19 +DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.20 +{ 1.21 + size_t amount = 0; 1.22 + amount += mChunks.SizeOfExcludingThis(aMallocSizeOf); 1.23 + for (size_t i = 0; i < mChunks.Length(); i++) { 1.24 + amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false); 1.25 + } 1.26 + 1.27 + amount += mUpmixChannels.SizeOfExcludingThis(aMallocSizeOf); 1.28 + return amount; 1.29 +} 1.30 + 1.31 +void 1.32 +DelayBuffer::Write(const AudioChunk& aInputChunk) 1.33 +{ 1.34 + // We must have a reference to the buffer if there are channels 1.35 + MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.mChannelData.Length()); 1.36 +#ifdef DEBUG 1.37 + MOZ_ASSERT(!mHaveWrittenBlock); 1.38 + mHaveWrittenBlock = true; 1.39 +#endif 1.40 + 1.41 + if (!EnsureBuffer()) { 1.42 + return; 1.43 + } 1.44 + 1.45 + if (mCurrentChunk == mLastReadChunk) { 1.46 + mLastReadChunk = -1; // invalidate cache 1.47 + } 1.48 + mChunks[mCurrentChunk] = aInputChunk; 1.49 +} 1.50 + 1.51 +void 1.52 +DelayBuffer::Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], 1.53 + AudioChunk* aOutputChunk, 1.54 + ChannelInterpretation aChannelInterpretation) 1.55 +{ 1.56 + int chunkCount = mChunks.Length(); 1.57 + if (!chunkCount) { 1.58 + aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); 1.59 + return; 1.60 + } 1.61 + 1.62 + // Find the maximum number of contributing channels to determine the output 1.63 + // channel count that retains all signal information. Buffered blocks will 1.64 + // be upmixed if necessary. 1.65 + // 1.66 + // First find the range of "delay" offsets backwards from the current 1.67 + // position. Note that these may be negative for frames that are after the 1.68 + // current position (including i). 1.69 + double minDelay = aPerFrameDelays[0]; 1.70 + double maxDelay = minDelay; 1.71 + for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) { 1.72 + minDelay = std::min(minDelay, aPerFrameDelays[i] - i); 1.73 + maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i); 1.74 + } 1.75 + 1.76 + // Now find the chunks touched by this range and check their channel counts. 1.77 + int oldestChunk = ChunkForDelay(int(maxDelay) + 1); 1.78 + int youngestChunk = ChunkForDelay(minDelay); 1.79 + 1.80 + uint32_t channelCount = 0; 1.81 + for (int i = oldestChunk; true; i = (i + 1) % chunkCount) { 1.82 + channelCount = GetAudioChannelsSuperset(channelCount, 1.83 + mChunks[i].ChannelCount()); 1.84 + if (i == youngestChunk) { 1.85 + break; 1.86 + } 1.87 + } 1.88 + 1.89 + if (channelCount) { 1.90 + AllocateAudioBlock(channelCount, aOutputChunk); 1.91 + ReadChannels(aPerFrameDelays, aOutputChunk, 1.92 + 0, channelCount, aChannelInterpretation); 1.93 + } else { 1.94 + aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); 1.95 + } 1.96 + 1.97 + // Remember currentDelayFrames for the next ProcessBlock call 1.98 + mCurrentDelay = aPerFrameDelays[WEBAUDIO_BLOCK_SIZE - 1]; 1.99 +} 1.100 + 1.101 +void 1.102 +DelayBuffer::ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], 1.103 + const AudioChunk* aOutputChunk, uint32_t aChannel, 1.104 + ChannelInterpretation aChannelInterpretation) 1.105 +{ 1.106 + if (!mChunks.Length()) { 1.107 + float* outputChannel = static_cast<float*> 1.108 + (const_cast<void*>(aOutputChunk->mChannelData[aChannel])); 1.109 + PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE); 1.110 + return; 1.111 + } 1.112 + 1.113 + ReadChannels(aPerFrameDelays, aOutputChunk, 1.114 + aChannel, 1, aChannelInterpretation); 1.115 +} 1.116 + 1.117 +void 1.118 +DelayBuffer::ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], 1.119 + const AudioChunk* aOutputChunk, 1.120 + uint32_t aFirstChannel, uint32_t aNumChannelsToRead, 1.121 + ChannelInterpretation aChannelInterpretation) 1.122 +{ 1.123 + uint32_t totalChannelCount = aOutputChunk->mChannelData.Length(); 1.124 + uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead; 1.125 + MOZ_ASSERT(readChannelsEnd <= totalChannelCount); 1.126 + 1.127 + if (mUpmixChannels.Length() != totalChannelCount) { 1.128 + mLastReadChunk = -1; // invalidate cache 1.129 + } 1.130 + 1.131 + float* const* outputChannels = reinterpret_cast<float* const*> 1.132 + (const_cast<void* const*>(aOutputChunk->mChannelData.Elements())); 1.133 + for (uint32_t channel = aFirstChannel; 1.134 + channel < readChannelsEnd; ++channel) { 1.135 + PodZero(outputChannels[channel], WEBAUDIO_BLOCK_SIZE); 1.136 + } 1.137 + 1.138 + for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { 1.139 + double currentDelay = aPerFrameDelays[i]; 1.140 + MOZ_ASSERT(currentDelay >= 0.0); 1.141 + MOZ_ASSERT(currentDelay <= (mChunks.Length() - 1) * WEBAUDIO_BLOCK_SIZE); 1.142 + 1.143 + // Interpolate two input frames in case the read position does not match 1.144 + // an integer index. 1.145 + // Use the larger delay, for the older frame, first, as this is more 1.146 + // likely to use the cached upmixed channel arrays. 1.147 + int floorDelay = int(currentDelay); 1.148 + double interpolationFactor = currentDelay - floorDelay; 1.149 + int positions[2]; 1.150 + positions[1] = PositionForDelay(floorDelay) + i; 1.151 + positions[0] = positions[1] - 1; 1.152 + 1.153 + for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) { 1.154 + int readChunk = ChunkForPosition(positions[tick]); 1.155 + // mVolume is not set on default initialized chunks so handle null 1.156 + // chunks specially. 1.157 + if (!mChunks[readChunk].IsNull()) { 1.158 + int readOffset = OffsetForPosition(positions[tick]); 1.159 + UpdateUpmixChannels(readChunk, totalChannelCount, 1.160 + aChannelInterpretation); 1.161 + double multiplier = interpolationFactor * mChunks[readChunk].mVolume; 1.162 + for (uint32_t channel = aFirstChannel; 1.163 + channel < readChannelsEnd; ++channel) { 1.164 + outputChannels[channel][i] += multiplier * 1.165 + static_cast<const float*>(mUpmixChannels[channel])[readOffset]; 1.166 + } 1.167 + } 1.168 + 1.169 + interpolationFactor = 1.0 - interpolationFactor; 1.170 + } 1.171 + } 1.172 +} 1.173 + 1.174 +void 1.175 +DelayBuffer::Read(double aDelayTicks, AudioChunk* aOutputChunk, 1.176 + ChannelInterpretation aChannelInterpretation) 1.177 +{ 1.178 + const bool firstTime = mCurrentDelay < 0.0; 1.179 + double currentDelay = firstTime ? aDelayTicks : mCurrentDelay; 1.180 + 1.181 + double computedDelay[WEBAUDIO_BLOCK_SIZE]; 1.182 + 1.183 + for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { 1.184 + // If the value has changed, smoothly approach it 1.185 + currentDelay += (aDelayTicks - currentDelay) * mSmoothingRate; 1.186 + computedDelay[i] = currentDelay; 1.187 + } 1.188 + 1.189 + Read(computedDelay, aOutputChunk, aChannelInterpretation); 1.190 +} 1.191 + 1.192 +bool 1.193 +DelayBuffer::EnsureBuffer() 1.194 +{ 1.195 + if (mChunks.Length() == 0) { 1.196 + // The length of the buffer is at least one block greater than the maximum 1.197 + // delay so that writing an input block does not overwrite the block that 1.198 + // would subsequently be read at maximum delay. Also round up to the next 1.199 + // block size, so that no block of writes will need to wrap. 1.200 + const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >> 1.201 + WEBAUDIO_BLOCK_SIZE_BITS; 1.202 + if (!mChunks.SetLength(chunkCount)) { 1.203 + return false; 1.204 + } 1.205 + 1.206 + mLastReadChunk = -1; 1.207 + } 1.208 + return true; 1.209 +} 1.210 + 1.211 +int 1.212 +DelayBuffer::PositionForDelay(int aDelay) { 1.213 + // Adding mChunks.Length() keeps integers positive for defined and 1.214 + // appropriate bitshift, remainder, and bitwise operations. 1.215 + return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay; 1.216 +} 1.217 + 1.218 +int 1.219 +DelayBuffer::ChunkForPosition(int aPosition) 1.220 +{ 1.221 + MOZ_ASSERT(aPosition >= 0); 1.222 + return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length(); 1.223 +} 1.224 + 1.225 +int 1.226 +DelayBuffer::OffsetForPosition(int aPosition) 1.227 +{ 1.228 + MOZ_ASSERT(aPosition >= 0); 1.229 + return aPosition & (WEBAUDIO_BLOCK_SIZE - 1); 1.230 +} 1.231 + 1.232 +int 1.233 +DelayBuffer::ChunkForDelay(int aDelay) 1.234 +{ 1.235 + return ChunkForPosition(PositionForDelay(aDelay)); 1.236 +} 1.237 + 1.238 +void 1.239 +DelayBuffer::UpdateUpmixChannels(int aNewReadChunk, uint32_t aChannelCount, 1.240 + ChannelInterpretation aChannelInterpretation) 1.241 +{ 1.242 + if (aNewReadChunk == mLastReadChunk) { 1.243 + MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount); 1.244 + return; 1.245 + } 1.246 + 1.247 + static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {}; 1.248 + 1.249 + NS_WARN_IF_FALSE(mHaveWrittenBlock || aNewReadChunk != mCurrentChunk, 1.250 + "Smoothing is making feedback delay too small."); 1.251 + 1.252 + mLastReadChunk = aNewReadChunk; 1.253 + // Missing assignment operator is bug 976927 1.254 + mUpmixChannels.ReplaceElementsAt(0, mUpmixChannels.Length(), 1.255 + mChunks[aNewReadChunk].mChannelData); 1.256 + MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount); 1.257 + if (mUpmixChannels.Length() < aChannelCount) { 1.258 + if (aChannelInterpretation == ChannelInterpretation::Speakers) { 1.259 + AudioChannelsUpMix(&mUpmixChannels, aChannelCount, silenceChannel); 1.260 + MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount, 1.261 + "We called GetAudioChannelsSuperset to avoid this"); 1.262 + } else { 1.263 + // Fill up the remaining channels with zeros 1.264 + for (uint32_t channel = mUpmixChannels.Length(); 1.265 + channel < aChannelCount; ++channel) { 1.266 + mUpmixChannels.AppendElement(silenceChannel); 1.267 + } 1.268 + } 1.269 + } 1.270 +} 1.271 + 1.272 +} // mozilla