• 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 #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