michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AudioChannelFormat.h" michael@0: #include "nsTArray.h" michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: michael@0: enum { michael@0: SURROUND_L, michael@0: SURROUND_R, michael@0: SURROUND_C, michael@0: SURROUND_LFE, michael@0: SURROUND_SL, michael@0: SURROUND_SR michael@0: }; michael@0: michael@0: static const uint32_t CUSTOM_CHANNEL_LAYOUTS = 6; michael@0: michael@0: static const int IGNORE = CUSTOM_CHANNEL_LAYOUTS; michael@0: static const float IGNORE_F = 0.0f; michael@0: michael@0: uint32_t michael@0: GetAudioChannelsSuperset(uint32_t aChannels1, uint32_t aChannels2) michael@0: { michael@0: return std::max(aChannels1, aChannels2); michael@0: } michael@0: michael@0: /** michael@0: * UpMixMatrix represents a conversion matrix by exploiting the fact that michael@0: * each output channel comes from at most one input channel. michael@0: */ michael@0: struct UpMixMatrix { michael@0: uint8_t mInputDestination[CUSTOM_CHANNEL_LAYOUTS]; michael@0: }; michael@0: michael@0: static const UpMixMatrix michael@0: gUpMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(CUSTOM_CHANNEL_LAYOUTS - 1)/2] = michael@0: { michael@0: // Upmixes from mono michael@0: { { 0, 0 } }, michael@0: { { 0, IGNORE, IGNORE } }, michael@0: { { 0, 0, IGNORE, IGNORE } }, michael@0: { { 0, IGNORE, IGNORE, IGNORE, IGNORE } }, michael@0: { { IGNORE, IGNORE, 0, IGNORE, IGNORE, IGNORE } }, michael@0: // Upmixes from stereo michael@0: { { 0, 1, IGNORE } }, michael@0: { { 0, 1, IGNORE, IGNORE } }, michael@0: { { 0, 1, IGNORE, IGNORE, IGNORE } }, michael@0: { { 0, 1, IGNORE, IGNORE, IGNORE, IGNORE } }, michael@0: // Upmixes from 3-channel michael@0: { { 0, 1, 2, IGNORE } }, michael@0: { { 0, 1, 2, IGNORE, IGNORE } }, michael@0: { { 0, 1, 2, IGNORE, IGNORE, IGNORE } }, michael@0: // Upmixes from quad michael@0: { { 0, 1, 2, 3, IGNORE } }, michael@0: { { 0, 1, IGNORE, IGNORE, 2, 3 } }, michael@0: // Upmixes from 5-channel michael@0: { { 0, 1, 2, 3, 4, IGNORE } } michael@0: }; michael@0: michael@0: static const int gMixingMatrixIndexByChannels[CUSTOM_CHANNEL_LAYOUTS - 1] = michael@0: { 0, 5, 9, 12, 14 }; michael@0: michael@0: void michael@0: AudioChannelsUpMix(nsTArray* aChannelArray, michael@0: uint32_t aOutputChannelCount, michael@0: const void* aZeroChannel) michael@0: { michael@0: uint32_t inputChannelCount = aChannelArray->Length(); michael@0: uint32_t outputChannelCount = michael@0: GetAudioChannelsSuperset(aOutputChannelCount, inputChannelCount); michael@0: NS_ASSERTION(outputChannelCount > inputChannelCount, michael@0: "No up-mix needed"); michael@0: NS_ASSERTION(inputChannelCount > 0, "Bad number of channels"); michael@0: NS_ASSERTION(outputChannelCount > 0, "Bad number of channels"); michael@0: michael@0: aChannelArray->SetLength(outputChannelCount); michael@0: michael@0: if (inputChannelCount < CUSTOM_CHANNEL_LAYOUTS && michael@0: outputChannelCount <= CUSTOM_CHANNEL_LAYOUTS) { michael@0: const UpMixMatrix& m = gUpMixMatrices[ michael@0: gMixingMatrixIndexByChannels[inputChannelCount - 1] + michael@0: outputChannelCount - inputChannelCount - 1]; michael@0: michael@0: const void* outputChannels[CUSTOM_CHANNEL_LAYOUTS]; michael@0: michael@0: for (uint32_t i = 0; i < outputChannelCount; ++i) { michael@0: uint8_t channelIndex = m.mInputDestination[i]; michael@0: if (channelIndex == IGNORE) { michael@0: outputChannels[i] = aZeroChannel; michael@0: } else { michael@0: outputChannels[i] = aChannelArray->ElementAt(channelIndex); michael@0: } michael@0: } michael@0: for (uint32_t i = 0; i < outputChannelCount; ++i) { michael@0: aChannelArray->ElementAt(i) = outputChannels[i]; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: for (uint32_t i = inputChannelCount; i < outputChannelCount; ++i) { michael@0: aChannelArray->ElementAt(i) = aZeroChannel; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * DownMixMatrix represents a conversion matrix efficiently by exploiting the michael@0: * fact that each input channel contributes to at most one output channel, michael@0: * except possibly for the C input channel in layouts that have one. Also, michael@0: * every input channel is multiplied by the same coefficient for every output michael@0: * channel it contributes to. michael@0: */ michael@0: struct DownMixMatrix { michael@0: // Every input channel c is copied to output channel mInputDestination[c] michael@0: // after multiplying by mInputCoefficient[c]. michael@0: uint8_t mInputDestination[CUSTOM_CHANNEL_LAYOUTS]; michael@0: // If not IGNORE, then the C channel is copied to this output channel after michael@0: // multiplying by its coefficient. michael@0: uint8_t mCExtraDestination; michael@0: float mInputCoefficient[CUSTOM_CHANNEL_LAYOUTS]; michael@0: }; michael@0: michael@0: static const DownMixMatrix michael@0: gDownMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(CUSTOM_CHANNEL_LAYOUTS - 1)/2] = michael@0: { michael@0: // Downmixes to mono michael@0: { { 0, 0 }, IGNORE, { 0.5f, 0.5f } }, michael@0: { { 0, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F } }, michael@0: { { 0, 0, 0, 0 }, IGNORE, { 0.25f, 0.25f, 0.25f, 0.25f } }, michael@0: { { 0, IGNORE, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F, IGNORE_F, IGNORE_F } }, michael@0: { { 0, 0, 0, IGNORE, 0, 0 }, IGNORE, { 0.7071f, 0.7071f, 1.0f, IGNORE_F, 0.5f, 0.5f } }, michael@0: // Downmixes to stereo michael@0: { { 0, 1, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F } }, michael@0: { { 0, 1, 0, 1 }, IGNORE, { 0.5f, 0.5f, 0.5f, 0.5f } }, michael@0: { { 0, 1, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } }, michael@0: { { 0, 1, 0, IGNORE, 0, 1 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 0.7071f, 0.7071f } }, michael@0: // Downmixes to 3-channel michael@0: { { 0, 1, 2, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F } }, michael@0: { { 0, 1, 2, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F } }, michael@0: { { 0, 1, 2, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } }, michael@0: // Downmixes to quad michael@0: { { 0, 1, 2, 3, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } }, michael@0: { { 0, 1, 0, IGNORE, 2, 3 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 1.0f, 1.0f } }, michael@0: // Downmixes to 5-channel michael@0: { { 0, 1, 2, 3, 4, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } } michael@0: }; michael@0: michael@0: void michael@0: AudioChannelsDownMix(const nsTArray& aChannelArray, michael@0: float** aOutputChannels, michael@0: uint32_t aOutputChannelCount, michael@0: uint32_t aDuration) michael@0: { michael@0: uint32_t inputChannelCount = aChannelArray.Length(); michael@0: const void* const* inputChannels = aChannelArray.Elements(); michael@0: NS_ASSERTION(inputChannelCount > aOutputChannelCount, "Nothing to do"); michael@0: michael@0: if (inputChannelCount > 6) { michael@0: // Just drop the unknown channels. michael@0: for (uint32_t o = 0; o < aOutputChannelCount; ++o) { michael@0: memcpy(aOutputChannels[o], inputChannels[o], aDuration*sizeof(float)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Ignore unknown channels, they're just dropped. michael@0: inputChannelCount = std::min(6, inputChannelCount); michael@0: michael@0: const DownMixMatrix& m = gDownMixMatrices[ michael@0: gMixingMatrixIndexByChannels[aOutputChannelCount - 1] + michael@0: inputChannelCount - aOutputChannelCount - 1]; michael@0: michael@0: // This is slow, but general. We can define custom code for special michael@0: // cases later. michael@0: for (uint32_t s = 0; s < aDuration; ++s) { michael@0: // Reserve an extra junk channel at the end for the cases where we michael@0: // want an input channel to contribute to nothing michael@0: float outputChannels[CUSTOM_CHANNEL_LAYOUTS + 1]; michael@0: memset(outputChannels, 0, sizeof(float)*(CUSTOM_CHANNEL_LAYOUTS)); michael@0: for (uint32_t c = 0; c < inputChannelCount; ++c) { michael@0: outputChannels[m.mInputDestination[c]] += michael@0: m.mInputCoefficient[c]*(static_cast(inputChannels[c]))[s]; michael@0: } michael@0: // Utilize the fact that in every layout, C is the third channel. michael@0: if (m.mCExtraDestination != IGNORE) { michael@0: outputChannels[m.mCExtraDestination] += michael@0: m.mInputCoefficient[SURROUND_C]*(static_cast(inputChannels[SURROUND_C]))[s]; michael@0: } michael@0: michael@0: for (uint32_t c = 0; c < aOutputChannelCount; ++c) { michael@0: aOutputChannels[c][s] = outputChannels[c]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: }