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 #include "iamf/cli/profile_filter.h"
13
14 #include <cstdint>
15 #include <optional>
16 #include <string>
17 #include <variant>
18
19 #include "absl/container/flat_hash_map.h"
20 #include "absl/container/flat_hash_set.h"
21 #include "absl/status/status.h"
22 #include "absl/strings/str_cat.h"
23 #include "absl/strings/string_view.h"
24 #include "iamf/cli/audio_element_with_data.h"
25 #include "iamf/common/utils/macros.h"
26 #include "iamf/common/utils/validation_utils.h"
27 #include "iamf/obu/audio_element.h"
28 #include "iamf/obu/ia_sequence_header.h"
29 #include "iamf/obu/mix_presentation.h"
30
31 namespace iamf_tools {
32
33 namespace {
34
35 constexpr int kSimpleProfileMaxAudioElements = 1;
36 constexpr int kBaseProfileMaxAudioElements = 2;
37 constexpr int kBaseEnhancedProfileMaxAudioElements = 28;
38
39 constexpr int kSimpleProfileMaxChannels = 16;
40 constexpr int kBaseProfileMaxChannels = 18;
41 constexpr int kBaseEnhancedProfileMaxChannels = 28;
42
ClearAndReturnError(absl::string_view context,absl::flat_hash_set<ProfileVersion> & profile_versions)43 absl::Status ClearAndReturnError(
44 absl::string_view context,
45 absl::flat_hash_set<ProfileVersion>& profile_versions) {
46 profile_versions.clear();
47 return absl::InvalidArgumentError(context);
48 }
49
FilterAudioElementType(absl::string_view debugging_context,AudioElementObu::AudioElementType audio_element_type,absl::flat_hash_set<ProfileVersion> & profile_versions)50 absl::Status FilterAudioElementType(
51 absl::string_view debugging_context,
52 AudioElementObu::AudioElementType audio_element_type,
53 absl::flat_hash_set<ProfileVersion>& profile_versions) {
54 switch (audio_element_type) {
55 using enum AudioElementObu::AudioElementType;
56 case AudioElementObu::kAudioElementChannelBased:
57 case AudioElementObu::kAudioElementSceneBased:
58 break;
59 default:
60 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
61 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
62 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
63 }
64
65 if (profile_versions.empty()) {
66 return absl::InvalidArgumentError(absl::StrCat(
67 debugging_context, "has audio element type= ", audio_element_type,
68 ". But the requested profiles do support not support this type."));
69 }
70 return absl::OkStatus();
71 }
72
73 // Filters out profiles that do not support specific expanded loudspeaker
74 // layouts. This function assumes profiles that do not support expanded layout
75 // have already been filtered out (e.g. kIamfSimpleProfile, kIamfBaseProfile).
FilterExpandedLoudspeakerLayout(absl::string_view debugging_context,std::optional<ChannelAudioLayerConfig::ExpandedLoudspeakerLayout> expanded_loudspeaker_layout,absl::flat_hash_set<ProfileVersion> & profile_versions)76 absl::Status FilterExpandedLoudspeakerLayout(
77 absl::string_view debugging_context,
78 std::optional<ChannelAudioLayerConfig::ExpandedLoudspeakerLayout>
79 expanded_loudspeaker_layout,
80 absl::flat_hash_set<ProfileVersion>& profile_versions) {
81 if (auto status = ValidateHasValue(expanded_loudspeaker_layout,
82 "expanded_loudspeaker_layout");
83 !status.ok()) {
84 return ClearAndReturnError(
85 absl::StrCat(debugging_context, status.message()), profile_versions);
86 }
87
88 switch (*expanded_loudspeaker_layout) {
89 using enum ChannelAudioLayerConfig::ExpandedLoudspeakerLayout;
90 case kExpandedLayoutLFE:
91 case kExpandedLayoutStereoS:
92 case kExpandedLayoutStereoSS:
93 case kExpandedLayoutStereoRS:
94 case kExpandedLayoutStereoTF:
95 case kExpandedLayoutStereoTB:
96 case kExpandedLayoutTop4Ch:
97 case kExpandedLayout3_0_ch:
98 case kExpandedLayout9_1_6_ch:
99 case kExpandedLayoutStereoF:
100 case kExpandedLayoutStereoSi:
101 case kExpandedLayoutStereoTpSi:
102 case kExpandedLayoutTop6Ch:
103 break;
104 case kExpandedLayoutReserved13:
105 case kExpandedLayoutReserved255:
106 default:
107 // Other layouts are reserved and not supported by base-enhanced profile.
108 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
109 }
110 if (profile_versions.empty()) {
111 return absl::InvalidArgumentError(absl::StrCat(
112 debugging_context,
113 "has expanded_loudspeaker_layout= ", *expanded_loudspeaker_layout,
114 ". But the requested profiles do support not support this type."));
115 }
116 return absl::OkStatus();
117 }
118
FilterChannelBasedConfig(absl::string_view debugging_context,const AudioElementObu & audio_element_obu,absl::flat_hash_set<ProfileVersion> & profile_versions)119 absl::Status FilterChannelBasedConfig(
120 absl::string_view debugging_context,
121 const AudioElementObu& audio_element_obu,
122 absl::flat_hash_set<ProfileVersion>& profile_versions) {
123 if (!std::holds_alternative<ScalableChannelLayoutConfig>(
124 audio_element_obu.config_)) {
125 return ClearAndReturnError(
126 absl::StrCat(
127 debugging_context,
128 "signals that it is a channel-based audio element, but it does not "
129 "hold an `ScalableChannelLayoutConfig`."),
130 profile_versions);
131 }
132 const auto& scalable_channel_layout_config =
133 std::get<ScalableChannelLayoutConfig>(audio_element_obu.config_);
134 if (scalable_channel_layout_config.channel_audio_layer_configs.empty()) {
135 return ClearAndReturnError(
136 absl::StrCat(debugging_context, ". Expected at least one layer."),
137 profile_versions);
138 }
139
140 const auto& first_channel_audio_layer_config =
141 scalable_channel_layout_config.channel_audio_layer_configs[0];
142
143 switch (first_channel_audio_layer_config.loudspeaker_layout) {
144 using enum ChannelAudioLayerConfig::LoudspeakerLayout;
145 case kLayoutMono:
146 case kLayoutStereo:
147 case kLayout5_1_ch:
148 case kLayout5_1_2_ch:
149 case kLayout5_1_4_ch:
150 case kLayout7_1_ch:
151 case kLayout7_1_2_ch:
152 case kLayout7_1_4_ch:
153 case kLayout3_1_2_ch:
154 case kLayoutBinaural:
155 break;
156 case kLayoutReserved10:
157 case kLayoutReserved11:
158 case kLayoutReserved12:
159 case kLayoutReserved13:
160 case kLayoutReserved14:
161 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
162 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
163 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
164 break;
165 case kLayoutExpanded:
166 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
167 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
168 RETURN_IF_NOT_OK(FilterExpandedLoudspeakerLayout(
169 debugging_context,
170 first_channel_audio_layer_config.expanded_loudspeaker_layout,
171 profile_versions));
172 break;
173 }
174
175 if (profile_versions.empty()) {
176 return absl::InvalidArgumentError(absl::StrCat(
177 debugging_context, "has the first loudspeaker_layout= ",
178 first_channel_audio_layer_config.loudspeaker_layout,
179 ". But the requested profiles do support not support this type."));
180 }
181
182 return absl::OkStatus();
183 }
184
FilterAmbisonicsConfig(absl::string_view debugging_context,const AudioElementObu & audio_element_obu,absl::flat_hash_set<ProfileVersion> & profile_versions)185 absl::Status FilterAmbisonicsConfig(
186 absl::string_view debugging_context,
187 const AudioElementObu& audio_element_obu,
188 absl::flat_hash_set<ProfileVersion>& profile_versions) {
189 if (!std::holds_alternative<AmbisonicsConfig>(audio_element_obu.config_)) {
190 return ClearAndReturnError(
191 absl::StrCat(
192 debugging_context,
193 "signals that it is a scene-based audio element, but it does not "
194 "hold an `AmbisonicsConfig`."),
195 profile_versions);
196 }
197
198 auto ambisonics_mode =
199 std::get<AmbisonicsConfig>(audio_element_obu.config_).ambisonics_mode;
200 switch (ambisonics_mode) {
201 using enum AmbisonicsConfig::AmbisonicsMode;
202 case AmbisonicsConfig::kAmbisonicsModeMono:
203 case AmbisonicsConfig::kAmbisonicsModeProjection:
204 break;
205 default:
206 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
207 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
208 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
209 break;
210 }
211
212 if (profile_versions.empty()) {
213 return absl::InvalidArgumentError(absl::StrCat(
214 debugging_context, "has ambisonics_mode= ", ambisonics_mode,
215 ". But the requested profiles do support not support this mode."));
216 }
217 return absl::OkStatus();
218 }
219
FilterProfileForNumSubmixes(absl::string_view mix_presentation_id_for_debugging,int num_sub_mixes_in_mix_presentation,absl::flat_hash_set<ProfileVersion> & profile_versions)220 absl::Status FilterProfileForNumSubmixes(
221 absl::string_view mix_presentation_id_for_debugging,
222 int num_sub_mixes_in_mix_presentation,
223 absl::flat_hash_set<ProfileVersion>& profile_versions) {
224 if (num_sub_mixes_in_mix_presentation > 1) {
225 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
226 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
227 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
228 }
229
230 if (profile_versions.empty()) {
231 return absl::InvalidArgumentError(
232 absl::StrCat(mix_presentation_id_for_debugging, " has ",
233 num_sub_mixes_in_mix_presentation,
234 " sub mixes, but the requested profiles "
235 "do not support this number of sub-mixes."));
236 }
237 return absl::OkStatus();
238 }
239
FilterProfileForHeadphonesRenderingMode(absl::string_view mix_presentation_id_for_debugging,const MixPresentationObu & mix_presentation_obu,absl::flat_hash_set<ProfileVersion> & profile_versions)240 absl::Status FilterProfileForHeadphonesRenderingMode(
241 absl::string_view mix_presentation_id_for_debugging,
242 const MixPresentationObu& mix_presentation_obu,
243 absl::flat_hash_set<ProfileVersion>& profile_versions) {
244 for (const auto& sub_mix : mix_presentation_obu.sub_mixes_) {
245 for (const auto& sub_mix_audio_element : sub_mix.audio_elements) {
246 switch (
247 sub_mix_audio_element.rendering_config.headphones_rendering_mode) {
248 using enum RenderingConfig::HeadphonesRenderingMode;
249 case kHeadphonesRenderingModeReserved2:
250 case kHeadphonesRenderingModeReserved3:
251 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
252 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
253 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
254 break;
255 default:
256 break;
257 }
258
259 if (profile_versions.empty()) {
260 return absl::InvalidArgumentError(absl::StrCat(
261 mix_presentation_id_for_debugging,
262 " has an audio element with headphones rendering mode= ",
263 sub_mix_audio_element.rendering_config.headphones_rendering_mode,
264 " sub mixes, but the requested profiles do support not this "
265 "mode."));
266 }
267 }
268 }
269
270 return absl::OkStatus();
271 }
272
GetNumberOfChannels(const AudioElementWithData & audio_element)273 int GetNumberOfChannels(const AudioElementWithData& audio_element) {
274 int num_channels = 0;
275 for (const auto& [substream_id, labels] :
276 audio_element.substream_id_to_labels) {
277 num_channels += labels.size();
278 }
279 return num_channels;
280 }
281
FilterAudioElementsAndGetNumberOfAudioElementsAndChannels(absl::string_view mix_presentation_id_for_debugging,const absl::flat_hash_map<uint32_t,AudioElementWithData> & audio_elements,const MixPresentationObu & mix_presentation_obu,absl::flat_hash_set<ProfileVersion> & profile_versions,int & num_audio_elements_in_mix_presentation,int & num_channels_in_mix_presentation)282 absl::Status FilterAudioElementsAndGetNumberOfAudioElementsAndChannels(
283 absl::string_view mix_presentation_id_for_debugging,
284 const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
285 const MixPresentationObu& mix_presentation_obu,
286 absl::flat_hash_set<ProfileVersion>& profile_versions,
287 int& num_audio_elements_in_mix_presentation,
288 int& num_channels_in_mix_presentation) {
289 num_audio_elements_in_mix_presentation = 0;
290 num_channels_in_mix_presentation = 0;
291 for (const auto& sub_mix : mix_presentation_obu.sub_mixes_) {
292 num_audio_elements_in_mix_presentation += sub_mix.audio_elements.size();
293 for (const auto& sub_mix_audio_element : sub_mix.audio_elements) {
294 auto iter = audio_elements.find(sub_mix_audio_element.audio_element_id);
295 if (iter == audio_elements.end()) {
296 return ClearAndReturnError(
297 absl::StrCat(mix_presentation_id_for_debugging,
298 " has Audio Element ID= ",
299 sub_mix_audio_element.audio_element_id,
300 " , but there is no Audio Element with that ID."),
301 profile_versions);
302 }
303 RETURN_IF_NOT_OK(ProfileFilter::FilterProfilesForAudioElement(
304 mix_presentation_id_for_debugging, iter->second.obu,
305 profile_versions));
306
307 num_channels_in_mix_presentation += GetNumberOfChannels(iter->second);
308 }
309 }
310 return absl::OkStatus();
311 }
312
FilterProfilesForNumAudioElements(absl::string_view mix_presentation_id_for_debugging,int num_audio_elements_in_mix_presentation,absl::flat_hash_set<ProfileVersion> & profile_versions)313 absl::Status FilterProfilesForNumAudioElements(
314 absl::string_view mix_presentation_id_for_debugging,
315 int num_audio_elements_in_mix_presentation,
316 absl::flat_hash_set<ProfileVersion>& profile_versions) {
317 if (num_audio_elements_in_mix_presentation > kSimpleProfileMaxAudioElements) {
318 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
319 }
320 if (num_audio_elements_in_mix_presentation > kBaseProfileMaxAudioElements) {
321 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
322 }
323 if (num_audio_elements_in_mix_presentation >
324 kBaseEnhancedProfileMaxAudioElements) {
325 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
326 }
327
328 if (profile_versions.empty()) {
329 return absl::InvalidArgumentError(
330 absl::StrCat(mix_presentation_id_for_debugging, " has ",
331 num_audio_elements_in_mix_presentation,
332 " audio elements, but no "
333 "profile supports this number of audio elements."));
334 }
335
336 return absl::OkStatus();
337 }
338
FilterProfilesForNumChannels(absl::string_view mix_presentation_id_for_debugging,int num_channels_in_mix_presentation,absl::flat_hash_set<ProfileVersion> & profile_versions)339 absl::Status FilterProfilesForNumChannels(
340 absl::string_view mix_presentation_id_for_debugging,
341 int num_channels_in_mix_presentation,
342 absl::flat_hash_set<ProfileVersion>& profile_versions) {
343 if (num_channels_in_mix_presentation > kSimpleProfileMaxChannels) {
344 profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
345 }
346 if (num_channels_in_mix_presentation > kBaseProfileMaxChannels) {
347 profile_versions.erase(ProfileVersion::kIamfBaseProfile);
348 }
349 if (num_channels_in_mix_presentation > kBaseEnhancedProfileMaxChannels) {
350 profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
351 }
352
353 if (profile_versions.empty()) {
354 return absl::InvalidArgumentError(
355 absl::StrCat(mix_presentation_id_for_debugging, " has ",
356 num_channels_in_mix_presentation,
357 " channels, but no "
358 "profile supports this number of channels."));
359 }
360
361 return absl::OkStatus();
362 }
363
364 } // namespace
365
FilterProfilesForAudioElement(absl::string_view debugging_context,const AudioElementObu & audio_element_obu,absl::flat_hash_set<ProfileVersion> & profile_versions)366 absl::Status ProfileFilter::FilterProfilesForAudioElement(
367 absl::string_view debugging_context,
368 const AudioElementObu& audio_element_obu,
369 absl::flat_hash_set<ProfileVersion>& profile_versions) {
370 const std::string context_and_audio_element_id_for_debugging = absl::StrCat(
371 debugging_context,
372 " Audio element ID= ", audio_element_obu.GetAudioElementId(), " ");
373
374 RETURN_IF_NOT_OK(FilterAudioElementType(
375 context_and_audio_element_id_for_debugging,
376 audio_element_obu.GetAudioElementType(), profile_versions));
377 // Filter any type-specific properties.
378 switch (audio_element_obu.GetAudioElementType()) {
379 using enum AudioElementObu::AudioElementType;
380 case AudioElementObu::kAudioElementChannelBased:
381 RETURN_IF_NOT_OK(
382 FilterChannelBasedConfig(context_and_audio_element_id_for_debugging,
383 audio_element_obu, profile_versions));
384 break;
385 case AudioElementObu::kAudioElementSceneBased:
386 RETURN_IF_NOT_OK(
387 FilterAmbisonicsConfig(context_and_audio_element_id_for_debugging,
388 audio_element_obu, profile_versions));
389 break;
390 default:
391 break;
392 }
393
394 return absl::OkStatus();
395 }
396
FilterProfilesForMixPresentation(const absl::flat_hash_map<uint32_t,AudioElementWithData> & audio_elements,const MixPresentationObu & mix_presentation_obu,absl::flat_hash_set<ProfileVersion> & profile_versions)397 absl::Status ProfileFilter::FilterProfilesForMixPresentation(
398 const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
399 const MixPresentationObu& mix_presentation_obu,
400 absl::flat_hash_set<ProfileVersion>& profile_versions) {
401 const std::string mix_presentation_id_for_debugging =
402 absl::StrCat("Mix presentation with ID= ",
403 mix_presentation_obu.GetMixPresentationId());
404 MAYBE_RETURN_IF_NOT_OK(FilterProfileForNumSubmixes(
405 mix_presentation_id_for_debugging, mix_presentation_obu.GetNumSubMixes(),
406 profile_versions));
407
408 MAYBE_RETURN_IF_NOT_OK(FilterProfileForHeadphonesRenderingMode(
409 mix_presentation_id_for_debugging, mix_presentation_obu,
410 profile_versions));
411
412 int num_audio_elements_in_mix_presentation;
413 int num_channels_in_mix_presentation;
414 MAYBE_RETURN_IF_NOT_OK(
415 FilterAudioElementsAndGetNumberOfAudioElementsAndChannels(
416 mix_presentation_id_for_debugging, audio_elements,
417 mix_presentation_obu, profile_versions,
418 num_audio_elements_in_mix_presentation,
419 num_channels_in_mix_presentation));
420
421 MAYBE_RETURN_IF_NOT_OK(FilterProfilesForNumAudioElements(
422 mix_presentation_id_for_debugging, num_audio_elements_in_mix_presentation,
423 profile_versions));
424 MAYBE_RETURN_IF_NOT_OK(FilterProfilesForNumChannels(
425 mix_presentation_id_for_debugging, num_channels_in_mix_presentation,
426 profile_versions));
427
428 return absl::OkStatus();
429 }
430
431 } // namespace iamf_tools
432