/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DynamicResampler.h" namespace mozilla { DynamicResampler::DynamicResampler(uint32_t aInRate, uint32_t aOutRate, uint32_t aInputPreBufferFrameCount) : mOutRate(aOutRate), mInputPreBufferFrameCount(aInputPreBufferFrameCount), mInRate(aInRate) { MOZ_ASSERT(aInRate); MOZ_ASSERT(aOutRate); UpdateResampler(mInRate, STEREO); mInputStreamFile.Open("DynamicResamplerInFirstChannel", 1, mInRate); mOutputStreamFile.Open("DynamicResamplerOutFirstChannel", 1, mOutRate); } DynamicResampler::~DynamicResampler() { if (mResampler) { speex_resampler_destroy(mResampler); } } void DynamicResampler::SetSampleFormat(AudioSampleFormat aFormat) { MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_SILENCE); MOZ_ASSERT(aFormat == AUDIO_FORMAT_S16 || aFormat == AUDIO_FORMAT_FLOAT32); mSampleFormat = aFormat; for (AudioRingBuffer& b : mInternalInBuffer) { b.SetSampleFormat(mSampleFormat); } // Pre-allocate something big. // EnsureInputBufferDuration() adds 50ms for jitter to this first allocation // so the 50ms argument means at least 100ms. EnsureInputBufferSizeInFrames(mInRate / 20); } void DynamicResampler::EnsurePreBuffer(media::TimeUnit aDuration) { if (mIsPreBufferSet) { return; } uint32_t buffered = mInternalInBuffer[0].AvailableRead(); if (buffered == 0) { // Wait for the first input segment before deciding how much to pre-buffer. // If it is large it indicates high-latency, and the buffer would have to // handle that. This also means that the pre-buffer is not set up just // before a large input segment would extend the buffering beyond the // desired level. return; } mIsPreBufferSet = true; uint32_t needed = aDuration.ToTicksAtRate(mInRate) + mInputPreBufferFrameCount; EnsureInputBufferSizeInFrames(needed); if (needed > buffered) { for (auto& b : mInternalInBuffer) { b.PrependSilence(needed - buffered); } } else if (needed < buffered) { for (auto& b : mInternalInBuffer) { b.Discard(buffered - needed); } } } void DynamicResampler::SetInputPreBufferFrameCount( uint32_t aInputPreBufferFrameCount) { mInputPreBufferFrameCount = aInputPreBufferFrameCount; } bool DynamicResampler::Resample(float* aOutBuffer, uint32_t aOutFrames, uint32_t aChannelIndex) { MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_FLOAT32); return ResampleInternal(aOutBuffer, aOutFrames, aChannelIndex); } bool DynamicResampler::Resample(int16_t* aOutBuffer, uint32_t aOutFrames, uint32_t aChannelIndex) { MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16); return ResampleInternal(aOutBuffer, aOutFrames, aChannelIndex); } void DynamicResampler::ResampleInternal(const float* aInBuffer, uint32_t* aInFrames, float* aOutBuffer, uint32_t* aOutFrames, uint32_t aChannelIndex) { MOZ_ASSERT(mResampler); MOZ_ASSERT(mChannels); MOZ_ASSERT(mInRate); MOZ_ASSERT(mOutRate); MOZ_ASSERT(aInFrames); MOZ_ASSERT(*aInFrames > 0); MOZ_ASSERT(aOutBuffer); MOZ_ASSERT(aOutFrames); MOZ_ASSERT(*aOutFrames > 0); MOZ_ASSERT(aChannelIndex <= mChannels); #ifdef DEBUG int rv = #endif speex_resampler_process_float(mResampler, aChannelIndex, aInBuffer, aInFrames, aOutBuffer, aOutFrames); MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS); if (aChannelIndex == 0 && !mIsWarmingUp) { mInputStreamFile.Write(aInBuffer, *aInFrames); mOutputStreamFile.Write(aOutBuffer, *aOutFrames); } } void DynamicResampler::ResampleInternal(const int16_t* aInBuffer, uint32_t* aInFrames, int16_t* aOutBuffer, uint32_t* aOutFrames, uint32_t aChannelIndex) { MOZ_ASSERT(mResampler); MOZ_ASSERT(mChannels); MOZ_ASSERT(mInRate); MOZ_ASSERT(mOutRate); MOZ_ASSERT(aInFrames); MOZ_ASSERT(*aInFrames > 0); MOZ_ASSERT(aOutBuffer); MOZ_ASSERT(aOutFrames); MOZ_ASSERT(*aOutFrames > 0); MOZ_ASSERT(aChannelIndex <= mChannels); #ifdef DEBUG int rv = #endif speex_resampler_process_int(mResampler, aChannelIndex, aInBuffer, aInFrames, aOutBuffer, aOutFrames); MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS); if (aChannelIndex == 0 && !mIsWarmingUp) { mInputStreamFile.Write(aInBuffer, *aInFrames); mOutputStreamFile.Write(aOutBuffer, *aOutFrames); } } void DynamicResampler::UpdateResampler(uint32_t aInRate, uint32_t aChannels) { MOZ_ASSERT(aInRate); MOZ_ASSERT(aChannels); if (mChannels != aChannels) { uint32_t bufferSizeInFrames = InFramesBufferSize(); if (mResampler) { speex_resampler_destroy(mResampler); } mResampler = speex_resampler_init(aChannels, aInRate, mOutRate, SPEEX_RESAMPLER_QUALITY_MIN, nullptr); MOZ_ASSERT(mResampler); mChannels = aChannels; mInRate = aInRate; // Between mono and stereo changes, keep always allocated 2 channels to // avoid reallocations in the most common case. if ((mChannels == STEREO || mChannels == 1) && mInternalInBuffer.Length() == STEREO) { // Don't worry if format is not set it will write silence then. if ((mSampleFormat == AUDIO_FORMAT_S16 || mSampleFormat == AUDIO_FORMAT_FLOAT32) && mChannels == STEREO) { // The mono channel is always up to date. When we are going from mono // to stereo upmix the mono to stereo channel uint32_t bufferedDuration = mInternalInBuffer[0].AvailableRead(); mInternalInBuffer[1].Clear(); if (bufferedDuration) { mInternalInBuffer[1].Write(mInternalInBuffer[0], bufferedDuration); } } // Maintain stereo size mInputTail.SetLength(STEREO); WarmUpResampler(false); return; } // upmix or downmix, for now just clear but it has to be updated // because allocates and this is executed in audio thread. mInternalInBuffer.Clear(); for (uint32_t i = 0; i < mChannels; ++i) { AudioRingBuffer* b = mInternalInBuffer.AppendElement(0); if (mSampleFormat != AUDIO_FORMAT_SILENCE) { // In ctor this update is not needed b->SetSampleFormat(mSampleFormat); } } EnsureInputBufferSizeInFrames(bufferSizeInFrames); mInputTail.SetLength(mChannels); return; } if (mInRate != aInRate) { // If the rates was the same the resampler was not being used so warm up. if (mOutRate == mInRate) { WarmUpResampler(true); } #ifdef DEBUG int rv = #endif speex_resampler_set_rate(mResampler, aInRate, mOutRate); MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS); mInRate = aInRate; } } void DynamicResampler::WarmUpResampler(bool aSkipLatency) { MOZ_ASSERT(mInputTail.Length()); mIsWarmingUp = true; for (uint32_t i = 0; i < mChannels; ++i) { if (!mInputTail[i].Length()) { continue; } uint32_t inFrames = mInputTail[i].Length(); uint32_t outFrames = 5 * TailBuffer::MAXSIZE; // something big if (mSampleFormat == AUDIO_FORMAT_S16) { short outBuffer[5 * TailBuffer::MAXSIZE] = {}; ResampleInternal(mInputTail[i].Buffer(), &inFrames, outBuffer, &outFrames, i); MOZ_ASSERT(inFrames == (uint32_t)mInputTail[i].Length()); } else { float outBuffer[100] = {}; ResampleInternal(mInputTail[i].Buffer(), &inFrames, outBuffer, &outFrames, i); MOZ_ASSERT(inFrames == (uint32_t)mInputTail[i].Length()); } } if (aSkipLatency) { // Don't generate output frames corresponding to times before the next // input sample. speex_resampler_skip_zeros(mResampler); } mIsWarmingUp = false; } void DynamicResampler::AppendInput(Span aInBuffer, uint32_t aInFrames) { MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_FLOAT32); AppendInputInternal(aInBuffer, aInFrames); } void DynamicResampler::AppendInput(Span aInBuffer, uint32_t aInFrames) { MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16); AppendInputInternal(aInBuffer, aInFrames); } void DynamicResampler::AppendInputSilence(const uint32_t aInFrames) { MOZ_ASSERT(aInFrames); MOZ_ASSERT(mChannels); MOZ_ASSERT(mInternalInBuffer.Length() >= (uint32_t)mChannels); for (uint32_t i = 0; i < mChannels; ++i) { mInternalInBuffer[i].WriteSilence(aInFrames); } } uint32_t DynamicResampler::InFramesBufferSize() const { if (mSampleFormat == AUDIO_FORMAT_SILENCE) { return 0; } // Buffers may have different capacities if a memory allocation has failed. MOZ_ASSERT(!mInternalInBuffer.IsEmpty()); uint32_t min = std::numeric_limits::max(); for (const auto& b : mInternalInBuffer) { min = std::min(min, b.Capacity()); } return min; } uint32_t DynamicResampler::InFramesBuffered(uint32_t aChannelIndex) const { MOZ_ASSERT(mChannels); MOZ_ASSERT(aChannelIndex <= mChannels); MOZ_ASSERT(aChannelIndex <= mInternalInBuffer.Length()); if (!mIsPreBufferSet) { return mInputPreBufferFrameCount; } return mInternalInBuffer[aChannelIndex].AvailableRead(); } } // namespace mozilla