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/user_metadata_builder/audio_element_metadata_builder.h"
14
15 #include <cstdint>
16
17 #include "absl/base/no_destructor.h"
18 #include "absl/container/flat_hash_map.h"
19 #include "absl/status/status.h"
20 #include "absl/status/statusor.h"
21 #include "absl/strings/str_cat.h"
22 #include "iamf/cli/proto/audio_element.pb.h"
23 #include "iamf/cli/proto/user_metadata.pb.h"
24 #include "iamf/cli/user_metadata_builder/iamf_input_layout.h"
25 #include "iamf/common/utils/map_utils.h"
26
27 namespace iamf_tools {
28
29 namespace {
30
LookupNumSubstreamsFromInputLayout(IamfInputLayout input_layout)31 absl::StatusOr<int32_t> LookupNumSubstreamsFromInputLayout(
32 IamfInputLayout input_layout) {
33 // Map which holds the loudspeaker layout and the count of substream(s)
34 // corresponding to it.
35 using enum IamfInputLayout;
36 static const absl::NoDestructor<absl::flat_hash_map<IamfInputLayout, int32_t>>
37 kInputLayoutToNumSubstreams({
38 {kMono, 1},
39 {kStereo, 1},
40 {k5_1, 4},
41 {k5_1_2, 5},
42 {k5_1_4, 6},
43 {k5_1_4, 6},
44 {k7_1, 5},
45 {k7_1_4, 7},
46 {kBinaural, 1},
47 {kLFE, 1},
48 {kAmbisonicsOrder1, 4},
49 {kAmbisonicsOrder2, 9},
50 {kAmbisonicsOrder3, 16},
51 });
52
53 return LookupInMap(*kInputLayoutToNumSubstreams, input_layout,
54 "Number of substreams for `IamfInputLayout`");
55 }
56
LookupCoupledSubstreamCountFromInputLayout(IamfInputLayout input_layout)57 absl::StatusOr<int32_t> LookupCoupledSubstreamCountFromInputLayout(
58 IamfInputLayout input_layout) {
59 // Map which holds the loudspeaker layout and the count of coupled
60 // substream(s) corresponding to it.
61 using enum IamfInputLayout;
62 static const absl::NoDestructor<absl::flat_hash_map<IamfInputLayout, int32_t>>
63 kInputLayoutToCoupledSubstreamCount({
64 {kMono, 0},
65 {kStereo, 1},
66 {k5_1, 2},
67 {k5_1_2, 3},
68 {k5_1_4, 4},
69 {k7_1, 3},
70 {k7_1_4, 5},
71 {kBinaural, 1},
72 {kLFE, 0},
73 });
74
75 return LookupInMap(*kInputLayoutToCoupledSubstreamCount, input_layout,
76 "Coupled substream count for `IamfInputLayout`");
77 }
78
79 absl::StatusOr<iamf_tools_cli_proto::LoudspeakerLayout>
LookupLoudspeakerLayoutFromInputLayout(IamfInputLayout input_layout)80 LookupLoudspeakerLayoutFromInputLayout(IamfInputLayout input_layout) {
81 using enum IamfInputLayout;
82 using enum iamf_tools_cli_proto::LoudspeakerLayout;
83
84 // Map which holds the channel layout and the corresponding loudspeaker layout
85 // in IAMF.
86 static const absl::NoDestructor<absl::flat_hash_map<
87 IamfInputLayout, iamf_tools_cli_proto::LoudspeakerLayout>>
88 KInputLayoutToLoudspeakerLayout({
89 {kMono, LOUDSPEAKER_LAYOUT_MONO},
90 {kStereo, LOUDSPEAKER_LAYOUT_STEREO},
91 {k5_1, LOUDSPEAKER_LAYOUT_5_1_CH},
92 {k5_1_2, LOUDSPEAKER_LAYOUT_5_1_2_CH},
93 {k5_1_4, LOUDSPEAKER_LAYOUT_5_1_4_CH},
94 {k7_1, LOUDSPEAKER_LAYOUT_7_1_CH},
95 {k7_1_4, LOUDSPEAKER_LAYOUT_7_1_4_CH},
96 {kBinaural, LOUDSPEAKER_LAYOUT_BINAURAL},
97 {kLFE, LOUDSPEAKER_LAYOUT_EXPANDED},
98 });
99
100 return LookupInMap(*KInputLayoutToLoudspeakerLayout, input_layout,
101 "Proto `LoudspeakerLayout` for `IamfInputLayout`");
102 }
103
104 absl::StatusOr<iamf_tools_cli_proto::ExpandedLoudspeakerLayout>
LookupExpandedLoudspeakerLayoutFromInputLayout(IamfInputLayout input_layout)105 LookupExpandedLoudspeakerLayoutFromInputLayout(IamfInputLayout input_layout) {
106 using enum IamfInputLayout;
107 using enum iamf_tools_cli_proto::ExpandedLoudspeakerLayout;
108
109 // Map which holds the channel layout and the corresponding expanded
110 // loudspeaker layout in IAMF.
111 static const absl::NoDestructor<absl::flat_hash_map<
112 IamfInputLayout, iamf_tools_cli_proto::ExpandedLoudspeakerLayout>>
113 KInputLayoutToExpandedLoudspeakerLayout({
114 {kLFE, EXPANDED_LOUDSPEAKER_LAYOUT_LFE},
115 });
116
117 return LookupInMap(*KInputLayoutToExpandedLoudspeakerLayout, input_layout,
118 "Proto `ExpandedLoudspeakerLayout` for `IamfInputLayout`");
119 }
120
121 absl::StatusOr<iamf_tools_cli_proto::AudioElementType>
LookupAudioElementTypeFromInputLayout(IamfInputLayout input_layout)122 LookupAudioElementTypeFromInputLayout(IamfInputLayout input_layout) {
123 using enum IamfInputLayout;
124 using enum iamf_tools_cli_proto::AudioElementType;
125
126 // Map which holds the channel layout and the corresponding audio element
127 // type.
128 static const absl::NoDestructor<absl::flat_hash_map<
129 IamfInputLayout, iamf_tools_cli_proto::AudioElementType>>
130 KInputLayoutToAudioElementType({
131 {kMono, AUDIO_ELEMENT_CHANNEL_BASED},
132 {kStereo, AUDIO_ELEMENT_CHANNEL_BASED},
133 {k5_1, AUDIO_ELEMENT_CHANNEL_BASED},
134 {k5_1_2, AUDIO_ELEMENT_CHANNEL_BASED},
135 {k5_1_4, AUDIO_ELEMENT_CHANNEL_BASED},
136 {k7_1, AUDIO_ELEMENT_CHANNEL_BASED},
137 {k7_1_4, AUDIO_ELEMENT_CHANNEL_BASED},
138 {kBinaural, AUDIO_ELEMENT_CHANNEL_BASED},
139 {kLFE, AUDIO_ELEMENT_CHANNEL_BASED},
140 {kAmbisonicsOrder1, AUDIO_ELEMENT_SCENE_BASED},
141 {kAmbisonicsOrder2, AUDIO_ELEMENT_SCENE_BASED},
142 {kAmbisonicsOrder3, AUDIO_ELEMENT_SCENE_BASED},
143 });
144
145 return LookupInMap(*KInputLayoutToAudioElementType, input_layout,
146 "Proto `AudioElementType` for `IamfInputLayout`");
147 }
148
PopulateChannelBasedAudioElementMetadata(IamfInputLayout input_layout,int32_t num_substreams,iamf_tools_cli_proto::ScalableChannelLayoutConfig & scalable_channel_layout_config)149 absl::Status PopulateChannelBasedAudioElementMetadata(
150 IamfInputLayout input_layout, int32_t num_substreams,
151 iamf_tools_cli_proto::ScalableChannelLayoutConfig&
152 scalable_channel_layout_config) {
153 // Simplistically choose one layer. This most closely matches other popular
154 // formats (e.g. ADM).
155 scalable_channel_layout_config.set_num_layers(1);
156
157 auto* channel_audio_layer_config =
158 scalable_channel_layout_config.add_channel_audio_layer_configs();
159
160 const auto loudspeaker_layout =
161 LookupLoudspeakerLayoutFromInputLayout(input_layout);
162 if (!loudspeaker_layout.ok()) {
163 return loudspeaker_layout.status();
164 }
165 channel_audio_layer_config->set_loudspeaker_layout(*loudspeaker_layout);
166
167 // Set 'output_gain_is_present_flag' and 'recon_gain_is_present_flag' to agree
168 // with the single-layer assumption.
169 channel_audio_layer_config->set_output_gain_is_present_flag(0);
170 channel_audio_layer_config->set_recon_gain_is_present_flag(0);
171
172 // As 'num_layers' is set to 1, 'substream_count' is equal to
173 // 'num_substreams'.
174 channel_audio_layer_config->set_substream_count(num_substreams);
175 const auto coupled_substream_count =
176 LookupCoupledSubstreamCountFromInputLayout(input_layout);
177 if (!coupled_substream_count.ok()) {
178 return coupled_substream_count.status();
179 }
180 channel_audio_layer_config->set_coupled_substream_count(
181 *coupled_substream_count);
182
183 // Set the specific 'expanded_loudspeaker_layout' field when it is relevant
184 // (e.g. LFE).
185 if (channel_audio_layer_config->loudspeaker_layout() ==
186 iamf_tools_cli_proto::LOUDSPEAKER_LAYOUT_EXPANDED) {
187 const auto expanded_loudspeaker_layout =
188 LookupExpandedLoudspeakerLayoutFromInputLayout(input_layout);
189 if (!expanded_loudspeaker_layout.ok()) {
190 return expanded_loudspeaker_layout.status();
191 }
192 channel_audio_layer_config->set_expanded_loudspeaker_layout(
193 *expanded_loudspeaker_layout);
194 }
195
196 return absl::OkStatus();
197 }
198
PopulateSceneBasedAudioElementMetadata(int32_t num_substreams,iamf_tools_cli_proto::AudioElementObuMetadata & audio_element_obu_metadata)199 void PopulateSceneBasedAudioElementMetadata(
200 int32_t num_substreams,
201 iamf_tools_cli_proto::AudioElementObuMetadata& audio_element_obu_metadata) {
202 audio_element_obu_metadata.set_audio_element_type(
203 iamf_tools_cli_proto::AUDIO_ELEMENT_SCENE_BASED);
204
205 auto* ambisonics_config =
206 audio_element_obu_metadata.mutable_ambisonics_config();
207 // For typeDefinition = HOA and since input contains LPCM audio samples, set
208 // ambisonics_mode to AMBISONICS_MODE_MONO.
209 ambisonics_config->set_ambisonics_mode(
210 iamf_tools_cli_proto::AMBISONICS_MODE_MONO);
211
212 auto* ambisonics_mono_config =
213 ambisonics_config->mutable_ambisonics_mono_config();
214
215 ambisonics_mono_config->set_output_channel_count(num_substreams);
216 ambisonics_mono_config->set_substream_count(num_substreams);
217
218 for (int32_t substream_id = 0; substream_id < num_substreams;
219 ++substream_id) {
220 ambisonics_mono_config->mutable_channel_mapping()->Add(substream_id);
221 }
222 }
223
224 } // namespace
225
226 // Sets the required textproto fields for audio_element_metadata.
PopulateAudioElementMetadata(uint32_t audio_element_id,uint32_t codec_config_id,const IamfInputLayout input_layout,iamf_tools_cli_proto::AudioElementObuMetadata & audio_element_obu_metadata)227 absl::Status AudioElementMetadataBuilder::PopulateAudioElementMetadata(
228 uint32_t audio_element_id, uint32_t codec_config_id,
229 const IamfInputLayout input_layout,
230 iamf_tools_cli_proto::AudioElementObuMetadata& audio_element_obu_metadata) {
231 audio_element_obu_metadata.set_audio_element_id(audio_element_id);
232 audio_element_obu_metadata.set_codec_config_id(codec_config_id);
233
234 const auto num_substreams = LookupNumSubstreamsFromInputLayout(input_layout);
235 if (!num_substreams.ok()) {
236 return num_substreams.status();
237 }
238 audio_element_obu_metadata.set_num_substreams(*num_substreams);
239
240 // Generate sequential substream IDs. Although not REQUIRED by IAMF this helps
241 // ensure that the substream IDs are unique between subsequent calls to this
242 // function.
243 for (int i = 0; i < *num_substreams; ++i) {
244 audio_element_obu_metadata.mutable_audio_substream_ids()->Add(
245 audio_stream_id_counter_);
246 ++audio_stream_id_counter_;
247 }
248
249 // Simplistically set 'num_parameters' to zero.
250 audio_element_obu_metadata.set_num_parameters(0);
251
252 const auto audio_element_type =
253 LookupAudioElementTypeFromInputLayout(input_layout);
254 if (!audio_element_type.ok()) {
255 return audio_element_type.status();
256 }
257 audio_element_obu_metadata.set_audio_element_type(*audio_element_type);
258
259 switch (*audio_element_type) {
260 using enum iamf_tools_cli_proto::AudioElementType;
261 case AUDIO_ELEMENT_CHANNEL_BASED:
262 return PopulateChannelBasedAudioElementMetadata(
263 input_layout, *num_substreams,
264 *audio_element_obu_metadata.mutable_scalable_channel_layout_config());
265 case AUDIO_ELEMENT_SCENE_BASED:
266 PopulateSceneBasedAudioElementMetadata(*num_substreams,
267 audio_element_obu_metadata);
268 return absl::OkStatus();
269 default:
270 return absl::InvalidArgumentError(absl::StrCat(
271 "Unsupported audio_element_type= ", *audio_element_type));
272 }
273 }
274
275 } // namespace iamf_tools
276