• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024, Alliance for Open Media. All rights reserved
3  *
4  * This source code is subject to the terms of the BSD 3-Clause Clear License
5  * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6  * License was not distributed with this source code in the LICENSE file, you
7  * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8  * Alliance for Open Media Patent License 1.0 was not distributed with this
9  * source code in the PATENTS file, you can obtain it at
10  * www.aomedia.org/license/patent.
11  */
12 
13 #include "iamf/cli/renderer/audio_element_renderer_ambisonics_to_channel.h"
14 
15 #include <cstddef>
16 #include <cstdint>
17 #include <limits>
18 #include <vector>
19 
20 #include "absl/status/status_matchers.h"
21 #include "gtest/gtest.h"
22 #include "iamf/cli/audio_element_with_data.h"
23 #include "iamf/cli/channel_label.h"
24 #include "iamf/cli/demixing_module.h"
25 #include "iamf/cli/tests/cli_test_utils.h"
26 #include "iamf/obu/audio_element.h"
27 #include "iamf/obu/mix_presentation.h"
28 #include "iamf/obu/types.h"
29 namespace iamf_tools {
30 namespace {
31 
32 using ::absl_testing::IsOk;
33 using enum ChannelLabel::Label;
34 
35 using enum LoudspeakersSsConventionLayout::SoundSystem;
36 
37 constexpr size_t kOneSamplePerFrame = 1;
38 const Layout kMonoLayout = {
39     .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
40     .specific_layout =
41         LoudspeakersSsConventionLayout{.sound_system = kSoundSystem12_0_1_0}};
42 const Layout kStereoLayout = {
43     .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
44     .specific_layout =
45         LoudspeakersSsConventionLayout{.sound_system = kSoundSystemA_0_2_0}};
46 const Layout k9_1_6Layout = {
47     .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
48     .specific_layout =
49         LoudspeakersSsConventionLayout{.sound_system = kSoundSystem13_6_9_0}};
50 const Layout kBinauralLayout = {.layout_type = Layout::kLayoutTypeBinaural};
51 
52 // The IAMF spec recommends special rules for some layouts.
53 const Layout k7_1_2Layout = {
54     .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
55     .specific_layout =
56         LoudspeakersSsConventionLayout{.sound_system = kSoundSystem10_2_7_0}};
57 
58 const AmbisonicsConfig kFullZerothOrderAmbisonicsConfig = {
59     .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono,
60     .ambisonics_config = AmbisonicsMonoConfig{.output_channel_count = 1,
61                                               .substream_count = 1,
62                                               .channel_mapping = {0}}};
63 const std::vector<DecodedUleb128> kFullZerothOrderAudioSubstreamIds = {0};
64 
65 const AmbisonicsConfig kFullFirstOrderAmbisonicsConfig = {
66     .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono,
67     .ambisonics_config = AmbisonicsMonoConfig{.output_channel_count = 4,
68                                               .substream_count = 4,
69                                               .channel_mapping = {0, 1, 2, 3}}};
70 const std::vector<DecodedUleb128> kFullFirstOrderAudioSubstreamIds = {0, 1, 2,
71                                                                       3};
72 
73 const AmbisonicsConfig kFullThirdOrderAmbisonicsConfig = {
74     .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono,
75     .ambisonics_config =
76         AmbisonicsMonoConfig{.output_channel_count = 16,
77                              .substream_count = 16,
78                              .channel_mapping = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
79                                                  10, 11, 12, 13, 14, 15}}};
80 const std::vector<DecodedUleb128> kFullThirdOrderAudioSubstreamIds = {
81     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
82 
83 const AmbisonicsConfig kFullFourthOrderAmbisonicsConfig = {
84     .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono,
85     .ambisonics_config = AmbisonicsMonoConfig{
86         .output_channel_count = 25,
87         .substream_count = 25,
88         .channel_mapping = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12,
89                             13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}}};
90 const std::vector<DecodedUleb128> kFullFourthOrderAudioSubstreamIds = {
91     0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12,
92     13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
93 
94 // =========== Full-order ambisonics mono config ===========
TEST(CreateFromAmbisonicsConfig,SupportsZerothOrderToMono)95 TEST(CreateFromAmbisonicsConfig, SupportsZerothOrderToMono) {
96   const SubstreamIdLabelsMap kZerothOrderSubstreamIdToLabels = {{0, {kA0}}};
97 
98   EXPECT_NE(
99       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
100           kFullZerothOrderAmbisonicsConfig, kFullZerothOrderAudioSubstreamIds,
101           kZerothOrderSubstreamIdToLabels, kMonoLayout, kOneSamplePerFrame),
102       nullptr);
103 }
104 
TEST(CreateFromAmbisonicsConfig,SupportsZerothOrderToStereo)105 TEST(CreateFromAmbisonicsConfig, SupportsZerothOrderToStereo) {
106   const SubstreamIdLabelsMap kZerothOrderSubstreamIdToLabels = {{0, {kA0}}};
107 
108   EXPECT_NE(
109       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
110           kFullZerothOrderAmbisonicsConfig, kFullZerothOrderAudioSubstreamIds,
111           kZerothOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
112       nullptr);
113 }
114 
TEST(CreateFromAmbisonicsConfig,SupportsZerothOrderTo9_1_6)115 TEST(CreateFromAmbisonicsConfig, SupportsZerothOrderTo9_1_6) {
116   const SubstreamIdLabelsMap kZerothOrderSubstreamIdToLabels = {{0, {kA0}}};
117 
118   EXPECT_NE(
119       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
120           kFullZerothOrderAmbisonicsConfig, kFullZerothOrderAudioSubstreamIds,
121           kZerothOrderSubstreamIdToLabels, k9_1_6Layout, kOneSamplePerFrame),
122       nullptr);
123 }
124 
TEST(CreateFromAmbisonicsConfig,SupportsFirstOrderTo7_1_2)125 TEST(CreateFromAmbisonicsConfig, SupportsFirstOrderTo7_1_2) {
126   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {
127       {0, {kA0}}, {1, {kA1}}, {2, {kA2}}, {3, {kA3}}};
128 
129   EXPECT_NE(
130       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
131           kFullFirstOrderAmbisonicsConfig, kFullFirstOrderAudioSubstreamIds,
132           kFirstOrderSubstreamIdToLabels, k7_1_2Layout, kOneSamplePerFrame),
133       nullptr);
134 }
135 
TEST(CreateFromAmbisonicsConfig,SupportsThirdOrderToStereo)136 TEST(CreateFromAmbisonicsConfig, SupportsThirdOrderToStereo) {
137   const SubstreamIdLabelsMap kThirdOrderSubstreamIdToLabels = {
138       {0, {kA0}},   {1, {kA1}},   {2, {kA2}},   {3, {kA3}},
139       {4, {kA4}},   {5, {kA5}},   {6, {kA6}},   {7, {kA7}},
140       {8, {kA8}},   {9, {kA9}},   {10, {kA10}}, {11, {kA11}},
141       {12, {kA12}}, {13, {kA13}}, {14, {kA14}}, {15, {kA15}}};
142 
143   EXPECT_NE(
144       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
145           kFullThirdOrderAmbisonicsConfig, kFullThirdOrderAudioSubstreamIds,
146           kThirdOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
147       nullptr);
148 }
149 
TEST(CreateFromAmbisonicsConfig,SupportsFourthOrderAmbisonics)150 TEST(CreateFromAmbisonicsConfig, SupportsFourthOrderAmbisonics) {
151   const SubstreamIdLabelsMap kFourthOrderSubstreamIdToLabels = {
152       {0, {kA0}},   {1, {kA1}},   {2, {kA2}},   {3, {kA3}},   {4, {kA4}},
153       {5, {kA5}},   {6, {kA6}},   {7, {kA7}},   {8, {kA8}},   {9, {kA9}},
154       {10, {kA10}}, {11, {kA11}}, {12, {kA12}}, {13, {kA13}}, {14, {kA14}},
155       {15, {kA15}}, {16, {kA16}}, {17, {kA17}}, {18, {kA18}}, {19, {kA19}},
156       {20, {kA20}}, {21, {kA21}}, {22, {kA22}}, {23, {kA23}}, {24, {kA24}}};
157 
158   EXPECT_NE(
159       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
160           kFullFourthOrderAmbisonicsConfig, kFullFourthOrderAudioSubstreamIds,
161           kFourthOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
162       nullptr);
163 }
164 
TEST(CreateFromAmbisonicsConfig,DoesNotSupportBinauralOutput)165 TEST(CreateFromAmbisonicsConfig, DoesNotSupportBinauralOutput) {
166   const SubstreamIdLabelsMap kZerothOrderSubstreamIdToLabels = {{0, {kA0}}};
167 
168   EXPECT_EQ(
169       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
170           kFullZerothOrderAmbisonicsConfig, kFullZerothOrderAudioSubstreamIds,
171           kZerothOrderSubstreamIdToLabels, kBinauralLayout, kOneSamplePerFrame),
172       nullptr);
173 }
174 
175 // =========== Mixed-order ambisonics mono config ===========
176 
TEST(CreateFromAmbisonicsConfig,SupportsMixedFirstOrderAmbisonics)177 TEST(CreateFromAmbisonicsConfig, SupportsMixedFirstOrderAmbisonics) {
178   const AmbisonicsConfig kMixedFirstOrderAmbisonicsConfig = {
179       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono,
180       .ambisonics_config =
181           AmbisonicsMonoConfig{.output_channel_count = 4,
182                                .substream_count = 3,
183                                .channel_mapping = {0, 255, 1, 2}}};
184   const std::vector<DecodedUleb128> kMixedFirstOrderAudioSubstreamIds = {0, 1,
185                                                                          2};
186   const SubstreamIdLabelsMap kMixedFirstOrderSubstreamIdToLabels = {
187       {0, {kA0}}, {1, {kA2}}, {2, {kA3}}};
188 
189   EXPECT_NE(
190       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
191           kMixedFirstOrderAmbisonicsConfig, kMixedFirstOrderAudioSubstreamIds,
192           kMixedFirstOrderSubstreamIdToLabels, kStereoLayout,
193           kOneSamplePerFrame),
194       nullptr);
195 }
196 
197 // =========== Full-order ambisonics projection config ===========
198 
199 const int16_t kMaxGain = std::numeric_limits<int16_t>::max();
200 const std::vector<int16_t> kEpsilonIdentityFoa =
201     {/*           ACN#: 0, 1, 2, 3 */
202      /* Channel 0: */ kMaxGain, 0,        0,        0,
203      /* Channel 1: */ 0,        kMaxGain, 0,        0,
204      /* Channel 2: */ 0,        0,        kMaxGain, 0,
205      /* Channel 3: */ 0,        0,        0,        kMaxGain};
206 
207 const std::vector<int16_t> kNegativeEpsilonIdentityFoa =
208     {/*           ACN#: 0, 1, 2, 3 */
209      /* Channel 0: */ kMaxGain * -1,
210      0,
211      0,
212      0,
213      /* Channel 1: */ 0,
214      kMaxGain * -1,
215      0,
216      0,
217      /* Channel 2: */ 0,
218      0,
219      kMaxGain * -1,
220      0,
221      /* Channel 3: */ 0,
222      0,
223      0,
224      kMaxGain * -1};
225 
TEST(CreateFromAmbisonicsConfig,Projection)226 TEST(CreateFromAmbisonicsConfig, Projection) {
227   const AmbisonicsConfig kAmbisonicsProjectionConfig = {
228       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection,
229       .ambisonics_config =
230           AmbisonicsProjectionConfig{.output_channel_count = 4,
231                                      .substream_count = 4,
232                                      .coupled_substream_count = 0,
233                                      .demixing_matrix = kEpsilonIdentityFoa}};
234   const std::vector<DecodedUleb128> kFirstOrderAudioSubstreamIds = {0, 1, 2, 3};
235   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {
236       {0, {kA0}}, {1, {kA1}}, {2, {kA2}}, {3, {kA3}}};
237 
238   EXPECT_NE(
239       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
240           kAmbisonicsProjectionConfig, kFirstOrderAudioSubstreamIds,
241           kFirstOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
242       nullptr);
243 }
244 
TEST(CreateFromAmbisonicsConfig,SupportsAmbisonicsProjectionConfigWithCoupledSubstreams)245 TEST(CreateFromAmbisonicsConfig,
246      SupportsAmbisonicsProjectionConfigWithCoupledSubstreams) {
247   const AmbisonicsConfig kAmbisonicsProjectionConfig = {
248       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection,
249       .ambisonics_config =
250           AmbisonicsProjectionConfig{.output_channel_count = 4,
251                                      .substream_count = 2,
252                                      .coupled_substream_count = 2,
253                                      .demixing_matrix = kEpsilonIdentityFoa}};
254   const std::vector<DecodedUleb128> kFirstOrderAudioSubstreamIds = {0, 1};
255   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {{0, {kA0, kA1}},
256                                                                {1, {kA2, kA3}}};
257 
258   EXPECT_NE(
259       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
260           kAmbisonicsProjectionConfig, kFirstOrderAudioSubstreamIds,
261           kFirstOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
262       nullptr);
263 }
264 
265 // =========== Mixed-order ambisonics projection config ===========
266 
TEST(CreateFromAmbisonicsConfig,SupportedMixedOrderProjectionConfig)267 TEST(CreateFromAmbisonicsConfig, SupportedMixedOrderProjectionConfig) {
268   const std::vector<int16_t> kNearIdentityMixedFoa = {
269       /*           ACN#: 0, 1, 2, 3 */
270       /* Channel 0: */ kMaxGain, 0,        0, 0,
271       /* Channel 1: */ 0,        kMaxGain, 0, 0,
272       /* Channel 2: */ 0,        0,        0, kMaxGain};
273 
274   const AmbisonicsConfig kAmbisonicsProjectionConfig = {
275       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection,
276       .ambisonics_config =
277           AmbisonicsProjectionConfig{.output_channel_count = 4,
278                                      .substream_count = 3,
279                                      .coupled_substream_count = 0,
280                                      .demixing_matrix = kNearIdentityMixedFoa}};
281   const std::vector<DecodedUleb128> kFirstOrderAudioSubstreamIds = {0, 1, 2};
282   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {
283       {0, {kA0}}, {1, {kA1}}, {2, {kA3}}};
284 
285   EXPECT_NE(
286       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
287           kAmbisonicsProjectionConfig, kFirstOrderAudioSubstreamIds,
288           kFirstOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame),
289       nullptr);
290 }
291 
TEST(RenderFrames,AcnZeroIsSymmetric)292 TEST(RenderFrames, AcnZeroIsSymmetric) {
293   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {
294       {0, {kA0}}, {1, {kA1}}, {2, {kA2}}, {3, {kA3}}};
295 
296   auto renderer =
297       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
298           kFullFirstOrderAmbisonicsConfig, kFullFirstOrderAudioSubstreamIds,
299           kFirstOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame);
300 
301   const LabeledFrame frame = {
302       .label_to_samples = {{kA0, {10000}}, {kA1, {0}}, {kA2, {0}}, {kA3, {0}}}};
303   std::vector<InternalSampleType> output_samples;
304   RenderAndFlushExpectOk(frame, renderer.get(), output_samples);
305 
306   EXPECT_EQ(output_samples.size(), 2);
307   EXPECT_NEAR(output_samples[0], output_samples[1], 0.11);
308 }
309 
TEST(RenderFrames,UsesDemixingMatrix)310 TEST(RenderFrames, UsesDemixingMatrix) {
311   const AmbisonicsConfig kAmbisonicsProjectionConfigIdentity = {
312       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection,
313       .ambisonics_config =
314           AmbisonicsProjectionConfig{.output_channel_count = 4,
315                                      .substream_count = 4,
316                                      .coupled_substream_count = 0,
317                                      .demixing_matrix = kEpsilonIdentityFoa}};
318   const AmbisonicsConfig kAmbisonicsProjectionConfigIdentityInverse = {
319       .ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection,
320       .ambisonics_config = AmbisonicsProjectionConfig{
321           .output_channel_count = 4,
322           .substream_count = 4,
323           .coupled_substream_count = 0,
324           .demixing_matrix = kNegativeEpsilonIdentityFoa}};
325   const std::vector<DecodedUleb128> kFirstOrderAudioSubstreamIds = {0, 1, 2, 3};
326   const SubstreamIdLabelsMap kFirstOrderSubstreamIdToLabels = {
327       {0, {kA0}}, {1, {kA1}}, {2, {kA2}}, {3, {kA3}}};
328   const LabeledFrame frame = {
329       .label_to_samples = {
330           {kA0, {10000}}, {kA1, {5000}}, {kA2, {2500}}, {kA3, {1250}}}};
331   // Create a renderer which uses a near-identity matrix (I*epsilon) and a
332   // different one that uses (-1*I*epsilon).
333   auto renderer_epsilon_identity =
334       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
335           kAmbisonicsProjectionConfigIdentity, kFirstOrderAudioSubstreamIds,
336           kFirstOrderSubstreamIdToLabels, kStereoLayout, kOneSamplePerFrame);
337   std::vector<InternalSampleType> output_samples_epsilon_identity;
338   RenderAndFlushExpectOk(frame, renderer_epsilon_identity.get(),
339                          output_samples_epsilon_identity);
340   auto renderer_negative_epsilon_identity =
341       AudioElementRendererAmbisonicsToChannel::CreateFromAmbisonicsConfig(
342           kAmbisonicsProjectionConfigIdentityInverse,
343           kFirstOrderAudioSubstreamIds, kFirstOrderSubstreamIdToLabels,
344           kStereoLayout, kOneSamplePerFrame);
345   std::vector<InternalSampleType> output_samples_negative_epsilon_identity;
346   RenderAndFlushExpectOk(frame, renderer_negative_epsilon_identity.get(),
347                          output_samples_negative_epsilon_identity);
348 
349   // The samples should be multiplicative inverses of each other because of the
350   // difference in demixing matrices.
351   EXPECT_EQ(output_samples_epsilon_identity.size(), 2);
352   EXPECT_EQ(output_samples_negative_epsilon_identity.size(), 2);
353   EXPECT_DOUBLE_EQ(output_samples_epsilon_identity[0],
354                    -1 * output_samples_negative_epsilon_identity[0]);
355   EXPECT_DOUBLE_EQ(output_samples_epsilon_identity[1],
356                    -1 * output_samples_negative_epsilon_identity[1]);
357 }
358 
359 }  // namespace
360 }  // namespace iamf_tools
361