1
2 /*
3 * Copyright (c) 2024, Alliance for Open Media. All rights reserved
4 *
5 * This source code is subject to the terms of the BSD 3-Clause Clear License
6 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
7 * License was not distributed with this source code in the LICENSE file, you
8 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
9 * Alliance for Open Media Patent License 1.0 was not distributed with this
10 * source code in the PATENTS file, you can obtain it at
11 * www.aomedia.org/license/patent.
12 */
13
14 #include "iamf/cli/renderer/audio_element_renderer_passthrough.h"
15
16 #include <cstddef>
17 #include <memory>
18 #include <vector>
19
20 #include "absl/base/no_destructor.h"
21 #include "absl/container/flat_hash_map.h"
22 #include "absl/functional/any_invocable.h"
23 #include "absl/log/log.h"
24 #include "absl/memory/memory.h"
25 #include "absl/status/status.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/string_view.h"
28 #include "absl/types/span.h"
29 #include "iamf/cli/channel_label.h"
30 #include "iamf/common/utils/macros.h"
31 #include "iamf/common/utils/map_utils.h"
32 #include "iamf/common/utils/sample_processing_utils.h"
33 #include "iamf/common/utils/validation_utils.h"
34 #include "iamf/obu/audio_element.h"
35 #include "iamf/obu/mix_presentation.h"
36 #include "iamf/obu/types.h"
37
38 namespace iamf_tools {
39 namespace {
40
41 using enum LoudspeakersSsConventionLayout::SoundSystem;
42 using enum ChannelAudioLayerConfig::LoudspeakerLayout;
43 using enum ChannelAudioLayerConfig::ExpandedLoudspeakerLayout;
44
IsLoudspeakerLayoutEquivalentToSoundSystem(ChannelAudioLayerConfig::LoudspeakerLayout loudspeaker_layout,LoudspeakersSsConventionLayout::SoundSystem layout)45 absl::StatusOr<bool> IsLoudspeakerLayoutEquivalentToSoundSystem(
46 ChannelAudioLayerConfig::LoudspeakerLayout loudspeaker_layout,
47 LoudspeakersSsConventionLayout::SoundSystem layout) {
48 static const absl::NoDestructor<
49 absl::flat_hash_map<LoudspeakersSsConventionLayout::SoundSystem,
50 ChannelAudioLayerConfig::LoudspeakerLayout>>
51 kSoundSystemToLoudspeakerLayout({
52 {kSoundSystem12_0_1_0, kLayoutMono},
53 {kSoundSystemA_0_2_0, kLayoutStereo},
54 {kSoundSystemB_0_5_0, kLayout5_1_ch},
55 {kSoundSystemC_2_5_0, kLayout5_1_2_ch},
56 {kSoundSystemD_4_5_0, kLayout5_1_4_ch},
57 {kSoundSystemI_0_7_0, kLayout7_1_ch},
58 {kSoundSystem10_2_7_0, kLayout7_1_2_ch},
59 {kSoundSystemJ_4_7_0, kLayout7_1_4_ch},
60 {kSoundSystem11_2_3_0, kLayout3_1_2_ch},
61 });
62
63 ChannelAudioLayerConfig::LoudspeakerLayout equivalent_loudspeaker_layout;
64 RETURN_IF_NOT_OK(
65 CopyFromMap(*kSoundSystemToLoudspeakerLayout, layout,
66 "`LoudspeakerLayout` equivalent to `SoundSystem`",
67 equivalent_loudspeaker_layout));
68
69 return equivalent_loudspeaker_layout == loudspeaker_layout;
70 }
71
72 // Several expanded layouts are defined as being based on a particular sound
73 // system. The passthrough renderer can be used with the associated sound system
74 // if the expanded layout is a based on the sound system. Other channels can be
75 // omitted.
IsExpandedLoudspeakerLayoutBasedOnSoundSystem(ChannelAudioLayerConfig::ExpandedLoudspeakerLayout expanded_loudspeaker_layout,LoudspeakersSsConventionLayout::SoundSystem layout)76 absl::StatusOr<bool> IsExpandedLoudspeakerLayoutBasedOnSoundSystem(
77 ChannelAudioLayerConfig::ExpandedLoudspeakerLayout
78 expanded_loudspeaker_layout,
79 LoudspeakersSsConventionLayout::SoundSystem layout) {
80 switch (expanded_loudspeaker_layout) {
81 case kExpandedLayoutStereoS:
82 return layout == kSoundSystemD_4_5_0;
83 case kExpandedLayoutLFE:
84 case kExpandedLayoutStereoSS:
85 case kExpandedLayoutStereoRS:
86 case kExpandedLayoutStereoTF:
87 case kExpandedLayoutStereoTB:
88 case kExpandedLayoutTop4Ch:
89 case kExpandedLayout3_0_ch:
90 return layout == kSoundSystemJ_4_7_0;
91 case kExpandedLayout9_1_6_ch:
92 case kExpandedLayoutStereoF:
93 case kExpandedLayoutStereoSi:
94 case kExpandedLayoutStereoTpSi:
95 case kExpandedLayoutTop6Ch:
96 return layout == kSoundSystem13_6_9_0;
97 case kExpandedLayoutReserved13:
98 case kExpandedLayoutReserved255:
99 default:
100 return absl::InvalidArgumentError(absl::StrCat(
101 "Unknown expanded layout cannot be used for pass-through: ",
102 expanded_loudspeaker_layout));
103 }
104 }
105
CanChannelAudioLayerConfigPassThroguhToLayout(const ChannelAudioLayerConfig & channel_config,const Layout & layout)106 absl::StatusOr<bool> CanChannelAudioLayerConfigPassThroguhToLayout(
107 const ChannelAudioLayerConfig& channel_config, const Layout& layout) {
108 switch (layout.layout_type) {
109 case Layout::kLayoutTypeLoudspeakersSsConvention:
110 // Pass-through the associated demixed layer.
111 if (channel_config.loudspeaker_layout == kLayoutExpanded) {
112 RETURN_IF_NOT_OK(
113 ValidateHasValue(channel_config.expanded_loudspeaker_layout,
114 "expanded_loudspeaker_layout"));
115 return IsExpandedLoudspeakerLayoutBasedOnSoundSystem(
116 *channel_config.expanded_loudspeaker_layout,
117 std::get<LoudspeakersSsConventionLayout>(layout.specific_layout)
118 .sound_system);
119 } else {
120 return IsLoudspeakerLayoutEquivalentToSoundSystem(
121 channel_config.loudspeaker_layout,
122 std::get<LoudspeakersSsConventionLayout>(layout.specific_layout)
123 .sound_system);
124 }
125 case Layout::kLayoutTypeBinaural:
126 // Pass-through binaural.
127 return channel_config.loudspeaker_layout == kLayoutBinaural;
128 default:
129 return absl::InvalidArgumentError(
130 absl::StrCat("Unknown layout_type= ", layout.layout_type));
131 }
132 }
133
134 // Finds the layer with the equivalent loudspeaker layout if present.
FindEquivalentLayer(const ScalableChannelLayoutConfig & scalable_channel_layout_config,const Layout & layout)135 absl::StatusOr<ChannelAudioLayerConfig> FindEquivalentLayer(
136 const ScalableChannelLayoutConfig& scalable_channel_layout_config,
137 const Layout& layout) {
138 for (const auto& channel_audio_layer_config :
139 scalable_channel_layout_config.channel_audio_layer_configs) {
140 const auto can_pass_through = CanChannelAudioLayerConfigPassThroguhToLayout(
141 channel_audio_layer_config, layout);
142 if (!can_pass_through.ok()) {
143 return can_pass_through.status();
144 } else if (*can_pass_through) {
145 return channel_audio_layer_config;
146 } else {
147 // Search in the remaining layers.
148 continue;
149 }
150 }
151
152 return absl::InvalidArgumentError(
153 "No equivalent layers found for the requested layout. The passthrough "
154 "render is not suitable here. Down-mixing may be required.");
155 }
156
157 } // namespace
158
159 std::unique_ptr<AudioElementRendererPassThrough>
CreateFromScalableChannelLayoutConfig(const ScalableChannelLayoutConfig & scalable_channel_layout_config,const Layout & playback_layout,size_t num_samples_per_frame)160 AudioElementRendererPassThrough::CreateFromScalableChannelLayoutConfig(
161 const ScalableChannelLayoutConfig& scalable_channel_layout_config,
162 const Layout& playback_layout, size_t num_samples_per_frame) {
163 const auto& equivalent_layer =
164 FindEquivalentLayer(scalable_channel_layout_config, playback_layout);
165 if (!equivalent_layer.ok()) {
166 return nullptr;
167 }
168 const auto& ordered_labels =
169 ChannelLabel::LookupEarChannelOrderFromScalableLoudspeakerLayout(
170 equivalent_layer->loudspeaker_layout,
171 equivalent_layer->expanded_loudspeaker_layout);
172 if (!ordered_labels.ok()) {
173 return nullptr;
174 }
175
176 return absl::WrapUnique(new AudioElementRendererPassThrough(
177 *ordered_labels, num_samples_per_frame));
178 }
179
RenderSamples(absl::Span<const std::vector<InternalSampleType>> samples_to_render,std::vector<InternalSampleType> & rendered_samples)180 absl::Status AudioElementRendererPassThrough::RenderSamples(
181 absl::Span<const std::vector<InternalSampleType>> samples_to_render,
182 std::vector<InternalSampleType>& rendered_samples) {
183 // Flatten the (time, channel) axes into interleaved samples.
184 const absl::AnyInvocable<absl::Status(InternalSampleType, InternalSampleType&)
185 const>
186 kIdentityTransform =
187 [](InternalSampleType input, InternalSampleType& output) {
188 output = input;
189 return absl::OkStatus();
190 };
191
192 return ConvertTimeChannelToInterleaved(samples_to_render, kIdentityTransform,
193 rendered_samples);
194 }
195
196 } // namespace iamf_tools
197