mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 1184801 process AnalyserNode input only when required r=padenot
This moves the application of volume and averaging of channels to the main thread, performed when required. It avoids a potential allocation on the graph thread. If doing a full analysis for frequency data, the extra work on the main thread should be negligible. I assume that repeatedly fetching the same time domain data with getFloatFrequencyData() is not something we need to optimize for. If, in rare circumstances, the extra main thread work turns out to be significant, then the main thread work in getters is self-regulating, but too much load on the graph thread leads to catastrophic failure in the audio. This also fixes some bugs: Input and output streams for other consumers are not corrupted by in-place scaling of data intended to be read-only. When there are additional channels and fftSize < WEBAUDIO_BLOCK_SIZE, the channels are not written past the current length of a buffer, so there is no longer a dependency on nsTArray's behavior of never not reducing allocation size on length changes. The most recent fftSize samples are now used even when fftSize < WEBAUDIO_BLOCK_SIZE, instead of the first fftSize samples in the most recent block. Enough time domain data is recorded so that getters will work immediately following a change in fftSize.
This commit is contained in:
parent
9e1ad446ab
commit
4db23df7b6
@ -12,6 +12,14 @@
|
||||
#include "mozilla/PodOperations.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static const uint32_t MAX_FFT_SIZE = 32768;
|
||||
static const size_t CHUNK_COUNT = MAX_FFT_SIZE >> WEBAUDIO_BLOCK_SIZE_BITS;
|
||||
static_assert(MAX_FFT_SIZE == CHUNK_COUNT * WEBAUDIO_BLOCK_SIZE,
|
||||
"MAX_FFT_SIZE must be a multiple of WEBAUDIO_BLOCK_SIZE");
|
||||
static_assert((CHUNK_COUNT & (CHUNK_COUNT - 1)) == 0,
|
||||
"CHUNK_COUNT must be power of 2 for remainder behavior");
|
||||
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(AnalyserNode, AudioNode)
|
||||
@ -57,20 +65,7 @@ public:
|
||||
{
|
||||
*aOutput = aInput;
|
||||
|
||||
// If the input is silent, we sill need to send a silent buffer
|
||||
if (aOutput->IsNull()) {
|
||||
AllocateAudioBlock(1, aOutput);
|
||||
float* samples =
|
||||
static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
|
||||
PodZero(samples, WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
uint32_t channelCount = aOutput->mChannelData.Length();
|
||||
for (uint32_t channel = 0; channel < channelCount; ++channel) {
|
||||
float* samples =
|
||||
static_cast<float*>(const_cast<void*>(aOutput->mChannelData[channel]));
|
||||
AudioBlockInPlaceScale(samples, aOutput->mVolume);
|
||||
}
|
||||
nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, *aOutput);
|
||||
nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, aInput);
|
||||
NS_DispatchToMainThread(transfer);
|
||||
}
|
||||
|
||||
@ -89,10 +84,15 @@ AnalyserNode::AnalyserNode(AudioContext* aContext)
|
||||
, mMinDecibels(-100.)
|
||||
, mMaxDecibels(-30.)
|
||||
, mSmoothingTimeConstant(.8)
|
||||
, mWriteIndex(0)
|
||||
{
|
||||
mStream = aContext->Graph()->CreateAudioNodeStream(new AnalyserNodeEngine(this),
|
||||
MediaStreamGraph::INTERNAL_STREAM);
|
||||
|
||||
// Enough chunks must be recorded to handle the case of fftSize being
|
||||
// increased to maximum immediately before getFloatTimeDomainData() is
|
||||
// called, for example.
|
||||
(void)mChunks.SetLength(CHUNK_COUNT, fallible);
|
||||
|
||||
AllocateBuffer();
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
|
||||
amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
|
||||
amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
||||
amount += mChunks.SizeOfExcludingThis(aMallocSizeOf);
|
||||
amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
||||
return amount;
|
||||
}
|
||||
@ -123,7 +123,7 @@ AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv)
|
||||
{
|
||||
// Disallow values that are not a power of 2 and outside the [32,32768] range
|
||||
if (aValue < 32 ||
|
||||
aValue > 32768 ||
|
||||
aValue > MAX_FFT_SIZE ||
|
||||
(aValue & (aValue - 1)) != 0) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
||||
return;
|
||||
@ -212,11 +212,9 @@ AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray)
|
||||
aArray.ComputeLengthAndData();
|
||||
|
||||
float* buffer = aArray.Data();
|
||||
size_t length = std::min(size_t(aArray.Length()), mBuffer.Length());
|
||||
size_t length = std::min(aArray.Length(), FftSize());
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
buffer[i] = mBuffer[(i + mWriteIndex) % mBuffer.Length()];;
|
||||
}
|
||||
GetTimeDomainData(buffer, length);
|
||||
}
|
||||
|
||||
void
|
||||
@ -224,11 +222,18 @@ AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray)
|
||||
{
|
||||
aArray.ComputeLengthAndData();
|
||||
|
||||
unsigned char* buffer = aArray.Data();
|
||||
size_t length = std::min(size_t(aArray.Length()), mBuffer.Length());
|
||||
size_t length = std::min(aArray.Length(), FftSize());
|
||||
|
||||
AlignedTArray<float> tmpBuffer;
|
||||
if (!tmpBuffer.SetLength(length, fallible)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetTimeDomainData(tmpBuffer.Elements(), length);
|
||||
|
||||
unsigned char* buffer = aArray.Data();
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
const float value = mBuffer[(i + mWriteIndex) % mBuffer.Length()];
|
||||
const float value = tmpBuffer[i];
|
||||
// scale the value to the range of [0, UCHAR_MAX]
|
||||
const float scaled = std::max(0.0f, std::min(float(UCHAR_MAX),
|
||||
128.0f * (value + 1.0f)));
|
||||
@ -239,25 +244,19 @@ AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray)
|
||||
bool
|
||||
AnalyserNode::FFTAnalysis()
|
||||
{
|
||||
float* inputBuffer;
|
||||
AlignedTArray<float> tmpBuffer;
|
||||
if (mWriteIndex == 0) {
|
||||
inputBuffer = mBuffer.Elements();
|
||||
} else {
|
||||
if (!tmpBuffer.SetLength(FftSize(), fallible)) {
|
||||
size_t fftSize = FftSize();
|
||||
if (!tmpBuffer.SetLength(fftSize, fallible)) {
|
||||
return false;
|
||||
}
|
||||
inputBuffer = tmpBuffer.Elements();
|
||||
memcpy(inputBuffer, mBuffer.Elements() + mWriteIndex, sizeof(float) * (FftSize() - mWriteIndex));
|
||||
memcpy(inputBuffer + FftSize() - mWriteIndex, mBuffer.Elements(), sizeof(float) * mWriteIndex);
|
||||
}
|
||||
|
||||
ApplyBlackmanWindow(inputBuffer, FftSize());
|
||||
|
||||
float* inputBuffer = tmpBuffer.Elements();
|
||||
GetTimeDomainData(inputBuffer, fftSize);
|
||||
ApplyBlackmanWindow(inputBuffer, fftSize);
|
||||
mAnalysisBlock.PerformFFT(inputBuffer);
|
||||
|
||||
// Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor).
|
||||
const double magnitudeScale = 1.0 / FftSize();
|
||||
const double magnitudeScale = 1.0 / fftSize;
|
||||
|
||||
for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) {
|
||||
double scalarMagnitude = NS_hypot(mAnalysisBlock.RealData(i),
|
||||
@ -289,13 +288,7 @@ bool
|
||||
AnalyserNode::AllocateBuffer()
|
||||
{
|
||||
bool result = true;
|
||||
if (mBuffer.Length() != FftSize()) {
|
||||
if (!mBuffer.SetLength(FftSize(), fallible)) {
|
||||
return false;
|
||||
}
|
||||
memset(mBuffer.Elements(), 0, sizeof(float) * FftSize());
|
||||
mWriteIndex = 0;
|
||||
|
||||
if (mOutputBuffer.Length() != FrequencyBinCount()) {
|
||||
if (!mOutputBuffer.SetLength(FrequencyBinCount(), fallible)) {
|
||||
return false;
|
||||
}
|
||||
@ -307,31 +300,57 @@ AnalyserNode::AllocateBuffer()
|
||||
void
|
||||
AnalyserNode::AppendChunk(const AudioChunk& aChunk)
|
||||
{
|
||||
const uint32_t bufferSize = mBuffer.Length();
|
||||
const uint32_t channelCount = aChunk.mChannelData.Length();
|
||||
uint32_t chunkDuration = aChunk.mDuration;
|
||||
MOZ_ASSERT((bufferSize & (bufferSize - 1)) == 0); // Must be a power of two!
|
||||
MOZ_ASSERT(channelCount > 0);
|
||||
MOZ_ASSERT(chunkDuration == WEBAUDIO_BLOCK_SIZE);
|
||||
|
||||
if (chunkDuration > bufferSize) {
|
||||
// Copy a maximum bufferSize samples.
|
||||
chunkDuration = bufferSize;
|
||||
if (mChunks.Length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PodCopy(mBuffer.Elements() + mWriteIndex, static_cast<const float*>(aChunk.mChannelData[0]), chunkDuration);
|
||||
++mCurrentChunk;
|
||||
mChunks[mCurrentChunk & (CHUNK_COUNT - 1)] = aChunk;
|
||||
}
|
||||
|
||||
// Reads into aData the oldest aLength samples of the fftSize most recent
|
||||
// samples.
|
||||
void
|
||||
AnalyserNode::GetTimeDomainData(float* aData, size_t aLength)
|
||||
{
|
||||
size_t fftSize = FftSize();
|
||||
MOZ_ASSERT(aLength <= fftSize);
|
||||
|
||||
if (mChunks.Length() == 0) {
|
||||
PodZero(aData, aLength);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t readChunk =
|
||||
mCurrentChunk - ((fftSize - 1) >> WEBAUDIO_BLOCK_SIZE_BITS);
|
||||
size_t readIndex = (0 - fftSize) & (WEBAUDIO_BLOCK_SIZE - 1);
|
||||
MOZ_ASSERT(readIndex == 0 || readIndex + fftSize == WEBAUDIO_BLOCK_SIZE);
|
||||
|
||||
for (size_t writeIndex = 0; writeIndex < aLength; ) {
|
||||
const AudioChunk& chunk = mChunks[readChunk & (CHUNK_COUNT - 1)];
|
||||
const size_t channelCount = chunk.mChannelData.Length();
|
||||
size_t copyLength =
|
||||
std::min<size_t>(aLength - writeIndex, WEBAUDIO_BLOCK_SIZE);
|
||||
float* dataOut = &aData[writeIndex];
|
||||
|
||||
if (channelCount == 0) {
|
||||
PodZero(dataOut, copyLength);
|
||||
} else {
|
||||
float scale = chunk.mVolume / channelCount;
|
||||
{ // channel 0
|
||||
auto channelData =
|
||||
static_cast<const float*>(chunk.mChannelData[0]) + readIndex;
|
||||
AudioBufferCopyWithScale(channelData, scale, dataOut, copyLength);
|
||||
}
|
||||
for (uint32_t i = 1; i < channelCount; ++i) {
|
||||
AudioBlockAddChannelWithScale(static_cast<const float*>(aChunk.mChannelData[i]), 1.0f,
|
||||
mBuffer.Elements() + mWriteIndex);
|
||||
auto channelData =
|
||||
static_cast<const float*>(chunk.mChannelData[i]) + readIndex;
|
||||
AudioBufferAddWithScale(channelData, scale, dataOut, copyLength);
|
||||
}
|
||||
if (channelCount > 1) {
|
||||
AudioBlockInPlaceScale(mBuffer.Elements() + mWriteIndex,
|
||||
1.0f / aChunk.mChannelData.Length());
|
||||
}
|
||||
mWriteIndex += chunkDuration;
|
||||
MOZ_ASSERT(mWriteIndex <= bufferSize);
|
||||
if (mWriteIndex >= bufferSize) {
|
||||
mWriteIndex = 0;
|
||||
|
||||
readChunk++;
|
||||
writeIndex += copyLength;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,14 +71,15 @@ private:
|
||||
bool AllocateBuffer();
|
||||
bool FFTAnalysis();
|
||||
void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
|
||||
void GetTimeDomainData(float* aData, size_t aLength);
|
||||
|
||||
private:
|
||||
FFTBlock mAnalysisBlock;
|
||||
nsTArray<AudioChunk> mChunks;
|
||||
double mMinDecibels;
|
||||
double mMaxDecibels;
|
||||
double mSmoothingTimeConstant;
|
||||
uint32_t mWriteIndex;
|
||||
AlignedTArray<float> mBuffer;
|
||||
size_t mCurrentChunk = 0;
|
||||
AlignedTArray<float> mOutputBuffer;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user