xref: /aosp_15_r20/system/media/audio_utils/ChannelMix.cpp (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
1*b9df5ad1SAndroid Build Coastguard Worker /*
2*b9df5ad1SAndroid Build Coastguard Worker  * Copyright (C) 2023 The Android Open Source Project
3*b9df5ad1SAndroid Build Coastguard Worker  *
4*b9df5ad1SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*b9df5ad1SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*b9df5ad1SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*b9df5ad1SAndroid Build Coastguard Worker  *
8*b9df5ad1SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*b9df5ad1SAndroid Build Coastguard Worker  *
10*b9df5ad1SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*b9df5ad1SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*b9df5ad1SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*b9df5ad1SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*b9df5ad1SAndroid Build Coastguard Worker  * limitations under the License.
15*b9df5ad1SAndroid Build Coastguard Worker  */
16*b9df5ad1SAndroid Build Coastguard Worker 
17*b9df5ad1SAndroid Build Coastguard Worker #include <audio_utils/ChannelMix.h>
18*b9df5ad1SAndroid Build Coastguard Worker 
19*b9df5ad1SAndroid Build Coastguard Worker namespace android::audio_utils::channels {
20*b9df5ad1SAndroid Build Coastguard Worker 
21*b9df5ad1SAndroid Build Coastguard Worker /**
22*b9df5ad1SAndroid Build Coastguard Worker  * The ChannelMix code relies on sparse matrix optimization for speed.
23*b9df5ad1SAndroid Build Coastguard Worker  *
24*b9df5ad1SAndroid Build Coastguard Worker  * This requires -ffast-math to be specified.
25*b9df5ad1SAndroid Build Coastguard Worker  */
26*b9df5ad1SAndroid Build Coastguard Worker 
27*b9df5ad1SAndroid Build Coastguard Worker 
28*b9df5ad1SAndroid Build Coastguard Worker /*
29*b9df5ad1SAndroid Build Coastguard Worker  Implementation detail:
30*b9df5ad1SAndroid Build Coastguard Worker 
31*b9df5ad1SAndroid Build Coastguard Worker  We "compute" the channel mix matrix by constexpr computation,
32*b9df5ad1SAndroid Build Coastguard Worker  but alternatively we could use a straight 2D array initialization.
33*b9df5ad1SAndroid Build Coastguard Worker 
34*b9df5ad1SAndroid Build Coastguard Worker  The thought is that the channel mix matrix by computation is easier
35*b9df5ad1SAndroid Build Coastguard Worker  to keep consistent with modifications.
36*b9df5ad1SAndroid Build Coastguard Worker  */
37*b9df5ad1SAndroid Build Coastguard Worker 
38*b9df5ad1SAndroid Build Coastguard Worker namespace {
39*b9df5ad1SAndroid Build Coastguard Worker // A container for the channel matrix
40*b9df5ad1SAndroid Build Coastguard Worker template <audio_channel_mask_t INPUT_CHANNEL_MASK, audio_channel_mask_t OUTPUT_CHANNEL_MASK>
41*b9df5ad1SAndroid Build Coastguard Worker struct ChannelMatrixContainer {
42*b9df5ad1SAndroid Build Coastguard Worker     static inline constexpr size_t INPUT_CHANNEL_COUNT =
43*b9df5ad1SAndroid Build Coastguard Worker             audio_channel_count_from_out_mask(INPUT_CHANNEL_MASK);
44*b9df5ad1SAndroid Build Coastguard Worker     static inline constexpr size_t OUTPUT_CHANNEL_COUNT =
45*b9df5ad1SAndroid Build Coastguard Worker             audio_channel_count_from_out_mask(OUTPUT_CHANNEL_MASK);
46*b9df5ad1SAndroid Build Coastguard Worker     float f[INPUT_CHANNEL_COUNT][OUTPUT_CHANNEL_COUNT];
47*b9df5ad1SAndroid Build Coastguard Worker };
48*b9df5ad1SAndroid Build Coastguard Worker 
49*b9df5ad1SAndroid Build Coastguard Worker template <audio_channel_mask_t INPUT_CHANNEL_MASK, audio_channel_mask_t OUTPUT_CHANNEL_MASK>
computeMatrix()50*b9df5ad1SAndroid Build Coastguard Worker constexpr ChannelMatrixContainer<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK> computeMatrix() {
51*b9df5ad1SAndroid Build Coastguard Worker     ChannelMatrixContainer<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK> channelMatrix{};
52*b9df5ad1SAndroid Build Coastguard Worker     // Compiler bug: cannot check result of this through static_assert.
53*b9df5ad1SAndroid Build Coastguard Worker     (void)fillChannelMatrix<OUTPUT_CHANNEL_MASK>(INPUT_CHANNEL_MASK, channelMatrix.f);
54*b9df5ad1SAndroid Build Coastguard Worker     return channelMatrix;
55*b9df5ad1SAndroid Build Coastguard Worker }
56*b9df5ad1SAndroid Build Coastguard Worker 
57*b9df5ad1SAndroid Build Coastguard Worker } // namespace
58*b9df5ad1SAndroid Build Coastguard Worker 
59*b9df5ad1SAndroid Build Coastguard Worker /**
60*b9df5ad1SAndroid Build Coastguard Worker  * Remixes a multichannel signal of specified number of channels
61*b9df5ad1SAndroid Build Coastguard Worker  *
62*b9df5ad1SAndroid Build Coastguard Worker  * INPUT_CHANNEL_MASK the src input.
63*b9df5ad1SAndroid Build Coastguard Worker  * OUTPUT_CHANNEL_MASK the dst output.
64*b9df5ad1SAndroid Build Coastguard Worker  * ACCUMULATE is true if the remix is added to the destination or
65*b9df5ad1SAndroid Build Coastguard Worker  *               false if the remix replaces the destination.
66*b9df5ad1SAndroid Build Coastguard Worker  *
67*b9df5ad1SAndroid Build Coastguard Worker  * \param src          multichannel audio buffer to remix
68*b9df5ad1SAndroid Build Coastguard Worker  * \param dst          remixed stereo audio samples
69*b9df5ad1SAndroid Build Coastguard Worker  * \param frameCount   number of multichannel frames to remix
70*b9df5ad1SAndroid Build Coastguard Worker  *
71*b9df5ad1SAndroid Build Coastguard Worker  * \return false if the CHANNEL_COUNT is not supported.
72*b9df5ad1SAndroid Build Coastguard Worker  */
73*b9df5ad1SAndroid Build Coastguard Worker template <audio_channel_mask_t INPUT_CHANNEL_MASK,
74*b9df5ad1SAndroid Build Coastguard Worker         audio_channel_mask_t OUTPUT_CHANNEL_MASK, bool ACCUMULATE>
sparseChannelMatrixMultiply(const float * src,float * dst,size_t frameCount)75*b9df5ad1SAndroid Build Coastguard Worker bool sparseChannelMatrixMultiply(const float *src, float *dst, size_t frameCount) {
76*b9df5ad1SAndroid Build Coastguard Worker     static constexpr auto s = computeMatrix<INPUT_CHANNEL_MASK, OUTPUT_CHANNEL_MASK>();
77*b9df5ad1SAndroid Build Coastguard Worker 
78*b9df5ad1SAndroid Build Coastguard Worker     // matrix multiply
79*b9df5ad1SAndroid Build Coastguard Worker     if (INPUT_CHANNEL_MASK == AUDIO_CHANNEL_NONE) return false;
80*b9df5ad1SAndroid Build Coastguard Worker     for (;frameCount > 0; --frameCount) {
81*b9df5ad1SAndroid Build Coastguard Worker         float ch[s.OUTPUT_CHANNEL_COUNT]{};
82*b9df5ad1SAndroid Build Coastguard Worker         #pragma unroll
83*b9df5ad1SAndroid Build Coastguard Worker         for (size_t i = 0; i < s.INPUT_CHANNEL_COUNT; ++i) {
84*b9df5ad1SAndroid Build Coastguard Worker             const float (&array)[s.OUTPUT_CHANNEL_COUNT] = s.f[i];
85*b9df5ad1SAndroid Build Coastguard Worker             #pragma unroll
86*b9df5ad1SAndroid Build Coastguard Worker             for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
87*b9df5ad1SAndroid Build Coastguard Worker                 ch[j] += array[j] * src[i];
88*b9df5ad1SAndroid Build Coastguard Worker             }
89*b9df5ad1SAndroid Build Coastguard Worker         }
90*b9df5ad1SAndroid Build Coastguard Worker         if constexpr (ACCUMULATE) {
91*b9df5ad1SAndroid Build Coastguard Worker             #pragma unroll
92*b9df5ad1SAndroid Build Coastguard Worker             for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
93*b9df5ad1SAndroid Build Coastguard Worker                 ch[j] += dst[j];
94*b9df5ad1SAndroid Build Coastguard Worker             }
95*b9df5ad1SAndroid Build Coastguard Worker         }
96*b9df5ad1SAndroid Build Coastguard Worker         #pragma unroll
97*b9df5ad1SAndroid Build Coastguard Worker         for (size_t j = 0; j < s.OUTPUT_CHANNEL_COUNT; ++j) {
98*b9df5ad1SAndroid Build Coastguard Worker             dst[j] = clamp(ch[j]);
99*b9df5ad1SAndroid Build Coastguard Worker         }
100*b9df5ad1SAndroid Build Coastguard Worker         src += s.INPUT_CHANNEL_COUNT;
101*b9df5ad1SAndroid Build Coastguard Worker         dst += s.OUTPUT_CHANNEL_COUNT;
102*b9df5ad1SAndroid Build Coastguard Worker     }
103*b9df5ad1SAndroid Build Coastguard Worker     return true;
104*b9df5ad1SAndroid Build Coastguard Worker }
105*b9df5ad1SAndroid Build Coastguard Worker 
106*b9df5ad1SAndroid Build Coastguard Worker // Create accelerated instances
107*b9df5ad1SAndroid Build Coastguard Worker 
108*b9df5ad1SAndroid Build Coastguard Worker #define INSTANTIATE(INPUT_MASK, OUTPUT_MASK) \
109*b9df5ad1SAndroid Build Coastguard Worker template bool \
110*b9df5ad1SAndroid Build Coastguard Worker sparseChannelMatrixMultiply<INPUT_MASK, OUTPUT_MASK, true>( \
111*b9df5ad1SAndroid Build Coastguard Worker         const float *src, float *dst, size_t frameCount); \
112*b9df5ad1SAndroid Build Coastguard Worker template bool \
113*b9df5ad1SAndroid Build Coastguard Worker sparseChannelMatrixMultiply<INPUT_MASK, OUTPUT_MASK, false>( \
114*b9df5ad1SAndroid Build Coastguard Worker         const float *src, float *dst, size_t frameCount); \
115*b9df5ad1SAndroid Build Coastguard Worker 
116*b9df5ad1SAndroid Build Coastguard Worker #define INSTANTIATE_MASKS(CHANNEL) \
117*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_STEREO, (CHANNEL)) \
118*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_QUAD_BACK, (CHANNEL)) \
119*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1_BACK, (CHANNEL)) \
120*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1, (CHANNEL)) \
121*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1POINT2, (CHANNEL)) \
122*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_5POINT1POINT4, (CHANNEL)) \
123*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1POINT2, (CHANNEL)) \
124*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_7POINT1POINT4, (CHANNEL)) \
125*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_9POINT1POINT6, (CHANNEL)) \
126*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE(AUDIO_CHANNEL_OUT_22POINT2, (CHANNEL))
127*b9df5ad1SAndroid Build Coastguard Worker 
128*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_STEREO)
INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_5POINT1)129*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_5POINT1)
130*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_7POINT1)
131*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_7POINT1POINT4)
132*b9df5ad1SAndroid Build Coastguard Worker INSTANTIATE_MASKS(AUDIO_CHANNEL_OUT_9POINT1POINT6)
133*b9df5ad1SAndroid Build Coastguard Worker 
134*b9df5ad1SAndroid Build Coastguard Worker /* static */
135*b9df5ad1SAndroid Build Coastguard Worker std::shared_ptr<IChannelMix> IChannelMix::create(audio_channel_mask_t outputChannelMask) {
136*b9df5ad1SAndroid Build Coastguard Worker      switch (outputChannelMask) {
137*b9df5ad1SAndroid Build Coastguard Worker      case AUDIO_CHANNEL_OUT_STEREO:
138*b9df5ad1SAndroid Build Coastguard Worker          return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_STEREO>>();
139*b9df5ad1SAndroid Build Coastguard Worker      case AUDIO_CHANNEL_OUT_5POINT1:
140*b9df5ad1SAndroid Build Coastguard Worker          return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_5POINT1>>();
141*b9df5ad1SAndroid Build Coastguard Worker      case AUDIO_CHANNEL_OUT_7POINT1:
142*b9df5ad1SAndroid Build Coastguard Worker          return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_7POINT1>>();
143*b9df5ad1SAndroid Build Coastguard Worker      case AUDIO_CHANNEL_OUT_7POINT1POINT4:
144*b9df5ad1SAndroid Build Coastguard Worker          return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_7POINT1POINT4>>();
145*b9df5ad1SAndroid Build Coastguard Worker      case AUDIO_CHANNEL_OUT_9POINT1POINT6:
146*b9df5ad1SAndroid Build Coastguard Worker          return std::make_shared<ChannelMix<AUDIO_CHANNEL_OUT_9POINT1POINT6>>();
147*b9df5ad1SAndroid Build Coastguard Worker      default:
148*b9df5ad1SAndroid Build Coastguard Worker          return {};
149*b9df5ad1SAndroid Build Coastguard Worker      }
150*b9df5ad1SAndroid Build Coastguard Worker }
151*b9df5ad1SAndroid Build Coastguard Worker 
152*b9df5ad1SAndroid Build Coastguard Worker /* static */
isOutputChannelMaskSupported(audio_channel_mask_t outputChannelMask)153*b9df5ad1SAndroid Build Coastguard Worker bool IChannelMix::isOutputChannelMaskSupported(audio_channel_mask_t outputChannelMask) {
154*b9df5ad1SAndroid Build Coastguard Worker     switch (outputChannelMask) {
155*b9df5ad1SAndroid Build Coastguard Worker     case AUDIO_CHANNEL_OUT_STEREO:
156*b9df5ad1SAndroid Build Coastguard Worker     case AUDIO_CHANNEL_OUT_5POINT1:
157*b9df5ad1SAndroid Build Coastguard Worker     case AUDIO_CHANNEL_OUT_7POINT1:
158*b9df5ad1SAndroid Build Coastguard Worker     case AUDIO_CHANNEL_OUT_7POINT1POINT4:
159*b9df5ad1SAndroid Build Coastguard Worker     case AUDIO_CHANNEL_OUT_9POINT1POINT6:
160*b9df5ad1SAndroid Build Coastguard Worker         return true;
161*b9df5ad1SAndroid Build Coastguard Worker     default:
162*b9df5ad1SAndroid Build Coastguard Worker         return false;
163*b9df5ad1SAndroid Build Coastguard Worker     }
164*b9df5ad1SAndroid Build Coastguard Worker }
165*b9df5ad1SAndroid Build Coastguard Worker 
166*b9df5ad1SAndroid Build Coastguard Worker } // android::audio_utils::channels
167