diff options
Diffstat (limited to 'dom/media/AudioConverter.h')
-rw-r--r-- | dom/media/AudioConverter.h | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/dom/media/AudioConverter.h b/dom/media/AudioConverter.h new file mode 100644 index 0000000000..0ace580b26 --- /dev/null +++ b/dom/media/AudioConverter.h @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#if !defined(AudioConverter_h) +# define AudioConverter_h + +# include "MediaInfo.h" + +// Forward declaration +typedef struct SpeexResamplerState_ SpeexResamplerState; + +namespace mozilla { + +template <AudioConfig::SampleFormat T> +struct AudioDataBufferTypeChooser; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_U8> { + typedef uint8_t Type; +}; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S16> { + typedef int16_t Type; +}; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24LSB> { + typedef int32_t Type; +}; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S24> { + typedef int32_t Type; +}; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_S32> { + typedef int32_t Type; +}; +template <> +struct AudioDataBufferTypeChooser<AudioConfig::FORMAT_FLT> { + typedef float Type; +}; + +// 'Value' is the type used externally to deal with stored value. +// AudioDataBuffer can perform conversion between different SampleFormat +// content. +template <AudioConfig::SampleFormat Format, + typename Value = typename AudioDataBufferTypeChooser<Format>::Type> +class AudioDataBuffer { + public: + AudioDataBuffer() = default; + AudioDataBuffer(Value* aBuffer, size_t aLength) : mBuffer(aBuffer, aLength) {} + explicit AudioDataBuffer(const AudioDataBuffer& aOther) + : mBuffer(aOther.mBuffer) {} + AudioDataBuffer(AudioDataBuffer&& aOther) + : mBuffer(std::move(aOther.mBuffer)) {} + template <AudioConfig::SampleFormat OtherFormat, typename OtherValue> + explicit AudioDataBuffer( + const AudioDataBuffer<OtherFormat, OtherValue>& other) { + // TODO: Convert from different type, may use asm routines. + MOZ_CRASH("Conversion not implemented yet"); + } + + // A u8, s16 and float aligned buffer can only be treated as + // FORMAT_U8, FORMAT_S16 and FORMAT_FLT respectively. + // So allow them as copy and move constructors. + explicit AudioDataBuffer(const AlignedByteBuffer& aBuffer) + : mBuffer(aBuffer) { + static_assert(Format == AudioConfig::FORMAT_U8, + "Conversion not implemented yet"); + } + explicit AudioDataBuffer(const AlignedShortBuffer& aBuffer) + : mBuffer(aBuffer) { + static_assert(Format == AudioConfig::FORMAT_S16, + "Conversion not implemented yet"); + } + explicit AudioDataBuffer(const AlignedFloatBuffer& aBuffer) + : mBuffer(aBuffer) { + static_assert(Format == AudioConfig::FORMAT_FLT, + "Conversion not implemented yet"); + } + explicit AudioDataBuffer(AlignedByteBuffer&& aBuffer) + : mBuffer(std::move(aBuffer)) { + static_assert(Format == AudioConfig::FORMAT_U8, + "Conversion not implemented yet"); + } + explicit AudioDataBuffer(AlignedShortBuffer&& aBuffer) + : mBuffer(std::move(aBuffer)) { + static_assert(Format == AudioConfig::FORMAT_S16, + "Conversion not implemented yet"); + } + explicit AudioDataBuffer(AlignedFloatBuffer&& aBuffer) + : mBuffer(std::move(aBuffer)) { + static_assert(Format == AudioConfig::FORMAT_FLT, + "Conversion not implemented yet"); + } + AudioDataBuffer& operator=(AudioDataBuffer&& aOther) { + mBuffer = std::move(aOther.mBuffer); + return *this; + } + AudioDataBuffer& operator=(const AudioDataBuffer& aOther) { + mBuffer = aOther.mBuffer; + return *this; + } + + Value* Data() const { return mBuffer.Data(); } + size_t Length() const { return mBuffer.Length(); } + size_t Size() const { return mBuffer.Size(); } + AlignedBuffer<Value> Forget() { + // Correct type -> Just give values as-is. + return std::move(mBuffer); + } + + private: + AlignedBuffer<Value> mBuffer; +}; + +typedef AudioDataBuffer<AudioConfig::FORMAT_DEFAULT> AudioSampleBuffer; + +class AudioConverter { + public: + AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut); + ~AudioConverter(); + + // Convert the AudioDataBuffer. + // Conversion will be done in place if possible. Otherwise a new buffer will + // be returned. + // Providing an empty buffer and resampling is expected, the resampler + // will be drained. + template <AudioConfig::SampleFormat Format, typename Value> + AudioDataBuffer<Format, Value> Process( + AudioDataBuffer<Format, Value>&& aBuffer) { + MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && + mIn.Format() == Format); + AudioDataBuffer<Format, Value> buffer = std::move(aBuffer); + if (CanWorkInPlace()) { + AlignedBuffer<Value> temp = buffer.Forget(); + Process(temp, temp.Data(), SamplesInToFrames(temp.Length())); + return AudioDataBuffer<Format, Value>(std::move(temp)); + ; + } + return Process(buffer); + } + + template <AudioConfig::SampleFormat Format, typename Value> + AudioDataBuffer<Format, Value> Process( + const AudioDataBuffer<Format, Value>& aBuffer) { + MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && + mIn.Format() == Format); + // Perform the downmixing / reordering in temporary buffer. + size_t frames = SamplesInToFrames(aBuffer.Length()); + AlignedBuffer<Value> temp1; + if (!temp1.SetLength(FramesOutToSamples(frames))) { + return AudioDataBuffer<Format, Value>(std::move(temp1)); + } + frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames); + if (mIn.Rate() == mOut.Rate()) { + MOZ_ALWAYS_TRUE(temp1.SetLength(FramesOutToSamples(frames))); + return AudioDataBuffer<Format, Value>(std::move(temp1)); + } + + // At this point, temp1 contains the buffer reordered and downmixed. + // If we are downsampling we can re-use it. + AlignedBuffer<Value>* outputBuffer = &temp1; + AlignedBuffer<Value> temp2; + if (!frames || mOut.Rate() > mIn.Rate()) { + // We are upsampling or about to drain, we can't work in place. + // Allocate another temporary buffer where the upsampling will occur. + if (!temp2.SetLength( + FramesOutToSamples(ResampleRecipientFrames(frames)))) { + return AudioDataBuffer<Format, Value>(std::move(temp2)); + } + outputBuffer = &temp2; + } + if (!frames) { + frames = DrainResampler(outputBuffer->Data()); + } else { + frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames); + } + MOZ_ALWAYS_TRUE(outputBuffer->SetLength(FramesOutToSamples(frames))); + return AudioDataBuffer<Format, Value>(std::move(*outputBuffer)); + } + + // Attempt to convert the AudioDataBuffer in place. + // Will return 0 if the conversion wasn't possible. + template <typename Value> + size_t Process(Value* aBuffer, size_t aFrames) { + MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format()); + if (!CanWorkInPlace()) { + return 0; + } + size_t frames = ProcessInternal(aBuffer, aBuffer, aFrames); + if (frames && mIn.Rate() != mOut.Rate()) { + frames = ResampleAudio(aBuffer, aBuffer, aFrames); + } + return frames; + } + + template <typename Value> + size_t Process(AlignedBuffer<Value>& aOutBuffer, const Value* aInBuffer, + size_t aFrames) { + MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format()); + MOZ_ASSERT((aFrames && aInBuffer) || !aFrames); + // Up/down mixing first + if (!aOutBuffer.SetLength(FramesOutToSamples(aFrames))) { + MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0)); + return 0; + } + size_t frames = ProcessInternal(aOutBuffer.Data(), aInBuffer, aFrames); + MOZ_ASSERT(frames == aFrames); + // Check if resampling is needed + if (mIn.Rate() == mOut.Rate()) { + return frames; + } + // Prepare output in cases of drain or up-sampling + if ((!frames || mOut.Rate() > mIn.Rate()) && + !aOutBuffer.SetLength( + FramesOutToSamples(ResampleRecipientFrames(frames)))) { + MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0)); + return 0; + } + if (!frames) { + frames = DrainResampler(aOutBuffer.Data()); + } else { + frames = ResampleAudio(aOutBuffer.Data(), aInBuffer, frames); + } + // Update with the actual buffer length + MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(FramesOutToSamples(frames))); + return frames; + } + + bool CanWorkInPlace() const; + bool CanReorderAudio() const { + return mIn.Layout().MappingTable(mOut.Layout()); + } + static bool CanConvert(const AudioConfig& aIn, const AudioConfig& aOut); + + const AudioConfig& InputConfig() const { return mIn; } + const AudioConfig& OutputConfig() const { return mOut; } + + private: + const AudioConfig mIn; + const AudioConfig mOut; + // mChannelOrderMap will be empty if we do not know how to proceed with this + // channel layout. + AutoTArray<uint8_t, AudioConfig::ChannelLayout::MAX_CHANNELS> + mChannelOrderMap; + /** + * ProcessInternal + * Parameters: + * aOut : destination buffer where converted samples will be copied + * aIn : source buffer + * aSamples: number of frames in source buffer + * + * Return Value: number of frames converted or 0 if error + */ + size_t ProcessInternal(void* aOut, const void* aIn, size_t aFrames); + void ReOrderInterleavedChannels(void* aOut, const void* aIn, + size_t aFrames) const; + size_t DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const; + size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const; + + size_t FramesOutToSamples(size_t aFrames) const; + size_t SamplesInToFrames(size_t aSamples) const; + size_t FramesOutToBytes(size_t aFrames) const; + + // Resampler context. + SpeexResamplerState* mResampler; + size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames); + size_t ResampleRecipientFrames(size_t aFrames) const; + void RecreateResampler(); + size_t DrainResampler(void* aOut); +}; + +} // namespace mozilla + +#endif /* AudioConverter_h */ |