2016-04-04 11:22:05 +03:00
|
|
|
/* -*- 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"
|
|
|
|
|
2016-04-11 14:07:11 +03:00
|
|
|
// Forward declaration
|
|
|
|
typedef struct SpeexResamplerState_ SpeexResamplerState;
|
|
|
|
|
2016-04-04 11:22:05 +03:00
|
|
|
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() {}
|
|
|
|
AudioDataBuffer(Value* aBuffer, size_t aLength)
|
|
|
|
: mBuffer(aBuffer, aLength)
|
|
|
|
{}
|
|
|
|
explicit AudioDataBuffer(const AudioDataBuffer& aOther)
|
|
|
|
: mBuffer(aOther.mBuffer)
|
|
|
|
{}
|
|
|
|
AudioDataBuffer(AudioDataBuffer&& aOther)
|
|
|
|
: mBuffer(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(Move(aBuffer))
|
|
|
|
{
|
|
|
|
static_assert(Format == AudioConfig::FORMAT_U8,
|
|
|
|
"Conversion not implemented yet");
|
|
|
|
}
|
|
|
|
explicit AudioDataBuffer(AlignedShortBuffer&& aBuffer)
|
|
|
|
: mBuffer(Move(aBuffer))
|
|
|
|
{
|
|
|
|
static_assert(Format == AudioConfig::FORMAT_S16,
|
|
|
|
"Conversion not implemented yet");
|
|
|
|
}
|
2016-10-01 05:02:46 +03:00
|
|
|
explicit AudioDataBuffer(AlignedFloatBuffer&& aBuffer)
|
2016-04-04 11:22:05 +03:00
|
|
|
: mBuffer(Move(aBuffer))
|
|
|
|
{
|
|
|
|
static_assert(Format == AudioConfig::FORMAT_FLT,
|
|
|
|
"Conversion not implemented yet");
|
|
|
|
}
|
2016-04-11 14:01:45 +03:00
|
|
|
AudioDataBuffer& operator=(AudioDataBuffer&& aOther)
|
|
|
|
{
|
|
|
|
mBuffer = Move(aOther.mBuffer);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
AudioDataBuffer& operator=(const AudioDataBuffer& aOther)
|
|
|
|
{
|
|
|
|
mBuffer = aOther.mBuffer;
|
|
|
|
return *this;
|
|
|
|
}
|
2016-04-04 11:22:05 +03:00
|
|
|
|
|
|
|
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 Move(mBuffer);
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
AlignedBuffer<Value> mBuffer;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef AudioDataBuffer<AudioConfig::FORMAT_DEFAULT> AudioSampleBuffer;
|
|
|
|
|
|
|
|
class AudioConverter {
|
|
|
|
public:
|
|
|
|
AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut);
|
2016-04-11 14:07:11 +03:00
|
|
|
~AudioConverter();
|
2016-04-04 11:22:05 +03:00
|
|
|
|
2016-04-11 14:07:11 +03:00
|
|
|
// Convert the AudioDataBuffer.
|
|
|
|
// Conversion will be done in place if possible. Otherwise a new buffer will
|
|
|
|
// be returned.
|
2016-04-14 08:44:02 +03:00
|
|
|
// Providing an empty buffer and resampling is expected, the resampler
|
|
|
|
// will be drained.
|
2016-04-11 14:07:11 +03:00
|
|
|
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 = Move(aBuffer);
|
|
|
|
if (CanWorkInPlace()) {
|
2016-04-12 05:16:38 +03:00
|
|
|
size_t frames = SamplesInToFrames(buffer.Length());
|
|
|
|
frames = ProcessInternal(buffer.Data(), buffer.Data(), frames);
|
|
|
|
if (frames && mIn.Rate() != mOut.Rate()) {
|
|
|
|
frames = ResampleAudio(buffer.Data(), buffer.Data(), frames);
|
2016-04-11 14:07:11 +03:00
|
|
|
}
|
|
|
|
AlignedBuffer<Value> temp = buffer.Forget();
|
2016-04-12 05:16:38 +03:00
|
|
|
temp.SetLength(FramesOutToSamples(frames));
|
2016-04-11 14:07:11 +03:00
|
|
|
return AudioDataBuffer<Format, Value>(Move(temp));;
|
|
|
|
}
|
|
|
|
return Process(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <AudioConfig::SampleFormat Format, typename Value>
|
|
|
|
AudioDataBuffer<Format, Value> Process(const AudioDataBuffer<Format, Value>& aBuffer)
|
2016-04-04 11:22:05 +03:00
|
|
|
{
|
2016-04-11 14:07:11 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Format);
|
|
|
|
// Perform the downmixing / reordering in temporary buffer.
|
2016-04-12 05:16:38 +03:00
|
|
|
size_t frames = SamplesInToFrames(aBuffer.Length());
|
2016-04-11 14:07:11 +03:00
|
|
|
AlignedBuffer<Value> temp1;
|
2016-04-12 05:16:38 +03:00
|
|
|
if (!temp1.SetLength(FramesOutToSamples(frames))) {
|
2016-04-11 14:07:11 +03:00
|
|
|
return AudioDataBuffer<Format, Value>(Move(temp1));
|
|
|
|
}
|
2016-04-12 05:16:38 +03:00
|
|
|
frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames);
|
2016-04-14 08:44:02 +03:00
|
|
|
if (mIn.Rate() == mOut.Rate()) {
|
2016-05-11 13:15:36 +03:00
|
|
|
MOZ_ALWAYS_TRUE(temp1.SetLength(FramesOutToSamples(frames)));
|
2016-04-11 14:07:11 +03:00
|
|
|
return AudioDataBuffer<Format, Value>(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;
|
2016-04-14 08:44:02 +03:00
|
|
|
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.
|
2016-05-11 13:15:36 +03:00
|
|
|
if (!temp2.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)))) {
|
|
|
|
return AudioDataBuffer<Format, Value>(Move(temp2));
|
|
|
|
}
|
2016-04-11 14:07:11 +03:00
|
|
|
outputBuffer = &temp2;
|
|
|
|
}
|
2016-04-14 08:44:02 +03:00
|
|
|
if (!frames) {
|
|
|
|
frames = DrainResampler(outputBuffer->Data());
|
|
|
|
} else {
|
|
|
|
frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
|
|
|
|
}
|
2016-05-11 13:15:36 +03:00
|
|
|
MOZ_ALWAYS_TRUE(outputBuffer->SetLength(FramesOutToSamples(frames)));
|
2016-04-11 14:07:11 +03:00
|
|
|
return AudioDataBuffer<Format, Value>(Move(*outputBuffer));
|
2016-04-04 11:22:05 +03:00
|
|
|
}
|
2016-04-11 14:07:11 +03:00
|
|
|
|
|
|
|
// Attempt to convert the AudioDataBuffer in place.
|
|
|
|
// Will return 0 if the conversion wasn't possible.
|
2016-04-08 10:32:57 +03:00
|
|
|
template <typename Value>
|
2016-04-12 05:16:38 +03:00
|
|
|
size_t Process(Value* aBuffer, size_t aFrames)
|
2016-04-08 10:32:57 +03:00
|
|
|
{
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
|
2016-04-11 14:07:11 +03:00
|
|
|
if (!CanWorkInPlace()) {
|
|
|
|
return 0;
|
|
|
|
}
|
2016-04-12 05:16:38 +03:00
|
|
|
size_t frames = ProcessInternal(aBuffer, aBuffer, aFrames);
|
|
|
|
if (frames && mIn.Rate() != mOut.Rate()) {
|
|
|
|
frames = ResampleAudio(aBuffer, aBuffer, aFrames);
|
2016-04-11 14:07:11 +03:00
|
|
|
}
|
2016-04-12 05:16:38 +03:00
|
|
|
return frames;
|
2016-04-08 10:32:57 +03:00
|
|
|
}
|
2016-04-11 14:07:11 +03:00
|
|
|
|
2016-04-04 11:22:05 +03:00
|
|
|
bool CanWorkInPlace() const;
|
|
|
|
bool CanReorderAudio() const
|
|
|
|
{
|
2016-04-07 08:26:20 +03:00
|
|
|
return mIn.Layout().MappingTable(mOut.Layout());
|
2016-04-04 11:22:05 +03:00
|
|
|
}
|
|
|
|
|
2016-04-11 14:04:15 +03:00
|
|
|
const AudioConfig& InputConfig() const { return mIn; }
|
|
|
|
const AudioConfig& OutputConfig() const { return mOut; }
|
|
|
|
|
2016-04-04 11:22:05 +03:00
|
|
|
private:
|
|
|
|
const AudioConfig mIn;
|
|
|
|
const AudioConfig mOut;
|
2016-04-07 08:26:20 +03:00
|
|
|
uint8_t mChannelOrderMap[MAX_AUDIO_CHANNELS];
|
2016-04-04 11:22:05 +03:00
|
|
|
/**
|
2016-04-11 14:07:11 +03:00
|
|
|
* ProcessInternal
|
2016-04-04 11:22:05 +03:00
|
|
|
* Parameters:
|
|
|
|
* aOut : destination buffer where converted samples will be copied
|
|
|
|
* aIn : source buffer
|
2016-04-12 05:16:38 +03:00
|
|
|
* aSamples: number of frames in source buffer
|
2016-04-04 11:22:05 +03:00
|
|
|
*
|
2016-04-12 05:16:38 +03:00
|
|
|
* Return Value: number of frames converted or 0 if error
|
2016-04-04 11:22:05 +03:00
|
|
|
*/
|
2016-04-12 05:16:38 +03:00
|
|
|
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;
|
2016-04-13 12:50:54 +03:00
|
|
|
size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
|
2016-04-12 05:16:38 +03:00
|
|
|
|
|
|
|
size_t FramesOutToSamples(size_t aFrames) const;
|
|
|
|
size_t SamplesInToFrames(size_t aSamples) const;
|
|
|
|
size_t FramesOutToBytes(size_t aFrames) const;
|
2016-04-11 14:07:11 +03:00
|
|
|
|
|
|
|
// Resampler context.
|
|
|
|
SpeexResamplerState* mResampler;
|
2016-04-12 05:16:38 +03:00
|
|
|
size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames);
|
2016-04-11 14:07:11 +03:00
|
|
|
size_t ResampleRecipientFrames(size_t aFrames) const;
|
2016-04-14 08:44:02 +03:00
|
|
|
void RecreateResampler();
|
|
|
|
size_t DrainResampler(void* aOut);
|
2016-04-04 11:22:05 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace mozilla
|
|
|
|
|
|
|
|
#endif /* AudioConverter_h */
|