/* -*- Mode: C++; tab-width: 8; 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/. */ #include "AudioConverter.h" #include /* * Parts derived from MythTV AudioConvert Class * Created by Jean-Yves Avenard. * * Copyright (C) Bubblestuff Pty Ltd 2013 * Copyright (C) foobum@gmail.com 2010 */ namespace mozilla { AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut) : mIn(aIn) , mOut(aOut) { MOZ_DIAGNOSTIC_ASSERT(aIn.Rate() == aOut.Rate() && aIn.Format() == aOut.Format() && aIn.Interleaved() == aOut.Interleaved(), "No format or rate conversion is supported at this stage"); MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) || aIn.Channels() == aOut.Channels(), "Only downmixing to mono or stereo is supported at this stage"); MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported"); mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap); } bool AudioConverter::CanWorkInPlace() const { return mIn.Channels() * mIn.Rate() * AudioConfig::SampleSize(mIn.Format()) >= mOut.Channels() * mOut.Rate() * AudioConfig::SampleSize(mOut.Format()); } size_t AudioConverter::Process(void* aOut, const void* aIn, size_t aBytes) { if (!CanWorkInPlace()) { return 0; } if (mIn.Channels() > mOut.Channels()) { return DownmixAudio(aOut, aIn, aBytes); } else if (mIn.Layout() != mOut.Layout() && CanReorderAudio()) { ReOrderInterleavedChannels(aOut, aIn, aBytes); } return aBytes; } // Reorder interleaved channels. // Can work in place (e.g aOut == aIn). template void _ReOrderInterleavedChannels(AudioDataType* aOut, const AudioDataType* aIn, uint32_t aFrames, uint32_t aChannels, const uint8_t* aChannelOrderMap) { MOZ_DIAGNOSTIC_ASSERT(aChannels <= MAX_AUDIO_CHANNELS); AudioDataType val[MAX_AUDIO_CHANNELS]; for (uint32_t i = 0; i < aFrames; i++) { for (uint32_t j = 0; j < aChannels; j++) { val[j] = aIn[aChannelOrderMap[j]]; } for (uint32_t j = 0; j < aChannels; j++) { aOut[j] = val[j]; } aOut += aChannels; aIn += aChannels; } } void AudioConverter::ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aDataSize) const { MOZ_DIAGNOSTIC_ASSERT(mIn.Channels() == mOut.Channels()); if (mOut.Layout() == mIn.Layout()) { return; } if (mOut.Channels() == 1) { // If channel count is 1, planar and non-planar formats are the same and // there's nothing to reorder. if (aOut != aIn) { memmove(aOut, aIn, aDataSize); } return; } uint32_t bits = AudioConfig::FormatToBits(mOut.Format()); switch (bits) { case 8: _ReOrderInterleavedChannels((uint8_t*)aOut, (const uint8_t*)aIn, aDataSize/sizeof(uint8_t)/mIn.Channels(), mIn.Channels(), mChannelOrderMap); break; case 16: _ReOrderInterleavedChannels((int16_t*)aOut,(const int16_t*)aIn, aDataSize/sizeof(int16_t)/mIn.Channels(), mIn.Channels(), mChannelOrderMap); break; default: MOZ_DIAGNOSTIC_ASSERT(AudioConfig::SampleSize(mOut.Format()) == 4); _ReOrderInterleavedChannels((int32_t*)aOut,(const int32_t*)aIn, aDataSize/sizeof(int32_t)/mIn.Channels(), mIn.Channels(), mChannelOrderMap); break; } } static inline int16_t clipTo15(int32_t aX) { return aX < -32768 ? -32768 : aX <= 32767 ? aX : 32767; } size_t AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const { MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 || mIn.Format() == AudioConfig::FORMAT_FLT); MOZ_ASSERT(mIn.Channels() >= mOut.Channels()); MOZ_ASSERT(mIn.Layout() == AudioConfig::ChannelLayout(mIn.Channels()), "Can only downmix input data in SMPTE layout"); MOZ_ASSERT(mOut.Layout() == AudioConfig::ChannelLayout(2) || mOut.Layout() == AudioConfig::ChannelLayout(1)); uint32_t channels = mIn.Channels(); uint32_t frames = aDataSize / AudioConfig::SampleSize(mOut.Format()) / channels; if (channels == 1 && mOut.Channels() == 1) { if (aOut != aIn) { memmove(aOut, aIn, aDataSize); } return aDataSize; } if (channels > 2) { if (mIn.Format() == AudioConfig::FORMAT_FLT) { // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8. static const float dmatrix[6][8][2]= { /*3*/{{0.5858f,0},{0,0.5858f},{0.4142f,0.4142f}}, /*4*/{{0.4226f,0},{0,0.4226f},{0.366f, 0.2114f},{0.2114f,0.366f}}, /*5*/{{0.6510f,0},{0,0.6510f},{0.4600f,0.4600f},{0.5636f,0.3254f},{0.3254f,0.5636f}}, /*6*/{{0.5290f,0},{0,0.5290f},{0.3741f,0.3741f},{0.3741f,0.3741f},{0.4582f,0.2645f},{0.2645f,0.4582f}}, /*7*/{{0.4553f,0},{0,0.4553f},{0.3220f,0.3220f},{0.3220f,0.3220f},{0.2788f,0.2788f},{0.3943f,0.2277f},{0.2277f,0.3943f}}, /*8*/{{0.3886f,0},{0,0.3886f},{0.2748f,0.2748f},{0.2748f,0.2748f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f}}, }; // Re-write the buffer with downmixed data const float* in = static_cast(aIn); float* out = static_cast(aOut); for (uint32_t i = 0; i < frames; i++) { float sampL = 0.0; float sampR = 0.0; for (uint32_t j = 0; j < channels; j++) { sampL += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][0]; sampR += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][1]; } *out++ = sampL; *out++ = sampR; } } else if (mIn.Format() == AudioConfig::FORMAT_S16) { // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8. // Coefficients in Q14. static const int16_t dmatrix[6][8][2]= { /*3*/{{9598, 0},{0, 9598},{6786,6786}}, /*4*/{{6925, 0},{0, 6925},{5997,3462},{3462,5997}}, /*5*/{{10663,0},{0, 10663},{7540,7540},{9234,5331},{5331,9234}}, /*6*/{{8668, 0},{0, 8668},{6129,6129},{6129,6129},{7507,4335},{4335,7507}}, /*7*/{{7459, 0},{0, 7459},{5275,5275},{5275,5275},{4568,4568},{6460,3731},{3731,6460}}, /*8*/{{6368, 0},{0, 6368},{4502,4502},{4502,4502},{5514,3184},{3184,5514},{5514,3184},{3184,5514}} }; // Re-write the buffer with downmixed data const int16_t* in = static_cast(aIn); int16_t* out = static_cast(aOut); for (uint32_t i = 0; i < frames; i++) { int32_t sampL = 0; int32_t sampR = 0; for (uint32_t j = 0; j < channels; j++) { sampL+=in[i*channels+j]*dmatrix[channels-3][j][0]; sampR+=in[i*channels+j]*dmatrix[channels-3][j][1]; } *out++ = clipTo15((sampL + 8192)>>14); *out++ = clipTo15((sampR + 8192)>>14); } } else { MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type"); } // If we are to continue downmixing to mono, start working on the output // buffer. aIn = aOut; channels = 2; } if (mOut.Channels() == 1) { if (mIn.Format() == AudioConfig::FORMAT_FLT) { const float* in = static_cast(aIn); float* out = static_cast(aOut); for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) { float sample = 0.0; // The sample of the buffer would be interleaved. sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5; *out++ = sample; } } else if (mIn.Format() == AudioConfig::FORMAT_S16) { const int16_t* in = static_cast(aIn); int16_t* out = static_cast(aOut); for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) { int32_t sample = 0.0; // The sample of the buffer would be interleaved. sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5; *out++ = sample; } } else { MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type"); } } return frames * AudioConfig::SampleSize(mOut.Format()) * mOut.Channels(); } } // namespace mozilla