Tue, 06 Jan 2015 21:39:09 +0100
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.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2011 Google Inc. All rights reserved. |
michael@0 | 3 | * |
michael@0 | 4 | * Redistribution and use in source and binary forms, with or without |
michael@0 | 5 | * modification, are permitted provided that the following conditions |
michael@0 | 6 | * are met: |
michael@0 | 7 | * |
michael@0 | 8 | * 1. Redistributions of source code must retain the above copyright |
michael@0 | 9 | * notice, this list of conditions and the following disclaimer. |
michael@0 | 10 | * 2. Redistributions in binary form must reproduce the above copyright |
michael@0 | 11 | * notice, this list of conditions and the following disclaimer in the |
michael@0 | 12 | * documentation and/or other materials provided with the distribution. |
michael@0 | 13 | * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
michael@0 | 14 | * its contributors may be used to endorse or promote products derived |
michael@0 | 15 | * from this software without specific prior written permission. |
michael@0 | 16 | * |
michael@0 | 17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
michael@0 | 18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
michael@0 | 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
michael@0 | 20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
michael@0 | 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
michael@0 | 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
michael@0 | 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
michael@0 | 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
michael@0 | 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 27 | */ |
michael@0 | 28 | |
michael@0 | 29 | #include "DynamicsCompressor.h" |
michael@0 | 30 | #include "AudioSegment.h" |
michael@0 | 31 | |
michael@0 | 32 | #include <cmath> |
michael@0 | 33 | #include "AudioNodeEngine.h" |
michael@0 | 34 | #include "nsDebug.h" |
michael@0 | 35 | |
michael@0 | 36 | using mozilla::WEBAUDIO_BLOCK_SIZE; |
michael@0 | 37 | using mozilla::AudioBlockCopyChannelWithScale; |
michael@0 | 38 | |
michael@0 | 39 | namespace WebCore { |
michael@0 | 40 | |
michael@0 | 41 | DynamicsCompressor::DynamicsCompressor(float sampleRate, unsigned numberOfChannels) |
michael@0 | 42 | : m_numberOfChannels(numberOfChannels) |
michael@0 | 43 | , m_sampleRate(sampleRate) |
michael@0 | 44 | , m_compressor(sampleRate, numberOfChannels) |
michael@0 | 45 | { |
michael@0 | 46 | // Uninitialized state - for parameter recalculation. |
michael@0 | 47 | m_lastFilterStageRatio = -1; |
michael@0 | 48 | m_lastAnchor = -1; |
michael@0 | 49 | m_lastFilterStageGain = -1; |
michael@0 | 50 | |
michael@0 | 51 | setNumberOfChannels(numberOfChannels); |
michael@0 | 52 | initializeParameters(); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
michael@0 | 56 | { |
michael@0 | 57 | size_t amount = aMallocSizeOf(this); |
michael@0 | 58 | amount += m_preFilterPacks.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 59 | for (size_t i = 0; i < m_preFilterPacks.Length(); i++) { |
michael@0 | 60 | if (m_preFilterPacks[i]) { |
michael@0 | 61 | amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 62 | } |
michael@0 | 63 | } |
michael@0 | 64 | |
michael@0 | 65 | amount += m_postFilterPacks.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 66 | for (size_t i = 0; i < m_postFilterPacks.Length(); i++) { |
michael@0 | 67 | if (m_postFilterPacks[i]) { |
michael@0 | 68 | amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 69 | } |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | amount += m_sourceChannels.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 73 | amount += m_destinationChannels.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 74 | amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 75 | return amount; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | void DynamicsCompressor::setParameterValue(unsigned parameterID, float value) |
michael@0 | 79 | { |
michael@0 | 80 | MOZ_ASSERT(parameterID < ParamLast); |
michael@0 | 81 | if (parameterID < ParamLast) |
michael@0 | 82 | m_parameters[parameterID] = value; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | void DynamicsCompressor::initializeParameters() |
michael@0 | 86 | { |
michael@0 | 87 | // Initializes compressor to default values. |
michael@0 | 88 | |
michael@0 | 89 | m_parameters[ParamThreshold] = -24; // dB |
michael@0 | 90 | m_parameters[ParamKnee] = 30; // dB |
michael@0 | 91 | m_parameters[ParamRatio] = 12; // unit-less |
michael@0 | 92 | m_parameters[ParamAttack] = 0.003f; // seconds |
michael@0 | 93 | m_parameters[ParamRelease] = 0.250f; // seconds |
michael@0 | 94 | m_parameters[ParamPreDelay] = 0.006f; // seconds |
michael@0 | 95 | |
michael@0 | 96 | // Release zone values 0 -> 1. |
michael@0 | 97 | m_parameters[ParamReleaseZone1] = 0.09f; |
michael@0 | 98 | m_parameters[ParamReleaseZone2] = 0.16f; |
michael@0 | 99 | m_parameters[ParamReleaseZone3] = 0.42f; |
michael@0 | 100 | m_parameters[ParamReleaseZone4] = 0.98f; |
michael@0 | 101 | |
michael@0 | 102 | m_parameters[ParamFilterStageGain] = 4.4f; // dB |
michael@0 | 103 | m_parameters[ParamFilterStageRatio] = 2; |
michael@0 | 104 | m_parameters[ParamFilterAnchor] = 15000 / nyquist(); |
michael@0 | 105 | |
michael@0 | 106 | m_parameters[ParamPostGain] = 0; // dB |
michael@0 | 107 | m_parameters[ParamReduction] = 0; // dB |
michael@0 | 108 | |
michael@0 | 109 | // Linear crossfade (0 -> 1). |
michael@0 | 110 | m_parameters[ParamEffectBlend] = 1; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | float DynamicsCompressor::parameterValue(unsigned parameterID) |
michael@0 | 114 | { |
michael@0 | 115 | MOZ_ASSERT(parameterID < ParamLast); |
michael@0 | 116 | return m_parameters[parameterID]; |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | void DynamicsCompressor::setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */) |
michael@0 | 120 | { |
michael@0 | 121 | float gk = 1 - gain / 20; |
michael@0 | 122 | float f1 = normalizedFrequency * gk; |
michael@0 | 123 | float f2 = normalizedFrequency / gk; |
michael@0 | 124 | float r1 = expf(-f1 * M_PI); |
michael@0 | 125 | float r2 = expf(-f2 * M_PI); |
michael@0 | 126 | |
michael@0 | 127 | MOZ_ASSERT(m_numberOfChannels == m_preFilterPacks.Length()); |
michael@0 | 128 | |
michael@0 | 129 | for (unsigned i = 0; i < m_numberOfChannels; ++i) { |
michael@0 | 130 | // Set pre-filter zero and pole to create an emphasis filter. |
michael@0 | 131 | ZeroPole& preFilter = m_preFilterPacks[i]->filters[stageIndex]; |
michael@0 | 132 | preFilter.setZero(r1); |
michael@0 | 133 | preFilter.setPole(r2); |
michael@0 | 134 | |
michael@0 | 135 | // Set post-filter with zero and pole reversed to create the de-emphasis filter. |
michael@0 | 136 | // If there were no compressor kernel in between, they would cancel each other out (allpass filter). |
michael@0 | 137 | ZeroPole& postFilter = m_postFilterPacks[i]->filters[stageIndex]; |
michael@0 | 138 | postFilter.setZero(r2); |
michael@0 | 139 | postFilter.setPole(r1); |
michael@0 | 140 | } |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio) |
michael@0 | 144 | { |
michael@0 | 145 | setEmphasisStageParameters(0, gain, anchorFreq); |
michael@0 | 146 | setEmphasisStageParameters(1, gain, anchorFreq / filterStageRatio); |
michael@0 | 147 | setEmphasisStageParameters(2, gain, anchorFreq / (filterStageRatio * filterStageRatio)); |
michael@0 | 148 | setEmphasisStageParameters(3, gain, anchorFreq / (filterStageRatio * filterStageRatio * filterStageRatio)); |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | void DynamicsCompressor::process(const AudioChunk* sourceChunk, AudioChunk* destinationChunk, unsigned framesToProcess) |
michael@0 | 152 | { |
michael@0 | 153 | // Though numberOfChannels is retrived from destinationBus, we still name it numberOfChannels instead of numberOfDestinationChannels. |
michael@0 | 154 | // It's because we internally match sourceChannels's size to destinationBus by channel up/down mix. Thus we need numberOfChannels |
michael@0 | 155 | // to do the loop work for both m_sourceChannels and m_destinationChannels. |
michael@0 | 156 | |
michael@0 | 157 | unsigned numberOfChannels = destinationChunk->mChannelData.Length(); |
michael@0 | 158 | unsigned numberOfSourceChannels = sourceChunk->mChannelData.Length(); |
michael@0 | 159 | |
michael@0 | 160 | MOZ_ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels); |
michael@0 | 161 | |
michael@0 | 162 | if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) { |
michael@0 | 163 | destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE); |
michael@0 | 164 | return; |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | switch (numberOfChannels) { |
michael@0 | 168 | case 2: // stereo |
michael@0 | 169 | m_sourceChannels[0] = static_cast<const float*>(sourceChunk->mChannelData[0]); |
michael@0 | 170 | |
michael@0 | 171 | if (numberOfSourceChannels > 1) |
michael@0 | 172 | m_sourceChannels[1] = static_cast<const float*>(sourceChunk->mChannelData[1]); |
michael@0 | 173 | else |
michael@0 | 174 | // Simply duplicate mono channel input data to right channel for stereo processing. |
michael@0 | 175 | m_sourceChannels[1] = m_sourceChannels[0]; |
michael@0 | 176 | |
michael@0 | 177 | break; |
michael@0 | 178 | default: |
michael@0 | 179 | // FIXME : support other number of channels. |
michael@0 | 180 | NS_WARNING("Support other number of channels"); |
michael@0 | 181 | destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE); |
michael@0 | 182 | return; |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | for (unsigned i = 0; i < numberOfChannels; ++i) |
michael@0 | 186 | m_destinationChannels[i] = const_cast<float*>(static_cast<const float*>( |
michael@0 | 187 | destinationChunk->mChannelData[i])); |
michael@0 | 188 | |
michael@0 | 189 | float filterStageGain = parameterValue(ParamFilterStageGain); |
michael@0 | 190 | float filterStageRatio = parameterValue(ParamFilterStageRatio); |
michael@0 | 191 | float anchor = parameterValue(ParamFilterAnchor); |
michael@0 | 192 | |
michael@0 | 193 | if (filterStageGain != m_lastFilterStageGain || filterStageRatio != m_lastFilterStageRatio || anchor != m_lastAnchor) { |
michael@0 | 194 | m_lastFilterStageGain = filterStageGain; |
michael@0 | 195 | m_lastFilterStageRatio = filterStageRatio; |
michael@0 | 196 | m_lastAnchor = anchor; |
michael@0 | 197 | |
michael@0 | 198 | setEmphasisParameters(filterStageGain, anchor, filterStageRatio); |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | float sourceWithVolume[WEBAUDIO_BLOCK_SIZE]; |
michael@0 | 202 | |
michael@0 | 203 | // Apply pre-emphasis filter. |
michael@0 | 204 | // Note that the final three stages are computed in-place in the destination buffer. |
michael@0 | 205 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
michael@0 | 206 | const float* sourceData; |
michael@0 | 207 | if (sourceChunk->mVolume == 1.0f) { |
michael@0 | 208 | // Fast path, the volume scale doesn't need to get taken into account |
michael@0 | 209 | sourceData = m_sourceChannels[i]; |
michael@0 | 210 | } else { |
michael@0 | 211 | AudioBlockCopyChannelWithScale(m_sourceChannels[i], |
michael@0 | 212 | sourceChunk->mVolume, |
michael@0 | 213 | sourceWithVolume); |
michael@0 | 214 | sourceData = sourceWithVolume; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | float* destinationData = m_destinationChannels[i]; |
michael@0 | 218 | ZeroPole* preFilters = m_preFilterPacks[i]->filters; |
michael@0 | 219 | |
michael@0 | 220 | preFilters[0].process(sourceData, destinationData, framesToProcess); |
michael@0 | 221 | preFilters[1].process(destinationData, destinationData, framesToProcess); |
michael@0 | 222 | preFilters[2].process(destinationData, destinationData, framesToProcess); |
michael@0 | 223 | preFilters[3].process(destinationData, destinationData, framesToProcess); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | float dbThreshold = parameterValue(ParamThreshold); |
michael@0 | 227 | float dbKnee = parameterValue(ParamKnee); |
michael@0 | 228 | float ratio = parameterValue(ParamRatio); |
michael@0 | 229 | float attackTime = parameterValue(ParamAttack); |
michael@0 | 230 | float releaseTime = parameterValue(ParamRelease); |
michael@0 | 231 | float preDelayTime = parameterValue(ParamPreDelay); |
michael@0 | 232 | |
michael@0 | 233 | // This is effectively a master volume on the compressed signal (pre-blending). |
michael@0 | 234 | float dbPostGain = parameterValue(ParamPostGain); |
michael@0 | 235 | |
michael@0 | 236 | // Linear blending value from dry to completely processed (0 -> 1) |
michael@0 | 237 | // 0 means the signal is completely unprocessed. |
michael@0 | 238 | // 1 mixes in only the compressed signal. |
michael@0 | 239 | float effectBlend = parameterValue(ParamEffectBlend); |
michael@0 | 240 | |
michael@0 | 241 | float releaseZone1 = parameterValue(ParamReleaseZone1); |
michael@0 | 242 | float releaseZone2 = parameterValue(ParamReleaseZone2); |
michael@0 | 243 | float releaseZone3 = parameterValue(ParamReleaseZone3); |
michael@0 | 244 | float releaseZone4 = parameterValue(ParamReleaseZone4); |
michael@0 | 245 | |
michael@0 | 246 | // Apply compression to the pre-filtered signal. |
michael@0 | 247 | // The processing is performed in place. |
michael@0 | 248 | m_compressor.process(m_destinationChannels.get(), |
michael@0 | 249 | m_destinationChannels.get(), |
michael@0 | 250 | numberOfChannels, |
michael@0 | 251 | framesToProcess, |
michael@0 | 252 | |
michael@0 | 253 | dbThreshold, |
michael@0 | 254 | dbKnee, |
michael@0 | 255 | ratio, |
michael@0 | 256 | attackTime, |
michael@0 | 257 | releaseTime, |
michael@0 | 258 | preDelayTime, |
michael@0 | 259 | dbPostGain, |
michael@0 | 260 | effectBlend, |
michael@0 | 261 | |
michael@0 | 262 | releaseZone1, |
michael@0 | 263 | releaseZone2, |
michael@0 | 264 | releaseZone3, |
michael@0 | 265 | releaseZone4 |
michael@0 | 266 | ); |
michael@0 | 267 | |
michael@0 | 268 | // Update the compression amount. |
michael@0 | 269 | setParameterValue(ParamReduction, m_compressor.meteringGain()); |
michael@0 | 270 | |
michael@0 | 271 | // Apply de-emphasis filter. |
michael@0 | 272 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
michael@0 | 273 | float* destinationData = m_destinationChannels[i]; |
michael@0 | 274 | ZeroPole* postFilters = m_postFilterPacks[i]->filters; |
michael@0 | 275 | |
michael@0 | 276 | postFilters[0].process(destinationData, destinationData, framesToProcess); |
michael@0 | 277 | postFilters[1].process(destinationData, destinationData, framesToProcess); |
michael@0 | 278 | postFilters[2].process(destinationData, destinationData, framesToProcess); |
michael@0 | 279 | postFilters[3].process(destinationData, destinationData, framesToProcess); |
michael@0 | 280 | } |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | void DynamicsCompressor::reset() |
michael@0 | 284 | { |
michael@0 | 285 | m_lastFilterStageRatio = -1; // for recalc |
michael@0 | 286 | m_lastAnchor = -1; |
michael@0 | 287 | m_lastFilterStageGain = -1; |
michael@0 | 288 | |
michael@0 | 289 | for (unsigned channel = 0; channel < m_numberOfChannels; ++channel) { |
michael@0 | 290 | for (unsigned stageIndex = 0; stageIndex < 4; ++stageIndex) { |
michael@0 | 291 | m_preFilterPacks[channel]->filters[stageIndex].reset(); |
michael@0 | 292 | m_postFilterPacks[channel]->filters[stageIndex].reset(); |
michael@0 | 293 | } |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | m_compressor.reset(); |
michael@0 | 297 | } |
michael@0 | 298 | |
michael@0 | 299 | void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels) |
michael@0 | 300 | { |
michael@0 | 301 | if (m_preFilterPacks.Length() == numberOfChannels) |
michael@0 | 302 | return; |
michael@0 | 303 | |
michael@0 | 304 | m_preFilterPacks.Clear(); |
michael@0 | 305 | m_postFilterPacks.Clear(); |
michael@0 | 306 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
michael@0 | 307 | m_preFilterPacks.AppendElement(new ZeroPoleFilterPack4()); |
michael@0 | 308 | m_postFilterPacks.AppendElement(new ZeroPoleFilterPack4()); |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | m_sourceChannels = new const float* [numberOfChannels]; |
michael@0 | 312 | m_destinationChannels = new float* [numberOfChannels]; |
michael@0 | 313 | |
michael@0 | 314 | m_compressor.setNumberOfChannels(numberOfChannels); |
michael@0 | 315 | m_numberOfChannels = numberOfChannels; |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | } // namespace WebCore |