| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2010, Google Inc. All rights reserved. | 2 * Copyright (C) 2010, Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
| 8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
| 10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
| (...skipping 22 matching lines...) Expand all Loading... |
| 33 #include <complex> | 33 #include <complex> |
| 34 #include <limits.h> | 34 #include <limits.h> |
| 35 | 35 |
| 36 namespace blink { | 36 namespace blink { |
| 37 | 37 |
| 38 const double RealtimeAnalyser::DefaultSmoothingTimeConstant = 0.8; | 38 const double RealtimeAnalyser::DefaultSmoothingTimeConstant = 0.8; |
| 39 const double RealtimeAnalyser::DefaultMinDecibels = -100; | 39 const double RealtimeAnalyser::DefaultMinDecibels = -100; |
| 40 const double RealtimeAnalyser::DefaultMaxDecibels = -30; | 40 const double RealtimeAnalyser::DefaultMaxDecibels = -30; |
| 41 | 41 |
| 42 const unsigned RealtimeAnalyser::DefaultFFTSize = 2048; | 42 const unsigned RealtimeAnalyser::DefaultFFTSize = 2048; |
| 43 // All FFT implementations are expected to handle power-of-two sizes MinFFTSize
<= size <= MaxFFTSize. | 43 // All FFT implementations are expected to handle power-of-two sizes |
| 44 // MinFFTSize <= size <= MaxFFTSize. |
| 44 const unsigned RealtimeAnalyser::MinFFTSize = 32; | 45 const unsigned RealtimeAnalyser::MinFFTSize = 32; |
| 45 const unsigned RealtimeAnalyser::MaxFFTSize = 32768; | 46 const unsigned RealtimeAnalyser::MaxFFTSize = 32768; |
| 46 const unsigned RealtimeAnalyser::InputBufferSize = | 47 const unsigned RealtimeAnalyser::InputBufferSize = |
| 47 RealtimeAnalyser::MaxFFTSize * 2; | 48 RealtimeAnalyser::MaxFFTSize * 2; |
| 48 | 49 |
| 49 RealtimeAnalyser::RealtimeAnalyser() | 50 RealtimeAnalyser::RealtimeAnalyser() |
| 50 : m_inputBuffer(InputBufferSize), | 51 : m_inputBuffer(InputBufferSize), |
| 51 m_writeIndex(0), | 52 m_writeIndex(0), |
| 52 m_downMixBus(AudioBus::create(1, AudioUtilities::kRenderQuantumFrames)), | 53 m_downMixBus(AudioBus::create(1, AudioUtilities::kRenderQuantumFrames)), |
| 53 m_fftSize(DefaultFFTSize), | 54 m_fftSize(DefaultFFTSize), |
| (...skipping 10 matching lines...) Expand all Loading... |
| 64 | 65 |
| 65 // Only allow powers of two. | 66 // Only allow powers of two. |
| 66 unsigned log2size = static_cast<unsigned>(log2(size)); | 67 unsigned log2size = static_cast<unsigned>(log2(size)); |
| 67 bool isPOT(1UL << log2size == size); | 68 bool isPOT(1UL << log2size == size); |
| 68 | 69 |
| 69 if (!isPOT || size > MaxFFTSize || size < MinFFTSize) | 70 if (!isPOT || size > MaxFFTSize || size < MinFFTSize) |
| 70 return false; | 71 return false; |
| 71 | 72 |
| 72 if (m_fftSize != size) { | 73 if (m_fftSize != size) { |
| 73 m_analysisFrame = wrapUnique(new FFTFrame(size)); | 74 m_analysisFrame = wrapUnique(new FFTFrame(size)); |
| 74 // m_magnitudeBuffer has size = fftSize / 2 because it contains floats reduc
ed from complex values in m_analysisFrame. | 75 // m_magnitudeBuffer has size = fftSize / 2 because it contains floats |
| 76 // reduced from complex values in m_analysisFrame. |
| 75 m_magnitudeBuffer.allocate(size / 2); | 77 m_magnitudeBuffer.allocate(size / 2); |
| 76 m_fftSize = size; | 78 m_fftSize = size; |
| 77 } | 79 } |
| 78 | 80 |
| 79 return true; | 81 return true; |
| 80 } | 82 } |
| 81 | 83 |
| 82 void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess) { | 84 void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess) { |
| 83 bool isBusGood = bus && bus->numberOfChannels() > 0 && | 85 bool isBusGood = bus && bus->numberOfChannels() > 0 && |
| 84 bus->channel(0)->length() >= framesToProcess; | 86 bus->channel(0)->length() >= framesToProcess; |
| 85 DCHECK(isBusGood); | 87 DCHECK(isBusGood); |
| 86 if (!isBusGood) | 88 if (!isBusGood) |
| 87 return; | 89 return; |
| 88 | 90 |
| 89 // FIXME : allow to work with non-FFTSize divisible chunking | 91 // FIXME : allow to work with non-FFTSize divisible chunking |
| 90 bool isDestinationGood = | 92 bool isDestinationGood = |
| 91 m_writeIndex < m_inputBuffer.size() && | 93 m_writeIndex < m_inputBuffer.size() && |
| 92 m_writeIndex + framesToProcess <= m_inputBuffer.size(); | 94 m_writeIndex + framesToProcess <= m_inputBuffer.size(); |
| 93 DCHECK(isDestinationGood); | 95 DCHECK(isDestinationGood); |
| 94 if (!isDestinationGood) | 96 if (!isDestinationGood) |
| 95 return; | 97 return; |
| 96 | 98 |
| 97 // Perform real-time analysis | 99 // Perform real-time analysis |
| 98 float* dest = m_inputBuffer.data() + m_writeIndex; | 100 float* dest = m_inputBuffer.data() + m_writeIndex; |
| 99 | 101 |
| 100 // Clear the bus and downmix the input according to the down mixing rules. Th
en save the result | 102 // Clear the bus and downmix the input according to the down mixing rules. |
| 101 // in the m_inputBuffer at the appropriate place. | 103 // Then save the result in the m_inputBuffer at the appropriate place. |
| 102 m_downMixBus->zero(); | 104 m_downMixBus->zero(); |
| 103 m_downMixBus->sumFrom(*bus); | 105 m_downMixBus->sumFrom(*bus); |
| 104 memcpy(dest, m_downMixBus->channel(0)->data(), | 106 memcpy(dest, m_downMixBus->channel(0)->data(), |
| 105 framesToProcess * sizeof(*dest)); | 107 framesToProcess * sizeof(*dest)); |
| 106 | 108 |
| 107 m_writeIndex += framesToProcess; | 109 m_writeIndex += framesToProcess; |
| 108 if (m_writeIndex >= InputBufferSize) | 110 if (m_writeIndex >= InputBufferSize) |
| 109 m_writeIndex = 0; | 111 m_writeIndex = 0; |
| 110 } | 112 } |
| 111 | 113 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 126 a0 - a1 * cos(twoPiDouble * x) + a2 * cos(twoPiDouble * 2.0 * x); | 128 a0 - a1 * cos(twoPiDouble * x) + a2 * cos(twoPiDouble * 2.0 * x); |
| 127 p[i] *= float(window); | 129 p[i] *= float(window); |
| 128 } | 130 } |
| 129 } | 131 } |
| 130 | 132 |
| 131 } // namespace | 133 } // namespace |
| 132 | 134 |
| 133 void RealtimeAnalyser::doFFTAnalysis() { | 135 void RealtimeAnalyser::doFFTAnalysis() { |
| 134 DCHECK(isMainThread()); | 136 DCHECK(isMainThread()); |
| 135 | 137 |
| 136 // Unroll the input buffer into a temporary buffer, where we'll apply an analy
sis window followed by an FFT. | 138 // Unroll the input buffer into a temporary buffer, where we'll apply an |
| 139 // analysis window followed by an FFT. |
| 137 size_t fftSize = this->fftSize(); | 140 size_t fftSize = this->fftSize(); |
| 138 | 141 |
| 139 AudioFloatArray temporaryBuffer(fftSize); | 142 AudioFloatArray temporaryBuffer(fftSize); |
| 140 float* inputBuffer = m_inputBuffer.data(); | 143 float* inputBuffer = m_inputBuffer.data(); |
| 141 float* tempP = temporaryBuffer.data(); | 144 float* tempP = temporaryBuffer.data(); |
| 142 | 145 |
| 143 // Take the previous fftSize values from the input buffer and copy into the te
mporary buffer. | 146 // Take the previous fftSize values from the input buffer and copy into the |
| 147 // temporary buffer. |
| 144 unsigned writeIndex = m_writeIndex; | 148 unsigned writeIndex = m_writeIndex; |
| 145 if (writeIndex < fftSize) { | 149 if (writeIndex < fftSize) { |
| 146 memcpy(tempP, inputBuffer + writeIndex - fftSize + InputBufferSize, | 150 memcpy(tempP, inputBuffer + writeIndex - fftSize + InputBufferSize, |
| 147 sizeof(*tempP) * (fftSize - writeIndex)); | 151 sizeof(*tempP) * (fftSize - writeIndex)); |
| 148 memcpy(tempP + fftSize - writeIndex, inputBuffer, | 152 memcpy(tempP + fftSize - writeIndex, inputBuffer, |
| 149 sizeof(*tempP) * writeIndex); | 153 sizeof(*tempP) * writeIndex); |
| 150 } else { | 154 } else { |
| 151 memcpy(tempP, inputBuffer + writeIndex - fftSize, sizeof(*tempP) * fftSize); | 155 memcpy(tempP, inputBuffer + writeIndex - fftSize, sizeof(*tempP) * fftSize); |
| 152 } | 156 } |
| 153 | 157 |
| 154 // Window the input samples. | 158 // Window the input samples. |
| 155 applyWindow(tempP, fftSize); | 159 applyWindow(tempP, fftSize); |
| 156 | 160 |
| 157 // Do the analysis. | 161 // Do the analysis. |
| 158 m_analysisFrame->doFFT(tempP); | 162 m_analysisFrame->doFFT(tempP); |
| 159 | 163 |
| 160 float* realP = m_analysisFrame->realData(); | 164 float* realP = m_analysisFrame->realData(); |
| 161 float* imagP = m_analysisFrame->imagData(); | 165 float* imagP = m_analysisFrame->imagData(); |
| 162 | 166 |
| 163 // Blow away the packed nyquist component. | 167 // Blow away the packed nyquist component. |
| 164 imagP[0] = 0; | 168 imagP[0] = 0; |
| 165 | 169 |
| 166 // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT
scaling factor). | 170 // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT |
| 171 // scaling factor). |
| 167 const double magnitudeScale = 1.0 / fftSize; | 172 const double magnitudeScale = 1.0 / fftSize; |
| 168 | 173 |
| 169 // A value of 0 does no averaging with the previous result. Larger values pro
duce slower, but smoother changes. | 174 // A value of 0 does no averaging with the previous result. Larger values |
| 175 // produce slower, but smoother changes. |
| 170 double k = m_smoothingTimeConstant; | 176 double k = m_smoothingTimeConstant; |
| 171 k = std::max(0.0, k); | 177 k = std::max(0.0, k); |
| 172 k = std::min(1.0, k); | 178 k = std::min(1.0, k); |
| 173 | 179 |
| 174 // Convert the analysis data from complex to magnitude and average with the pr
evious result. | 180 // Convert the analysis data from complex to magnitude and average with the |
| 181 // previous result. |
| 175 float* destination = magnitudeBuffer().data(); | 182 float* destination = magnitudeBuffer().data(); |
| 176 size_t n = magnitudeBuffer().size(); | 183 size_t n = magnitudeBuffer().size(); |
| 177 for (size_t i = 0; i < n; ++i) { | 184 for (size_t i = 0; i < n; ++i) { |
| 178 std::complex<double> c(realP[i], imagP[i]); | 185 std::complex<double> c(realP[i], imagP[i]); |
| 179 double scalarMagnitude = abs(c) * magnitudeScale; | 186 double scalarMagnitude = abs(c) * magnitudeScale; |
| 180 destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude); | 187 destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude); |
| 181 } | 188 } |
| 182 } | 189 } |
| 183 | 190 |
| 184 void RealtimeAnalyser::convertFloatToDb(DOMFloat32Array* destinationArray) { | 191 void RealtimeAnalyser::convertFloatToDb(DOMFloat32Array* destinationArray) { |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 224 : 1 / (m_maxDecibels - m_minDecibels); | 231 : 1 / (m_maxDecibels - m_minDecibels); |
| 225 const double minDecibels = m_minDecibels; | 232 const double minDecibels = m_minDecibels; |
| 226 | 233 |
| 227 const float* source = magnitudeBuffer().data(); | 234 const float* source = magnitudeBuffer().data(); |
| 228 unsigned char* destination = destinationArray->data(); | 235 unsigned char* destination = destinationArray->data(); |
| 229 | 236 |
| 230 for (unsigned i = 0; i < len; ++i) { | 237 for (unsigned i = 0; i < len; ++i) { |
| 231 float linearValue = source[i]; | 238 float linearValue = source[i]; |
| 232 double dbMag = AudioUtilities::linearToDecibels(linearValue); | 239 double dbMag = AudioUtilities::linearToDecibels(linearValue); |
| 233 | 240 |
| 234 // The range m_minDecibels to m_maxDecibels will be scaled to byte values
from 0 to UCHAR_MAX. | 241 // The range m_minDecibels to m_maxDecibels will be scaled to byte values |
| 242 // from 0 to UCHAR_MAX. |
| 235 double scaledValue = UCHAR_MAX * (dbMag - minDecibels) * rangeScaleFactor; | 243 double scaledValue = UCHAR_MAX * (dbMag - minDecibels) * rangeScaleFactor; |
| 236 | 244 |
| 237 // Clip to valid range. | 245 // Clip to valid range. |
| 238 if (scaledValue < 0) | 246 if (scaledValue < 0) |
| 239 scaledValue = 0; | 247 scaledValue = 0; |
| 240 if (scaledValue > UCHAR_MAX) | 248 if (scaledValue > UCHAR_MAX) |
| 241 scaledValue = UCHAR_MAX; | 249 scaledValue = UCHAR_MAX; |
| 242 | 250 |
| 243 destination[i] = static_cast<unsigned char>(scaledValue); | 251 destination[i] = static_cast<unsigned char>(scaledValue); |
| 244 } | 252 } |
| 245 } | 253 } |
| 246 } | 254 } |
| 247 | 255 |
| 248 void RealtimeAnalyser::getByteFrequencyData(DOMUint8Array* destinationArray, | 256 void RealtimeAnalyser::getByteFrequencyData(DOMUint8Array* destinationArray, |
| 249 double currentTime) { | 257 double currentTime) { |
| 250 DCHECK(isMainThread()); | 258 DCHECK(isMainThread()); |
| 251 DCHECK(destinationArray); | 259 DCHECK(destinationArray); |
| 252 | 260 |
| 253 if (currentTime <= m_lastAnalysisTime) { | 261 if (currentTime <= m_lastAnalysisTime) { |
| 254 // FIXME: Is it worth caching the data so we don't have to do the conversion
every time? | 262 // FIXME: Is it worth caching the data so we don't have to do the conversion |
| 255 // Perhaps not, since we expect many calls in the same rendering quantum. | 263 // every time? Perhaps not, since we expect many calls in the same |
| 264 // rendering quantum. |
| 256 convertToByteData(destinationArray); | 265 convertToByteData(destinationArray); |
| 257 return; | 266 return; |
| 258 } | 267 } |
| 259 | 268 |
| 260 // Time has advanced since the last call; update the FFT data. | 269 // Time has advanced since the last call; update the FFT data. |
| 261 m_lastAnalysisTime = currentTime; | 270 m_lastAnalysisTime = currentTime; |
| 262 doFFTAnalysis(); | 271 doFFTAnalysis(); |
| 263 | 272 |
| 264 convertToByteData(destinationArray); | 273 convertToByteData(destinationArray); |
| 265 } | 274 } |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 324 scaledValue = 0; | 333 scaledValue = 0; |
| 325 if (scaledValue > UCHAR_MAX) | 334 if (scaledValue > UCHAR_MAX) |
| 326 scaledValue = UCHAR_MAX; | 335 scaledValue = UCHAR_MAX; |
| 327 | 336 |
| 328 destination[i] = static_cast<unsigned char>(scaledValue); | 337 destination[i] = static_cast<unsigned char>(scaledValue); |
| 329 } | 338 } |
| 330 } | 339 } |
| 331 } | 340 } |
| 332 | 341 |
| 333 } // namespace blink | 342 } // namespace blink |
| OLD | NEW |