blob: a4ded79b714a8852e3d4236f9e15f0104e13c838 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "channels.h"
#include <math.h>
namespace android::audio_utils::channels {
// sparseChannelMatrixMultiply must be compiled with specific compiler flags
// for optimization. The body is in ChannelMix.cpp.
template <audio_channel_mask_t INPUT_CHANNEL_MASK,
audio_channel_mask_t OUTPUT_CHANNEL_MASK, bool ACCUMULATE>
bool sparseChannelMatrixMultiply(const float *src, float *dst, size_t frameCount);
inline float clamp(float value) {
constexpr float LIMIT_AMPLITUDE = M_SQRT2; // 3dB = 1.41421356
return fmin(fmax(value, -LIMIT_AMPLITUDE), LIMIT_AMPLITUDE);
}
// This method can be evaluated constexpr.
template <audio_channel_mask_t OUTPUT_CHANNEL_MASK, size_t M>
constexpr bool fillChannelMatrix(audio_channel_mask_t INPUT_CHANNEL_MASK,
float (&matrix)[M][audio_channel_count_from_out_mask(OUTPUT_CHANNEL_MASK)]) {
// This is a bit long since there is no functional partial template specialization.
if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_STEREO) {
// Compute at what index each channel is: samples will be in the following order:
// FL FR FC LFE BL BR BC SL SR
//
// Prior to API 32, use of downmix resulted in channels being scaled in half amplitude.
// We now use a compliant downmix matrix for 5.1 with the following standards:
// ITU-R 775-2, ATSC A/52, ETSI TS 101 154, IEC 14496-3, which is unity gain for the
// front left and front right channel contribution.
//
// For 7.1 to 5.1 we set equal contributions for the side and back channels
// which follow Dolby downmix recommendations.
//
// We add contributions from the LFE into the L and R channels
// at a weight of 0.5 (rather than the power preserving 0.707)
// which is to ensure that headphones can still experience LFE
// with lesser risk of speaker overload.
//
// Note: geometrically left and right channels contribute only to the corresponding
// left and right outputs respectively. Geometrically center channels contribute
// to both left and right outputs, so they are scaled by 0.707 to preserve power.
//
// (transfer matrix)
// FL FR FC LFE BL BR BC SL SR
// 1.0 0.707 0.5 0.707 0.5 0.707
// 1.0 0.707 0.5 0.707 0.5 0.707
size_t index = 0;
constexpr float COEF_25 = 0.2508909536f;
constexpr float COEF_35 = 0.3543928915f;
constexpr float COEF_36 = 0.3552343859f;
constexpr float COEF_61 = 0.6057043428f;
constexpr float MINUS_3_DB_IN_FLOAT = M_SQRT1_2; // -3dB = 0.70710678
constexpr size_t FL = 0;
constexpr size_t FR = 1;
for (unsigned tmp = INPUT_CHANNEL_MASK; tmp != 0; ++index) {
if (index >= M) return false;
const unsigned lowestBit = tmp & -(signed)tmp;
switch (lowestBit) {
case AUDIO_CHANNEL_OUT_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT:
matrix[index][FL] = 1.f;
matrix[index][FR] = 0.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_LEFT:
case AUDIO_CHANNEL_OUT_BACK_LEFT:
case AUDIO_CHANNEL_OUT_TOP_BACK_LEFT:
case AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT: // FRONT_WIDE closer to SIDE.
matrix[index][FL] = MINUS_3_DB_IN_FLOAT;
matrix[index][FR] = 0.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT:
matrix[index][FL] = 0.f;
matrix[index][FR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_RIGHT:
case AUDIO_CHANNEL_OUT_BACK_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT:
case AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT: // FRONT_WIDE closer to SIDE.
matrix[index][FL] = 0.f;
matrix[index][FR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER:
matrix[index][FL] = matrix[index][FR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT:
matrix[index][FL] = COEF_61;
matrix[index][FR] = 0.f;
break;
case AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT:
matrix[index][FL] = 0.f;
matrix[index][FR] = COEF_61;
break;
case AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER:
matrix[index][FL] = COEF_61;
matrix[index][FR] = COEF_25;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER:
matrix[index][FL] = COEF_25;
matrix[index][FR] = COEF_61;
break;
case AUDIO_CHANNEL_OUT_TOP_CENTER:
matrix[index][FL] = matrix[index][FR] = COEF_36;
break;
case AUDIO_CHANNEL_OUT_TOP_BACK_CENTER:
matrix[index][FL] = matrix[index][FR] = COEF_35;
break;
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2:
matrix[index][FL] = 0.f;
matrix[index][FR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY:
if (INPUT_CHANNEL_MASK & AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2) {
matrix[index][FL] = MINUS_3_DB_IN_FLOAT;
matrix[index][FR] = 0.f;
break;
}
FALLTHROUGH_INTENDED;
case AUDIO_CHANNEL_OUT_BACK_CENTER:
matrix[index][FL] = matrix[index][FR] = 0.5f;
break;
}
tmp ^= lowestBit;
}
return true;
} else if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_5POINT1) {
// FL FR FC LFE BL BR
size_t index = 0;
constexpr float MINUS_3_DB_IN_FLOAT = M_SQRT1_2; // -3dB = 0.70710678
constexpr float MINUS_4_5_DB_IN_FLOAT = 0.5946035575f;
constexpr size_t FL = 0;
constexpr size_t FR = 1;
constexpr size_t FC = 2;
constexpr size_t LFE = 3;
constexpr size_t BL = 4;
constexpr size_t BR = 5;
for (unsigned tmp = INPUT_CHANNEL_MASK; tmp != 0; ++index) {
if (index >= M) return false;
const unsigned lowestBit = tmp & -(signed)tmp;
matrix[index][FL] = matrix[index][FR] = matrix[index][FC] = 0.f;
matrix[index][LFE] = matrix[index][BL] = matrix[index][BR] = 0.f;
switch (lowestBit) {
case AUDIO_CHANNEL_OUT_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT:
matrix[index][FL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT:
matrix[index][FR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER:
matrix[index][FC] = 1.f;
break;
// ADJUST
case AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT: // FRONT_WIDE closer to SIDE.
matrix[index][FL] = MINUS_3_DB_IN_FLOAT;
matrix[index][BL] = MINUS_4_5_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT: // FRONT_WIDE closer to SIDE.
matrix[index][FR] = MINUS_3_DB_IN_FLOAT;
matrix[index][BR] = MINUS_4_5_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER:
matrix[index][FL] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER:
matrix[index][FR] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_SIDE_LEFT:
case AUDIO_CHANNEL_OUT_BACK_LEFT:
case AUDIO_CHANNEL_OUT_TOP_BACK_LEFT:
matrix[index][BL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_RIGHT:
case AUDIO_CHANNEL_OUT_BACK_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT:
matrix[index][BR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT:
matrix[index][BL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT:
matrix[index][BR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_BACK_CENTER:
case AUDIO_CHANNEL_OUT_BACK_CENTER:
matrix[index][BL] = matrix[index][BR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_TOP_CENTER:
matrix[index][FC] = matrix[index][BL] = matrix[index][BR] = 0.5f;
break;
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY:
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2:
matrix[index][LFE] = 1.f;
break;
}
tmp ^= lowestBit;
}
return true;
} else if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_7POINT1) {
// FL FR FC LFE BL BR SL SR
size_t index = 0;
constexpr float MINUS_3_DB_IN_FLOAT = M_SQRT1_2; // -3dB = 0.70710678
constexpr float MINUS_4_5_DB_IN_FLOAT = 0.5946035575f;
constexpr size_t FL = 0;
constexpr size_t FR = 1;
constexpr size_t FC = 2;
constexpr size_t LFE = 3;
constexpr size_t BL = 4;
constexpr size_t BR = 5;
constexpr size_t SL = 6;
constexpr size_t SR = 7;
for (unsigned tmp = INPUT_CHANNEL_MASK; tmp != 0; ++index) {
if (index >= M) return false;
const unsigned lowestBit = tmp & -(signed)tmp;
matrix[index][FL] = matrix[index][FR] = matrix[index][FC] = 0.f;
matrix[index][LFE] = matrix[index][BL] = matrix[index][BR] = 0.f;
switch (lowestBit) {
case AUDIO_CHANNEL_OUT_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT:
matrix[index][FL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT:
matrix[index][FR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER:
matrix[index][FC] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT: // FRONT_WIDE closer to SIDE.
matrix[index][FL] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][SL] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT: // FRONT_WIDE closer to SIDE.
matrix[index][FR] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][SR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER:
matrix[index][FL] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER:
matrix[index][FR] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_BACK_LEFT:
case AUDIO_CHANNEL_OUT_TOP_BACK_LEFT:
matrix[index][BL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_BACK_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT:
matrix[index][BR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_LEFT:
case AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT:
matrix[index][SL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT:
matrix[index][SR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_BACK_CENTER:
case AUDIO_CHANNEL_OUT_BACK_CENTER:
matrix[index][BL] = matrix[index][BR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_TOP_CENTER:
matrix[index][FC] = matrix[index][BL] = matrix[index][BR] = 0.5f;
break;
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY:
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2:
matrix[index][LFE] = 1.f;
break;
}
tmp ^= lowestBit;
}
return true;
} else if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_7POINT1POINT4) {
// FL FR FC LFE BL BR SL SR TFL TFR TBL TBR
size_t index = 0;
constexpr float MINUS_3_DB_IN_FLOAT = M_SQRT1_2; // -3dB = 0.70710678
constexpr float MINUS_4_5_DB_IN_FLOAT = 0.5946035575f;
constexpr size_t FL = 0;
constexpr size_t FR = 1;
constexpr size_t FC = 2;
constexpr size_t LFE = 3;
constexpr size_t BL = 4;
constexpr size_t BR = 5;
constexpr size_t SL = 6;
constexpr size_t SR = 7;
constexpr size_t TFL = 8;
constexpr size_t TFR = 9;
constexpr size_t TBL = 10;
constexpr size_t TBR = 11;
for (unsigned tmp = INPUT_CHANNEL_MASK; tmp != 0; ++index) {
if (index >= M) return false;
const unsigned lowestBit = tmp & -(signed)tmp;
matrix[index][FL] = matrix[index][FR] = matrix[index][FC] = 0.f;
matrix[index][LFE] = matrix[index][BL] = matrix[index][BR] = 0.f;
switch (lowestBit) {
case AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT:
matrix[index][TFL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT:
matrix[index][TFR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER:
matrix[index][TFL] = matrix[index][TFR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_LEFT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_LEFT:
matrix[index][FL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_RIGHT:
matrix[index][FR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_CENTER:
case AUDIO_CHANNEL_OUT_BOTTOM_FRONT_CENTER:
matrix[index][FC] = 1.f;
break;
case AUDIO_CHANNEL_OUT_FRONT_WIDE_LEFT: // FRONT_WIDE closer to SIDE.
matrix[index][FL] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][SL] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_WIDE_RIGHT: // FRONT_WIDE closer to SIDE.
matrix[index][FR] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][SR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER:
matrix[index][FL] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER:
matrix[index][FR] = MINUS_4_5_DB_IN_FLOAT;
matrix[index][FC] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_BACK_LEFT:
case AUDIO_CHANNEL_OUT_TOP_BACK_LEFT:
matrix[index][BL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_BACK_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT:
matrix[index][BR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_LEFT:
case AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT:
matrix[index][SL] = 1.f;
break;
case AUDIO_CHANNEL_OUT_SIDE_RIGHT:
case AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT:
matrix[index][SR] = 1.f;
break;
case AUDIO_CHANNEL_OUT_TOP_BACK_CENTER:
matrix[index][TBL] = matrix[index][TBR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_BACK_CENTER:
matrix[index][BL] = matrix[index][BR] = MINUS_3_DB_IN_FLOAT;
break;
case AUDIO_CHANNEL_OUT_TOP_CENTER:
matrix[index][TFL] = matrix[index][TFR] = 0.5f;
matrix[index][TBL] = matrix[index][TBR] = 0.5f;
break;
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY:
case AUDIO_CHANNEL_OUT_LOW_FREQUENCY_2:
matrix[index][LFE] = 1.f;
break;
}
tmp ^= lowestBit;
}
return true;
} else /* constexpr */ {
// We only accept NONE here as we don't do anything in that case.
static_assert(OUTPUT_CHANNEL_MASK==AUDIO_CHANNEL_NONE);
return true;
}
return false;
}
class IChannelMix {
public:
virtual ~IChannelMix() = default;
/**
* Set the input channel mask.
*
* \param inputChannelMask channel position mask for input data.
*
* \return false if the channel mask is not supported.
*/
virtual bool setInputChannelMask(audio_channel_mask_t inputChannelMask) = 0;
/**
* Returns the input channel mask.
*/
virtual audio_channel_mask_t getInputChannelMask() const = 0;
/**
* Remixes audio data in src to dst.
*
* \param src input audio buffer to remix
* \param dst remixed audio samples
* \param frameCount number of frames to remix
* \param accumulate is true if the remix is added to the destination or
* false if the remix replaces the destination.
*
* \return false if the channel mask set is not supported.
*/
virtual bool process(
const float *src, float *dst, size_t frameCount, bool accumulate) const = 0;
/**
* Remixes audio data in src to dst.
*
* \param src input audio buffer to remix
* \param dst remixed audio samples
* \param frameCount number of frames to remix
* \param accumulate is true if the remix is added to the destination or
* false if the remix replaces the destination.
* \param inputChannelMask channel position mask for input data.
*
* \return false if the channel mask set is not supported.
*/
virtual bool process(const float *src, float *dst, size_t frameCount, bool accumulate,
audio_channel_mask_t inputChannelMask) = 0;
/** Built in ChannelMix factory. */
static std::shared_ptr<IChannelMix> create(audio_channel_mask_t outputChannelMask);
/** Returns true if the Built-in factory supports the outputChannelMask */
static bool isOutputChannelMaskSupported(audio_channel_mask_t outputChannelMask);
};
/**
* ChannelMix
*
* Converts audio streams with different positional channel configurations.
*
*/
template <audio_channel_mask_t OUTPUT_CHANNEL_MASK>
class ChannelMix : public IChannelMix {
public:
/**
* Creates a ChannelMix object
*
* Note: If construction is unsuccessful then getInputChannelMask will return
* AUDIO_CHANNEL_NONE.
*
* \param inputChannelMask channel position mask for input audio data.
*/
explicit ChannelMix(audio_channel_mask_t inputChannelMask) {
setInputChannelMask(inputChannelMask);
}
ChannelMix() = default;
bool setInputChannelMask(audio_channel_mask_t inputChannelMask) override {
if (mInputChannelMask != inputChannelMask) {
if (inputChannelMask & ~((1 << MAX_INPUT_CHANNELS_SUPPORTED) - 1)) {
return false; // not channel position mask, or has unknown channels.
}
if (!fillChannelMatrix<OUTPUT_CHANNEL_MASK>(inputChannelMask, mMatrix)) {
return false; // missized matrix.
}
mInputChannelMask = inputChannelMask;
mInputChannelCount = audio_channel_count_from_out_mask(inputChannelMask);
}
return true;
}
audio_channel_mask_t getInputChannelMask() const override {
return mInputChannelMask;
}
bool process(const float *src, float *dst, size_t frameCount,
bool accumulate) const override {
return accumulate ? processSwitch<true>(src, dst, frameCount)
: processSwitch<false>(src, dst, frameCount);
}
bool process(const float *src, float *dst, size_t frameCount,
bool accumulate, audio_channel_mask_t inputChannelMask) override {
return setInputChannelMask(inputChannelMask) && process(src, dst, frameCount, accumulate);
}
// The maximum channels supported (bits in the channel mask).
static constexpr size_t MAX_INPUT_CHANNELS_SUPPORTED = FCC_26;
private:
// Static/const parameters.
static inline constexpr audio_channel_mask_t mOutputChannelMask = OUTPUT_CHANNEL_MASK;
static inline constexpr size_t mOutputChannelCount =
audio_channel_count_from_out_mask(OUTPUT_CHANNEL_MASK);
static inline constexpr float MINUS_3_DB_IN_FLOAT = M_SQRT1_2; // -3dB = 0.70710678
// These values are modified only when the input channel mask changes.
// Keep alignment for matrix for more stable benchmarking.
//
// DO NOT change the order of these variables without running
// atest channelmix_benchmark
alignas(128) float mMatrix[MAX_INPUT_CHANNELS_SUPPORTED][mOutputChannelCount];
audio_channel_mask_t mInputChannelMask = AUDIO_CHANNEL_NONE;
size_t mInputChannelCount = 0;
/**
* Remixes audio data in src to dst.
*
* ACCUMULATE is true if the remix is added to the destination or
* false if the remix replaces the destination.
*
* \param src multichannel audio buffer to remix
* \param dst remixed audio samples
* \param frameCount number of multichannel frames to remix
*
* \return false if the CHANNEL_COUNT is not supported.
*/
template <bool ACCUMULATE>
bool processSwitch(const float *src, float *dst, size_t frameCount) const {
constexpr bool ANDROID_SPECIFIC = true; // change for testing.
if constexpr (ANDROID_SPECIFIC) {
if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_STEREO
|| OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_5POINT1) {
switch (mInputChannelMask) {
case AUDIO_CHANNEL_OUT_STEREO:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_STEREO,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_QUAD_BACK:
case AUDIO_CHANNEL_OUT_QUAD_SIDE:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_QUAD_BACK,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1_BACK:
case AUDIO_CHANNEL_OUT_5POINT1_SIDE:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1_BACK,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1POINT4:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1POINT4,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1POINT4:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1POINT4,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_22POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_22POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
default:
break; // handled below.
}
} else if constexpr (OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_7POINT1
|| OUTPUT_CHANNEL_MASK == AUDIO_CHANNEL_OUT_7POINT1POINT4) {
switch (mInputChannelMask) {
case AUDIO_CHANNEL_OUT_STEREO:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_STEREO,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_QUAD_BACK:
// Note: case AUDIO_CHANNEL_OUT_QUAD_SIDE is not equivalent.
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_QUAD_BACK,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1_BACK:
// Note: case AUDIO_CHANNEL_OUT_5POINT1_SIDE is not equivalent.
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1_BACK,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_5POINT1POINT4:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_5POINT1POINT4,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_7POINT1POINT4:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_7POINT1POINT4,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
case AUDIO_CHANNEL_OUT_22POINT2:
return sparseChannelMatrixMultiply<AUDIO_CHANNEL_OUT_22POINT2,
OUTPUT_CHANNEL_MASK, ACCUMULATE>(src, dst, frameCount);
default:
break; // handled below.
}
}
}
return matrixProcess(src, dst, frameCount, ACCUMULATE);
}
/**
* Converts a source audio stream to destination audio stream with a matrix
* channel conversion.
*
* \param src multichannel audio buffer to remix
* \param dst remixed audio samples
* \param frameCount number of multichannel frames to remix
* \param accumulate is true if the remix is added to the destination or
* false if the remix replaces the destination.
*
* \return false if the CHANNEL_COUNT is not supported.
*/
bool matrixProcess(const float *src, float *dst, size_t frameCount, bool accumulate) const {
// matrix multiply
if (mInputChannelMask == AUDIO_CHANNEL_NONE) return false;
while (frameCount) {
float ch[mOutputChannelCount]{};
for (size_t i = 0; i < mInputChannelCount; ++i) {
const float (&array)[mOutputChannelCount] = mMatrix[i];
for (size_t j = 0; j < mOutputChannelCount; ++j) {
ch[j] += array[j] * src[i];
}
}
if (accumulate) {
for (size_t j = 0; j < mOutputChannelCount; ++j) {
ch[j] += dst[j];
}
}
for (size_t j = 0; j < mOutputChannelCount; ++j) {
dst[j] = clamp(ch[j]);
}
src += mInputChannelCount;
dst += mOutputChannelCount;
--frameCount;
}
return true;
}
};
} // android::audio_utils::channels