static void TestAudioCompactor(size_t aBytes) { MediaQueue<AudioData> queue; AudioCompactor compactor(queue); uint64_t offset = 0; uint64_t time = 0; uint32_t sampleRate = 44000; uint32_t channels = 2; uint32_t frames = aBytes / (channels * sizeof(AudioDataValue)); size_t maxSlop = aBytes / AudioCompactor::MAX_SLOP_DIVISOR; uint32_t callCount = 0; uint32_t frameCount = 0; compactor.Push(offset, time, sampleRate, frames, channels, TestCopy(frames, channels, callCount, frameCount)); EXPECT_GT(callCount, 0U) << "copy functor never called"; EXPECT_EQ(frames, frameCount) << "incorrect number of frames copied"; MemoryFunctor memoryFunc; queue.LockedForEach(memoryFunc); size_t allocSize = memoryFunc.mSize - (callCount * sizeof(AudioData)); size_t slop = allocSize - aBytes; EXPECT_LE(slop, maxSlop) << "allowed too much allocation slop"; }
void MediaDecodeTask::SampleDecoded(AudioData* aData) { MOZ_ASSERT(!NS_IsMainThread()); mAudioQueue.Push(aData); RequestSample(); }
void MediaDecodeTask::SampleDecoded(RefPtr<AudioData> aData) { MOZ_ASSERT(!NS_IsMainThread()); mAudioQueue.Push(aData); if (!mFirstFrameDecoded) { mDecoderReader->ReadUpdatedMetadata(&mMediaInfo); mFirstFrameDecoded = true; } RequestSample(); }
void MediaDecodeTask::FinishDecode() { mDecoderReader->Shutdown(); uint32_t frameCount = mAudioQueue.FrameCount(); uint32_t channelCount = mMediaInfo.mAudio.mChannels; uint32_t sampleRate = mMediaInfo.mAudio.mRate; if (!frameCount || !channelCount || !sampleRate) { ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); return; } const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate(); AutoResampler resampler; uint32_t resampledFrames = frameCount; if (sampleRate != destSampleRate) { resampledFrames = static_cast<uint32_t>( static_cast<uint64_t>(destSampleRate) * static_cast<uint64_t>(frameCount) / static_cast<uint64_t>(sampleRate) ); resampler = speex_resampler_init(channelCount, sampleRate, destSampleRate, SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr); speex_resampler_skip_zeros(resampler); resampledFrames += speex_resampler_get_output_latency(resampler); } // Allocate the channel buffers. Note that if we end up resampling, we may // write fewer bytes than mResampledFrames to the output buffer, in which // case mWriteIndex will tell us how many valid samples we have. mDecodeJob.mBuffer = ThreadSharedFloatArrayBufferList:: Create(channelCount, resampledFrames, fallible); if (!mDecodeJob.mBuffer) { ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError); return; } RefPtr<MediaData> mediaData; while ((mediaData = mAudioQueue.PopFront())) { RefPtr<AudioData> audioData = mediaData->As<AudioData>(); audioData->EnsureAudioBuffer(); // could lead to a copy :( AudioDataValue* bufferData = static_cast<AudioDataValue*> (audioData->mAudioBuffer->Data()); if (sampleRate != destSampleRate) { const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex; for (uint32_t i = 0; i < audioData->mChannels; ++i) { uint32_t inSamples = audioData->mFrames; uint32_t outSamples = maxOutSamples; float* outData = mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex; WebAudioUtils::SpeexResamplerProcess( resampler, i, &bufferData[i * audioData->mFrames], &inSamples, outData, &outSamples); if (i == audioData->mChannels - 1) { mDecodeJob.mWriteIndex += outSamples; MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames); MOZ_ASSERT(inSamples == audioData->mFrames); } } } else { for (uint32_t i = 0; i < audioData->mChannels; ++i) { float* outData = mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex; ConvertAudioSamples(&bufferData[i * audioData->mFrames], outData, audioData->mFrames); if (i == audioData->mChannels - 1) { mDecodeJob.mWriteIndex += audioData->mFrames; } } } } if (sampleRate != destSampleRate) { uint32_t inputLatency = speex_resampler_get_input_latency(resampler); const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex; for (uint32_t i = 0; i < channelCount; ++i) { uint32_t inSamples = inputLatency; uint32_t outSamples = maxOutSamples; float* outData = mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex; WebAudioUtils::SpeexResamplerProcess( resampler, i, (AudioDataValue*)nullptr, &inSamples, outData, &outSamples); if (i == channelCount - 1) { mDecodeJob.mWriteIndex += outSamples; MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames); MOZ_ASSERT(inSamples == inputLatency); } } } mPhase = PhaseEnum::AllocateBuffer; NS_DispatchToMainThread(this); }
void MediaDecodeTask::Decode() { MOZ_ASSERT(!NS_IsMainThread()); mBufferDecoder->BeginDecoding(NS_GetCurrentThread()); // Tell the decoder reader that we are not going to play the data directly, // and that we should not reject files with more channels than the audio // bakend support. mDecoderReader->SetIgnoreAudioOutputFormat(); MediaInfo mediaInfo; nsAutoPtr<MetadataTags> tags; nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags)); if (NS_FAILED(rv)) { ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); return; } if (!mDecoderReader->HasAudio()) { ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio); return; } MediaQueue<AudioData> audioQueue; nsRefPtr<AudioDecodeRendezvous> barrier(new AudioDecodeRendezvous()); mDecoderReader->SetCallback(barrier); while (1) { mDecoderReader->RequestAudioData(); nsRefPtr<AudioData> audio; if (NS_FAILED(barrier->Await(audio))) { ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); return; } if (!audio) { // End of stream. break; } audioQueue.Push(audio); } mDecoderReader->Shutdown(); mDecoderReader->BreakCycles(); uint32_t frameCount = audioQueue.FrameCount(); uint32_t channelCount = mediaInfo.mAudio.mChannels; uint32_t sampleRate = mediaInfo.mAudio.mRate; if (!frameCount || !channelCount || !sampleRate) { ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); return; } const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate(); AutoResampler resampler; uint32_t resampledFrames = frameCount; if (sampleRate != destSampleRate) { resampledFrames = static_cast<uint32_t>( static_cast<uint64_t>(destSampleRate) * static_cast<uint64_t>(frameCount) / static_cast<uint64_t>(sampleRate) ); resampler = speex_resampler_init(channelCount, sampleRate, destSampleRate, SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr); speex_resampler_skip_zeros(resampler); resampledFrames += speex_resampler_get_output_latency(resampler); } // Allocate the channel buffers. Note that if we end up resampling, we may // write fewer bytes than mResampledFrames to the output buffer, in which // case mWriteIndex will tell us how many valid samples we have. static const fallible_t fallible = fallible_t(); bool memoryAllocationSuccess = true; if (!mDecodeJob.mChannelBuffers.SetLength(channelCount)) { memoryAllocationSuccess = false; } else { for (uint32_t i = 0; i < channelCount; ++i) { mDecodeJob.mChannelBuffers[i] = new(fallible) float[resampledFrames]; if (!mDecodeJob.mChannelBuffers[i]) { memoryAllocationSuccess = false; break; } } } if (!memoryAllocationSuccess) { ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError); return; } nsRefPtr<AudioData> audioData; while ((audioData = audioQueue.PopFront())) { audioData->EnsureAudioBuffer(); // could lead to a copy :( AudioDataValue* bufferData = static_cast<AudioDataValue*> (audioData->mAudioBuffer->Data()); if (sampleRate != destSampleRate) { const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex; for (uint32_t i = 0; i < audioData->mChannels; ++i) { uint32_t inSamples = audioData->mFrames; uint32_t outSamples = maxOutSamples; WebAudioUtils::SpeexResamplerProcess( resampler, i, &bufferData[i * audioData->mFrames], &inSamples, mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex, &outSamples); if (i == audioData->mChannels - 1) { mDecodeJob.mWriteIndex += outSamples; MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames); MOZ_ASSERT(inSamples == audioData->mFrames); } } } else { for (uint32_t i = 0; i < audioData->mChannels; ++i) { ConvertAudioSamples(&bufferData[i * audioData->mFrames], mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex, audioData->mFrames); if (i == audioData->mChannels - 1) { mDecodeJob.mWriteIndex += audioData->mFrames; } } } } if (sampleRate != destSampleRate) { uint32_t inputLatency = speex_resampler_get_input_latency(resampler); const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex; for (uint32_t i = 0; i < channelCount; ++i) { uint32_t inSamples = inputLatency; uint32_t outSamples = maxOutSamples; WebAudioUtils::SpeexResamplerProcess( resampler, i, (AudioDataValue*)nullptr, &inSamples, mDecodeJob.mChannelBuffers[i] + mDecodeJob.mWriteIndex, &outSamples); if (i == channelCount - 1) { mDecodeJob.mWriteIndex += outSamples; MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames); MOZ_ASSERT(inSamples == inputLatency); } } } mPhase = PhaseEnum::AllocateBuffer; NS_DispatchToMainThread(this); }
void MediaDecodeTask::FinishDecode() { mDecoderReader->Shutdown(); uint32_t frameCount = mAudioQueue.AudioFramesCount(); uint32_t channelCount = mMediaInfo.mAudio.mChannels; uint32_t sampleRate = mMediaInfo.mAudio.mRate; if (!frameCount || !channelCount || !sampleRate) { ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); return; } const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate(); AutoResampler resampler; uint32_t resampledFrames = frameCount; if (sampleRate != destSampleRate) { resampledFrames = static_cast<uint32_t>( static_cast<uint64_t>(destSampleRate) * static_cast<uint64_t>(frameCount) / static_cast<uint64_t>(sampleRate)); resampler = speex_resampler_init(channelCount, sampleRate, destSampleRate, SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr); speex_resampler_skip_zeros(resampler); resampledFrames += speex_resampler_get_output_latency(resampler); } // Allocate contiguous channel buffers. Note that if we end up resampling, // we may write fewer bytes than mResampledFrames to the output buffer, in // which case writeIndex will tell us how many valid samples we have. mDecodeJob.mBuffer.mChannelData.SetLength(channelCount); #if AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32 // This buffer has separate channel arrays that could be transferred to // JS::NewArrayBufferWithContents(), but AudioBuffer::RestoreJSChannelData() // does not yet take advantage of this. RefPtr<ThreadSharedFloatArrayBufferList> buffer = ThreadSharedFloatArrayBufferList::Create(channelCount, resampledFrames, fallible); if (!buffer) { ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError); return; } for (uint32_t i = 0; i < channelCount; ++i) { mDecodeJob.mBuffer.mChannelData[i] = buffer->GetData(i); } #else RefPtr<SharedBuffer> buffer = SharedBuffer::Create( sizeof(AudioDataValue) * resampledFrames * channelCount); if (!buffer) { ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError); return; } auto data = static_cast<AudioDataValue*>(floatBuffer->Data()); for (uint32_t i = 0; i < channelCount; ++i) { mDecodeJob.mBuffer.mChannelData[i] = data; data += resampledFrames; } #endif mDecodeJob.mBuffer.mBuffer = buffer.forget(); mDecodeJob.mBuffer.mVolume = 1.0f; mDecodeJob.mBuffer.mBufferFormat = AUDIO_OUTPUT_FORMAT; uint32_t writeIndex = 0; RefPtr<AudioData> audioData; while ((audioData = mAudioQueue.PopFront())) { audioData->EnsureAudioBuffer(); // could lead to a copy :( const AudioDataValue* bufferData = static_cast<AudioDataValue*>(audioData->mAudioBuffer->Data()); if (sampleRate != destSampleRate) { const uint32_t maxOutSamples = resampledFrames - writeIndex; for (uint32_t i = 0; i < audioData->mChannels; ++i) { uint32_t inSamples = audioData->Frames(); uint32_t outSamples = maxOutSamples; AudioDataValue* outData = mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) + writeIndex; WebAudioUtils::SpeexResamplerProcess( resampler, i, &bufferData[i * audioData->Frames()], &inSamples, outData, &outSamples); if (i == audioData->mChannels - 1) { writeIndex += outSamples; MOZ_ASSERT(writeIndex <= resampledFrames); MOZ_ASSERT(inSamples == audioData->Frames()); } } } else { for (uint32_t i = 0; i < audioData->mChannels; ++i) { AudioDataValue* outData = mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) + writeIndex; PodCopy(outData, &bufferData[i * audioData->Frames()], audioData->Frames()); if (i == audioData->mChannels - 1) { writeIndex += audioData->Frames(); } } } } if (sampleRate != destSampleRate) { uint32_t inputLatency = speex_resampler_get_input_latency(resampler); const uint32_t maxOutSamples = resampledFrames - writeIndex; for (uint32_t i = 0; i < channelCount; ++i) { uint32_t inSamples = inputLatency; uint32_t outSamples = maxOutSamples; AudioDataValue* outData = mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) + writeIndex; WebAudioUtils::SpeexResamplerProcess(resampler, i, (AudioDataValue*)nullptr, &inSamples, outData, &outSamples); if (i == channelCount - 1) { writeIndex += outSamples; MOZ_ASSERT(writeIndex <= resampledFrames); MOZ_ASSERT(inSamples == inputLatency); } } } mDecodeJob.mBuffer.mDuration = writeIndex; mPhase = PhaseEnum::AllocateBuffer; mMainThread->Dispatch(do_AddRef(this)); }