1 /*
2 * Copyright (c) 2023, 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/rendering_mix_presentation_finalizer.h"
13
14 #include <algorithm>
15 #include <cmath>
16 #include <cstddef>
17 #include <cstdint>
18 #include <cstring>
19 #include <functional>
20 #include <list>
21 #include <memory>
22 #include <string>
23 #include <utility>
24 #include <vector>
25
26 #include "absl/base/nullability.h"
27 #include "absl/container/flat_hash_map.h"
28 #include "absl/container/flat_hash_set.h"
29 #include "absl/functional/any_invocable.h"
30 #include "absl/log/check.h"
31 #include "absl/log/log.h"
32 #include "absl/status/status.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/time/clock.h"
36 #include "absl/time/time.h"
37 #include "absl/types/span.h"
38 #include "iamf/cli/audio_element_with_data.h"
39 #include "iamf/cli/cli_util.h"
40 #include "iamf/cli/demixing_module.h"
41 #include "iamf/cli/loudness_calculator_base.h"
42 #include "iamf/cli/loudness_calculator_factory_base.h"
43 #include "iamf/cli/parameter_block_with_data.h"
44 #include "iamf/cli/renderer/audio_element_renderer_base.h"
45 #include "iamf/cli/renderer_factory.h"
46 #include "iamf/cli/sample_processor_base.h"
47 #include "iamf/common/utils/macros.h"
48 #include "iamf/common/utils/numeric_utils.h"
49 #include "iamf/common/utils/sample_processing_utils.h"
50 #include "iamf/common/utils/validation_utils.h"
51 #include "iamf/obu/audio_element.h"
52 #include "iamf/obu/codec_config.h"
53 #include "iamf/obu/mix_presentation.h"
54 #include "iamf/obu/param_definitions.h"
55 #include "iamf/obu/parameter_block.h"
56 #include "iamf/obu/types.h"
57
58 namespace iamf_tools {
59
60 namespace {
61
62 using LayoutRenderingMetadata =
63 RenderingMixPresentationFinalizer::LayoutRenderingMetadata;
64 using SubmixRenderingMetadata =
65 RenderingMixPresentationFinalizer::SubmixRenderingMetadata;
66
CanRenderAnyLayout(const std::vector<SubmixRenderingMetadata> & rendering_metadata)67 bool CanRenderAnyLayout(
68 const std::vector<SubmixRenderingMetadata>& rendering_metadata) {
69 for (auto& submix_rendering_metadata : rendering_metadata) {
70 for (auto& layout_rendering_metadata :
71 submix_rendering_metadata.layout_rendering_metadata) {
72 if (layout_rendering_metadata.can_render) {
73 return true;
74 }
75 }
76 }
77 return false;
78 }
79
CollectAudioElementsInSubMix(const absl::flat_hash_map<uint32_t,AudioElementWithData> & audio_elements,const std::vector<SubMixAudioElement> & sub_mix_audio_elements,std::vector<const AudioElementWithData * > & audio_elements_in_sub_mix)80 absl::Status CollectAudioElementsInSubMix(
81 const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
82 const std::vector<SubMixAudioElement>& sub_mix_audio_elements,
83 std::vector<const AudioElementWithData*>& audio_elements_in_sub_mix) {
84 audio_elements_in_sub_mix.reserve(sub_mix_audio_elements.size());
85 for (const auto& audio_element : sub_mix_audio_elements) {
86 auto iter = audio_elements.find(audio_element.audio_element_id);
87 if (iter == audio_elements.end()) {
88 return absl::InvalidArgumentError(absl::StrCat(
89 "Audio Element with ID= ", audio_element.audio_element_id,
90 " not found"));
91 }
92 audio_elements_in_sub_mix.push_back(&iter->second);
93 }
94
95 return absl::OkStatus();
96 }
97
GetCommonCodecConfigPropertiesFromAudioElementIds(const std::vector<const AudioElementWithData * > & audio_elements_in_sub_mix,uint32_t & common_sample_rate,uint8_t & common_bit_depth,uint32_t & common_num_samples_per_frame,bool & requires_resampling)98 absl::Status GetCommonCodecConfigPropertiesFromAudioElementIds(
99 const std::vector<const AudioElementWithData*>& audio_elements_in_sub_mix,
100 uint32_t& common_sample_rate, uint8_t& common_bit_depth,
101 uint32_t& common_num_samples_per_frame, bool& requires_resampling) {
102 absl::flat_hash_set<uint32_t> sample_rates;
103 absl::flat_hash_set<uint32_t> num_samples_per_frame;
104 absl::flat_hash_set<uint8_t> bit_depths;
105
106 // Get all the bit-depths and sample_rates from each Audio Element.
107 for (const auto* audio_element : audio_elements_in_sub_mix) {
108 num_samples_per_frame.insert(
109 audio_element->codec_config->GetNumSamplesPerFrame());
110 sample_rates.insert(audio_element->codec_config->GetOutputSampleRate());
111 bit_depths.insert(
112 audio_element->codec_config->GetBitDepthToMeasureLoudness());
113 }
114
115 RETURN_IF_NOT_OK(GetCommonSampleRateAndBitDepth(
116 sample_rates, bit_depths, common_sample_rate, common_bit_depth,
117 requires_resampling));
118 if (num_samples_per_frame.size() != 1) {
119 return absl::InvalidArgumentError(
120 "Audio elements in a submix must have the same number of samples per "
121 "frame.");
122 }
123 common_num_samples_per_frame = *num_samples_per_frame.begin();
124
125 return absl::OkStatus();
126 }
127
128 using AudioElementRenderingMetadata =
129 RenderingMixPresentationFinalizer::AudioElementRenderingMetadata;
130
InitializeRenderingMetadata(const RendererFactoryBase & renderer_factory,const std::vector<const AudioElementWithData * > & audio_elements_in_sub_mix,const std::vector<SubMixAudioElement> & sub_mix_audio_elements,const Layout & loudness_layout,const uint32_t common_sample_rate,std::vector<AudioElementRenderingMetadata> & rendering_metadata_array)131 absl::Status InitializeRenderingMetadata(
132 const RendererFactoryBase& renderer_factory,
133 const std::vector<const AudioElementWithData*>& audio_elements_in_sub_mix,
134 const std::vector<SubMixAudioElement>& sub_mix_audio_elements,
135 const Layout& loudness_layout, const uint32_t common_sample_rate,
136 std::vector<AudioElementRenderingMetadata>& rendering_metadata_array) {
137 rendering_metadata_array.resize(audio_elements_in_sub_mix.size());
138
139 for (int i = 0; i < audio_elements_in_sub_mix.size(); i++) {
140 const auto& sub_mix_audio_element = *audio_elements_in_sub_mix[i];
141 auto& rendering_metadata = rendering_metadata_array[i];
142 rendering_metadata.audio_element = &(sub_mix_audio_element.obu);
143 rendering_metadata.codec_config = sub_mix_audio_element.codec_config;
144 rendering_metadata.renderer = renderer_factory.CreateRendererForLayout(
145 sub_mix_audio_element.obu.audio_substream_ids_,
146 sub_mix_audio_element.substream_id_to_labels,
147 rendering_metadata.audio_element->GetAudioElementType(),
148 sub_mix_audio_element.obu.config_,
149 sub_mix_audio_elements[i].rendering_config, loudness_layout,
150 static_cast<size_t>(
151 rendering_metadata.codec_config->GetNumSamplesPerFrame()));
152
153 if (rendering_metadata.renderer == nullptr) {
154 return absl::UnknownError("Unable to create renderer.");
155 }
156
157 const uint32_t output_sample_rate =
158 sub_mix_audio_element.codec_config->GetOutputSampleRate();
159 if (common_sample_rate != output_sample_rate) {
160 // Theoretically, we would have to resample this audio element to the
161 // common sample rate. However, as of IAMF v1.1.0, the spec forbids
162 // multiple Codec Config OBUs. This case is not possible to occur with a
163 // single Codec Config OBU.
164 return absl::UnimplementedError(
165 absl::StrCat("OBUs with different sample rates not supported yet: (",
166 common_sample_rate, " != ", output_sample_rate, ")."));
167 }
168 }
169
170 return absl::OkStatus();
171 }
172
FlushUntilNonEmptyOrTimeout(AudioElementRendererBase & audio_element_renderer,std::vector<InternalSampleType> & rendered_samples)173 absl::Status FlushUntilNonEmptyOrTimeout(
174 AudioElementRendererBase& audio_element_renderer,
175 std::vector<InternalSampleType>& rendered_samples) {
176 static const int kMaxNumTries = 500;
177 for (int i = 0; i < kMaxNumTries; i++) {
178 RETURN_IF_NOT_OK(audio_element_renderer.Flush(rendered_samples));
179 if (!rendered_samples.empty()) {
180 // Usually samples will be ready right away. So avoid sleeping.
181 return absl::OkStatus();
182 }
183 absl::SleepFor(absl::Milliseconds(10));
184 }
185 return absl::DeadlineExceededError("Timed out waiting for samples.");
186 }
187
RenderLabeledFrameToLayout(const LabeledFrame & labeled_frame,const AudioElementRenderingMetadata & rendering_metadata,std::vector<InternalSampleType> & rendered_samples)188 absl::Status RenderLabeledFrameToLayout(
189 const LabeledFrame& labeled_frame,
190 const AudioElementRenderingMetadata& rendering_metadata,
191 std::vector<InternalSampleType>& rendered_samples) {
192 const auto num_time_ticks =
193 rendering_metadata.renderer->RenderLabeledFrame(labeled_frame);
194 if (!num_time_ticks.ok()) {
195 return num_time_ticks.status();
196 } else if (*num_time_ticks >
197 static_cast<size_t>(
198 rendering_metadata.codec_config->GetNumSamplesPerFrame())) {
199 return absl::InvalidArgumentError("Too many samples in this frame");
200 } else if (*num_time_ticks == 0) {
201 // This was an empty frame.
202 return absl::OkStatus();
203 }
204
205 return FlushUntilNonEmptyOrTimeout(*rendering_metadata.renderer,
206 rendered_samples);
207 }
208
GetParameterBlockLinearMixGainsPerTick(uint32_t common_sample_rate,const ParameterBlockWithData & parameter_block,const MixGainParamDefinition & mix_gain,std::vector<float> & linear_mix_gain_per_tick)209 absl::Status GetParameterBlockLinearMixGainsPerTick(
210 uint32_t common_sample_rate, const ParameterBlockWithData& parameter_block,
211 const MixGainParamDefinition& mix_gain,
212 std::vector<float>& linear_mix_gain_per_tick) {
213 if (mix_gain.parameter_rate_ != common_sample_rate) {
214 // TODO(b/283281856): Support resampling parameter blocks.
215 return absl::UnimplementedError(
216 "Parameter blocks that require resampling are not supported yet.");
217 }
218
219 const int16_t default_mix_gain = mix_gain.default_mix_gain_;
220 // Initialize to the default gain value.
221 std::fill(linear_mix_gain_per_tick.begin(), linear_mix_gain_per_tick.end(),
222 std::pow(10.0f, Q7_8ToFloat(default_mix_gain) / 20.0f));
223
224 InternalTimestamp cur_tick = parameter_block.start_timestamp;
225 // Process as many ticks as possible until all are found or the parameter
226 // block ends.
227 while (cur_tick < parameter_block.end_timestamp &&
228 (cur_tick - parameter_block.start_timestamp) <
229 linear_mix_gain_per_tick.size()) {
230 RETURN_IF_NOT_OK(parameter_block.obu->GetLinearMixGain(
231 cur_tick - parameter_block.start_timestamp,
232 linear_mix_gain_per_tick[cur_tick - parameter_block.start_timestamp]));
233 cur_tick++;
234 }
235 return absl::OkStatus();
236 }
237
238 // Fills in the output `mix_gains` with the gain in Q7.8 format to apply at each
239 // tick.
240 // TODO(b/288073842): Consider improving computational efficiency instead of
241 // searching through all parameter blocks for each frame.
242 // TODO(b/379961928): Remove this function once the new
243 // `GetParameterBlockLinearMixGainsPerTick()` is in use.
GetParameterBlockLinearMixGainsPerTick(uint32_t common_sample_rate,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,const std::list<ParameterBlockWithData> & parameter_blocks,const MixGainParamDefinition & mix_gain,std::vector<float> & linear_mix_gain_per_tick)244 absl::Status GetParameterBlockLinearMixGainsPerTick(
245 uint32_t common_sample_rate, InternalTimestamp start_timestamp,
246 InternalTimestamp end_timestamp,
247 const std::list<ParameterBlockWithData>& parameter_blocks,
248 const MixGainParamDefinition& mix_gain,
249 std::vector<float>& linear_mix_gain_per_tick) {
250 if (mix_gain.parameter_rate_ != common_sample_rate) {
251 // TODO(b/283281856): Support resampling parameter blocks.
252 return absl::UnimplementedError(
253 "Parameter blocks that require resampling are not supported yet.");
254 }
255
256 const auto parameter_id = mix_gain.parameter_id_;
257 const int16_t default_mix_gain = mix_gain.default_mix_gain_;
258
259 // Initialize to the default gain value.
260 std::fill(linear_mix_gain_per_tick.begin(), linear_mix_gain_per_tick.end(),
261 std::pow(10.0f, Q7_8ToFloat(default_mix_gain) / 20.0f));
262
263 InternalTimestamp cur_tick = start_timestamp;
264
265 // Find the mix gain at each tick. May terminate early if there are samples to
266 // trim at the end.
267 while (cur_tick < end_timestamp &&
268 (cur_tick - start_timestamp) < linear_mix_gain_per_tick.size()) {
269 // Find the parameter block that this tick occurs during.
270 const auto parameter_block_iter = std::find_if(
271 parameter_blocks.begin(), parameter_blocks.end(),
272 [cur_tick, parameter_id](const auto& parameter_block) {
273 return parameter_block.obu->parameter_id_ == parameter_id &&
274 parameter_block.start_timestamp <= cur_tick &&
275 cur_tick < parameter_block.end_timestamp;
276 });
277 if (parameter_block_iter == parameter_blocks.end()) {
278 // Default mix gain will be used for this frame. Logic elsewhere validates
279 // the rest of the audio frames have consistent coverage.
280 break;
281 }
282
283 // Process as many ticks as possible until all are found or the parameter
284 // block ends.
285 while (cur_tick < end_timestamp &&
286 cur_tick < parameter_block_iter->end_timestamp &&
287 (cur_tick - start_timestamp) < linear_mix_gain_per_tick.size()) {
288 RETURN_IF_NOT_OK(parameter_block_iter->obu->GetLinearMixGain(
289 cur_tick - parameter_block_iter->start_timestamp,
290 linear_mix_gain_per_tick[cur_tick - start_timestamp]));
291 cur_tick++;
292 }
293 }
294
295 return absl::OkStatus();
296 }
297
GetAndApplyMixGain(uint32_t common_sample_rate,const ParameterBlockWithData & parameter_block,const MixGainParamDefinition & mix_gain,int32_t num_channels,std::vector<InternalSampleType> & rendered_samples)298 absl::Status GetAndApplyMixGain( // NOLINT
299 uint32_t common_sample_rate, const ParameterBlockWithData& parameter_block,
300 const MixGainParamDefinition& mix_gain, int32_t num_channels,
301 std::vector<InternalSampleType>& rendered_samples) {
302 if (rendered_samples.size() % num_channels != 0) {
303 return absl::InvalidArgumentError(absl::StrCat(
304 "Expected an integer number of interlaced channels. "
305 "renderered_samples.size()= ",
306 rendered_samples.size(), ", num_channels= ", num_channels));
307 }
308
309 // Get the mix gain on a per tick basis from the parameter block.
310 std::vector<float> linear_mix_gain_per_tick(rendered_samples.size() /
311 num_channels);
312 RETURN_IF_NOT_OK(GetParameterBlockLinearMixGainsPerTick(
313 common_sample_rate, parameter_block, mix_gain, linear_mix_gain_per_tick));
314
315 if (!linear_mix_gain_per_tick.empty()) {
316 LOG_FIRST_N(INFO, 6) << " First tick in this frame has gain: "
317 << linear_mix_gain_per_tick.front();
318 }
319
320 for (int tick = 0; tick < linear_mix_gain_per_tick.size(); tick++) {
321 for (int channel = 0; channel < num_channels; channel++) {
322 // Apply the same mix gain to all `num_channels` associated with this
323 // tick.
324 rendered_samples[tick * num_channels + channel] *=
325 linear_mix_gain_per_tick[tick];
326 }
327 }
328
329 return absl::OkStatus();
330 }
331
332 // TODO(b/379961928): Remove once the new GetAndApplyMixGain is in use.
GetAndApplyMixGain(uint32_t common_sample_rate,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,const std::list<ParameterBlockWithData> & parameter_blocks,const MixGainParamDefinition & mix_gain,int32_t num_channels,std::vector<float> & linear_mix_gain_per_tick,std::vector<InternalSampleType> & rendered_samples)333 absl::Status GetAndApplyMixGain(
334 uint32_t common_sample_rate, InternalTimestamp start_timestamp,
335 InternalTimestamp end_timestamp,
336 const std::list<ParameterBlockWithData>& parameter_blocks,
337 const MixGainParamDefinition& mix_gain, int32_t num_channels,
338 std::vector<float>& linear_mix_gain_per_tick,
339 std::vector<InternalSampleType>& rendered_samples) {
340 if (rendered_samples.size() % num_channels != 0) {
341 return absl::InvalidArgumentError(absl::StrCat(
342 "Expected an integer number of interlaced channels. "
343 "renderered_samples.size()= ",
344 rendered_samples.size(), ", num_channels= ", num_channels));
345 }
346
347 // Get the mix gain on a per tick basis from the parameter block.
348 linear_mix_gain_per_tick.resize(rendered_samples.size() / num_channels, 0.0f);
349 RETURN_IF_NOT_OK(GetParameterBlockLinearMixGainsPerTick(
350 common_sample_rate, start_timestamp, end_timestamp, parameter_blocks,
351 mix_gain, linear_mix_gain_per_tick));
352
353 if (!linear_mix_gain_per_tick.empty()) {
354 LOG_FIRST_N(INFO, 6) << " First tick in this frame has gain: "
355 << linear_mix_gain_per_tick.front();
356 }
357
358 for (int tick = 0; tick < linear_mix_gain_per_tick.size(); tick++) {
359 for (int channel = 0; channel < num_channels; channel++) {
360 // Apply the same mix gain to all `num_channels` associated with this
361 // tick.
362 rendered_samples[tick * num_channels + channel] *=
363 linear_mix_gain_per_tick[tick];
364 }
365 }
366
367 return absl::OkStatus();
368 }
369
MixAudioElements(std::vector<std::vector<InternalSampleType>> & rendered_audio_elements,std::vector<InternalSampleType> & rendered_samples)370 absl::Status MixAudioElements(
371 std::vector<std::vector<InternalSampleType>>& rendered_audio_elements,
372 std::vector<InternalSampleType>& rendered_samples) {
373 const size_t num_samples = rendered_audio_elements.empty()
374 ? 0
375 : rendered_audio_elements.front().size();
376 rendered_samples.reserve(num_samples);
377
378 for (const auto& rendered_audio_element : rendered_audio_elements) {
379 if (rendered_audio_element.size() != num_samples) {
380 return absl::UnknownError(
381 "Expected all frames to have the same number of samples.");
382 }
383 }
384
385 for (int i = 0; i < num_samples; i++) {
386 InternalSampleType mixed_sample = 0;
387 // Sum all audio elements for this tick.
388 for (const auto& rendered_audio_element : rendered_audio_elements) {
389 mixed_sample += rendered_audio_element[i];
390 }
391 // Push the clipped result.
392 rendered_samples.push_back(mixed_sample);
393 }
394
395 return absl::OkStatus();
396 }
397
398 // Fills in `valid_rendered_samples` which is a view backed by
399 // `rendered_samples` of the ticks actually rendered.
RenderAllFramesForLayout(int32_t num_channels,const std::vector<SubMixAudioElement> sub_mix_audio_elements,const MixGainParamDefinition & output_mix_gain,const IdLabeledFrameMap & id_to_labeled_frame,const std::vector<AudioElementRenderingMetadata> & rendering_metadata_array,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,const std::list<ParameterBlockWithData> & parameter_blocks,const uint32_t common_sample_rate,std::vector<std::vector<int32_t>> & rendered_samples,absl::Span<const std::vector<int32_t>> & valid_rendered_samples)400 absl::Status RenderAllFramesForLayout(
401 int32_t num_channels,
402 const std::vector<SubMixAudioElement> sub_mix_audio_elements,
403 const MixGainParamDefinition& output_mix_gain,
404 const IdLabeledFrameMap& id_to_labeled_frame,
405 const std::vector<AudioElementRenderingMetadata>& rendering_metadata_array,
406 InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
407 const std::list<ParameterBlockWithData>& parameter_blocks,
408 const uint32_t common_sample_rate,
409 std::vector<std::vector<int32_t>>& rendered_samples,
410 absl::Span<const std::vector<int32_t>>& valid_rendered_samples) {
411 // Each audio element rendered individually with `element_mix_gain` applied.
412 std::vector<std::vector<InternalSampleType>> rendered_audio_elements(
413 sub_mix_audio_elements.size());
414 std::vector<float> linear_mix_gain_per_tick;
415 for (int i = 0; i < sub_mix_audio_elements.size(); i++) {
416 const SubMixAudioElement& sub_mix_audio_element = sub_mix_audio_elements[i];
417 const auto audio_element_id = sub_mix_audio_element.audio_element_id;
418 const auto& rendering_metadata = rendering_metadata_array[i];
419
420 if (id_to_labeled_frame.find(audio_element_id) !=
421 id_to_labeled_frame.end()) {
422 const auto& labeled_frame = id_to_labeled_frame.at(audio_element_id);
423 // Render the frame to the specified `loudness_layout` and apply element
424 // mix gain.
425 RETURN_IF_NOT_OK(RenderLabeledFrameToLayout(
426 labeled_frame, rendering_metadata, rendered_audio_elements[i]));
427 }
428 RETURN_IF_NOT_OK(GetAndApplyMixGain(
429 common_sample_rate, start_timestamp, end_timestamp, parameter_blocks,
430 sub_mix_audio_element.element_mix_gain, num_channels,
431 linear_mix_gain_per_tick, rendered_audio_elements[i]));
432 }
433
434 // Mix the audio elements.
435 std::vector<InternalSampleType> rendered_samples_internal;
436 RETURN_IF_NOT_OK(
437 MixAudioElements(rendered_audio_elements, rendered_samples_internal));
438
439 LOG_FIRST_N(INFO, 1) << " Applying output_mix_gain.default_mix_gain= "
440 << output_mix_gain.default_mix_gain_;
441
442 RETURN_IF_NOT_OK(
443 GetAndApplyMixGain(common_sample_rate, start_timestamp, end_timestamp,
444 parameter_blocks, output_mix_gain, num_channels,
445 linear_mix_gain_per_tick, rendered_samples_internal));
446
447 // Convert the rendered samples to int32, clipping if needed.
448 size_t num_ticks = 0;
449 RETURN_IF_NOT_OK(ConvertInterleavedToTimeChannel(
450 absl::MakeConstSpan(rendered_samples_internal), num_channels,
451 absl::AnyInvocable<absl::Status(InternalSampleType, int32_t&) const>(
452 NormalizedFloatingPointToInt32<InternalSampleType>),
453 rendered_samples, num_ticks));
454 valid_rendered_samples =
455 absl::MakeConstSpan(rendered_samples).first(num_ticks);
456 return absl::OkStatus();
457 }
458
ValidateUserLoudness(const LoudnessInfo & user_loudness,const uint32_t mix_presentation_id,const int sub_mix_index,const int layout_index,const LoudnessInfo & output_loudness,bool & loudness_matches_user_data)459 absl::Status ValidateUserLoudness(const LoudnessInfo& user_loudness,
460 const uint32_t mix_presentation_id,
461 const int sub_mix_index,
462 const int layout_index,
463 const LoudnessInfo& output_loudness,
464 bool& loudness_matches_user_data) {
465 const std::string mix_presentation_sub_mix_layout_index =
466 absl::StrCat("Mix Presentation(ID ", mix_presentation_id, ")->sub_mixes[",
467 sub_mix_index, "]->layouts[", layout_index, "]: ");
468 if (output_loudness.integrated_loudness !=
469 user_loudness.integrated_loudness) {
470 LOG(ERROR) << mix_presentation_sub_mix_layout_index
471 << "Computed integrated loudness different from "
472 << "user specification: " << output_loudness.integrated_loudness
473 << " vs " << user_loudness.integrated_loudness;
474 loudness_matches_user_data = false;
475 }
476
477 if (output_loudness.digital_peak != user_loudness.digital_peak) {
478 LOG(ERROR) << mix_presentation_sub_mix_layout_index
479 << "Computed digital peak different from "
480 << "user specification: " << output_loudness.digital_peak
481 << " vs " << user_loudness.digital_peak;
482 loudness_matches_user_data = false;
483 }
484
485 if (output_loudness.info_type & LoudnessInfo::kTruePeak) {
486 if (output_loudness.true_peak != user_loudness.true_peak) {
487 LOG(ERROR) << mix_presentation_sub_mix_layout_index
488 << "Computed true peak different from "
489 << "user specification: " << output_loudness.true_peak
490 << " vs " << user_loudness.true_peak;
491 loudness_matches_user_data = false;
492 }
493 }
494
495 // Anchored loudness and layout extension are copied from the user input
496 // and do not need to be validated.
497
498 return absl::OkStatus();
499 }
500
501 // Calculates the loudness of the rendered samples. These rendered samples are
502 // for a specific timestamp for a given submix and layout. If
503 // `validate_loudness` is true, then the user provided loudness values are
504 // validated against the computed values.
UpdateLoudnessInfoForLayout(bool validate_loudness,const LoudnessInfo & input_loudness,const uint32_t mix_presentation_id,const int sub_mix_index,const int layout_index,bool & loudness_matches_user_data,std::unique_ptr<LoudnessCalculatorBase> loudness_calculator,LoudnessInfo & output_calculated_loudness)505 absl::Status UpdateLoudnessInfoForLayout(
506 bool validate_loudness, const LoudnessInfo& input_loudness,
507 const uint32_t mix_presentation_id, const int sub_mix_index,
508 const int layout_index, bool& loudness_matches_user_data,
509 std::unique_ptr<LoudnessCalculatorBase> loudness_calculator,
510 LoudnessInfo& output_calculated_loudness) {
511 // Copy the final loudness values back to the output OBU.
512 auto calculated_loudness_info = loudness_calculator->QueryLoudness();
513 if (!calculated_loudness_info.ok()) {
514 return calculated_loudness_info.status();
515 }
516
517 if (validate_loudness) {
518 // Validate any user provided loudness values match computed values.
519 RETURN_IF_NOT_OK(ValidateUserLoudness(
520 input_loudness, mix_presentation_id, sub_mix_index, layout_index,
521 *calculated_loudness_info, loudness_matches_user_data));
522 }
523 output_calculated_loudness = *calculated_loudness_info;
524 return absl::OkStatus();
525 }
526
527 // Generates rendering metadata for all layouts within a submix. This includes
528 // optionally creating a sample processor and/or a loudness calculator for each
529 // layout.
GenerateRenderingMetadataForLayouts(const RendererFactoryBase & renderer_factory,const LoudnessCalculatorFactoryBase * loudness_calculator_factory,const RenderingMixPresentationFinalizer::SampleProcessorFactory & sample_processor_factory,const DecodedUleb128 mix_presentation_id,const MixPresentationSubMix & sub_mix,int sub_mix_index,const std::vector<const AudioElementWithData * > & audio_elements_in_sub_mix,uint32_t common_sample_rate,uint8_t rendering_bit_depth,uint32_t common_num_samples_per_frame,std::vector<LayoutRenderingMetadata> & output_layout_rendering_metadata)530 absl::Status GenerateRenderingMetadataForLayouts(
531 const RendererFactoryBase& renderer_factory,
532 const LoudnessCalculatorFactoryBase* loudness_calculator_factory,
533 const RenderingMixPresentationFinalizer::SampleProcessorFactory&
534 sample_processor_factory,
535 const DecodedUleb128 mix_presentation_id,
536 const MixPresentationSubMix& sub_mix, int sub_mix_index,
537 const std::vector<const AudioElementWithData*>& audio_elements_in_sub_mix,
538 uint32_t common_sample_rate, uint8_t rendering_bit_depth,
539 uint32_t common_num_samples_per_frame,
540 std::vector<LayoutRenderingMetadata>& output_layout_rendering_metadata) {
541 output_layout_rendering_metadata.resize(sub_mix.layouts.size());
542 for (int layout_index = 0; layout_index < sub_mix.layouts.size();
543 layout_index++) {
544 LayoutRenderingMetadata& layout_rendering_metadata =
545 output_layout_rendering_metadata[layout_index];
546 const auto& layout = sub_mix.layouts[layout_index];
547
548 int32_t num_channels = 0;
549 auto can_render_status = MixPresentationObu::GetNumChannelsFromLayout(
550 layout.loudness_layout, num_channels);
551 layout_rendering_metadata.num_channels = num_channels;
552
553 can_render_status.Update(InitializeRenderingMetadata(
554 renderer_factory, audio_elements_in_sub_mix, sub_mix.audio_elements,
555 layout.loudness_layout, common_sample_rate,
556 layout_rendering_metadata.audio_element_rendering_metadata));
557
558 if (!can_render_status.ok()) {
559 layout_rendering_metadata.can_render = false;
560 continue;
561 } else {
562 layout_rendering_metadata.can_render = true;
563 }
564 if (loudness_calculator_factory != nullptr) {
565 // Optionally create a loudness calculator.
566 layout_rendering_metadata.loudness_calculator =
567 loudness_calculator_factory->CreateLoudnessCalculator(
568 layout, common_num_samples_per_frame, common_sample_rate,
569 rendering_bit_depth);
570 }
571 // Optionally create a post-processor.
572 layout_rendering_metadata.sample_processor = sample_processor_factory(
573 mix_presentation_id, sub_mix_index, layout_index,
574 layout.loudness_layout, num_channels, common_sample_rate,
575 rendering_bit_depth, common_num_samples_per_frame);
576
577 // Pre-allocate a buffer to store a frame's worth of rendered samples.
578 layout_rendering_metadata.rendered_samples.resize(
579 common_num_samples_per_frame, std::vector<int32_t>(num_channels, 0));
580 }
581
582 return absl::OkStatus();
583 }
584
585 // We generate one rendering metadata object for each submix. Once this
586 // metadata is generated, we will loop through it to render all submixes
587 // for a given timestamp. Within a submix, there can be many different audio
588 // elements and layouts that need to be rendered as well. Not all of these
589 // need to be rendered; only the ones that either have a wav writer or a
590 // loudness calculator.
GenerateRenderingMetadataForSubmixes(const RendererFactoryBase & renderer_factory,absl::Nullable<const LoudnessCalculatorFactoryBase * > loudness_calculator_factory,const RenderingMixPresentationFinalizer::SampleProcessorFactory & sample_processor_factory,const absl::flat_hash_map<uint32_t,AudioElementWithData> & audio_elements,const MixPresentationObu & mix_presentation_obu,std::vector<SubmixRenderingMetadata> & output_rendering_metadata)591 absl::Status GenerateRenderingMetadataForSubmixes(
592 const RendererFactoryBase& renderer_factory,
593 absl::Nullable<const LoudnessCalculatorFactoryBase*>
594 loudness_calculator_factory,
595 const RenderingMixPresentationFinalizer::SampleProcessorFactory&
596 sample_processor_factory,
597 const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
598 const MixPresentationObu& mix_presentation_obu,
599 std::vector<SubmixRenderingMetadata>& output_rendering_metadata) {
600 const auto mix_presentation_id = mix_presentation_obu.GetMixPresentationId();
601 output_rendering_metadata.resize(mix_presentation_obu.sub_mixes_.size());
602 for (int sub_mix_index = 0;
603 sub_mix_index < mix_presentation_obu.sub_mixes_.size();
604 ++sub_mix_index) {
605 SubmixRenderingMetadata& submix_rendering_metadata =
606 output_rendering_metadata[sub_mix_index];
607 const auto& sub_mix = mix_presentation_obu.sub_mixes_[sub_mix_index];
608
609 // Pointers to audio elements in this sub mix; useful later.
610 std::vector<const AudioElementWithData*> audio_elements_in_sub_mix;
611 RETURN_IF_NOT_OK(CollectAudioElementsInSubMix(
612 audio_elements, sub_mix.audio_elements, audio_elements_in_sub_mix));
613
614 submix_rendering_metadata.audio_elements_in_sub_mix =
615 sub_mix.audio_elements;
616 submix_rendering_metadata.mix_gain =
617 std::make_unique<MixGainParamDefinition>(sub_mix.output_mix_gain);
618
619 // Data common to all audio elements and layouts.
620 bool requires_resampling;
621 uint32_t common_num_samples_per_frame;
622 uint8_t rendering_bit_depth;
623 RETURN_IF_NOT_OK(GetCommonCodecConfigPropertiesFromAudioElementIds(
624 audio_elements_in_sub_mix, submix_rendering_metadata.common_sample_rate,
625 rendering_bit_depth, common_num_samples_per_frame,
626 requires_resampling));
627 if (requires_resampling) {
628 // Detected multiple Codec Config OBUs with different sample rates or
629 // bit-depths. As of IAMF v1.1.0, multiple Codec Config OBUs in the same
630 // IA sequence are never permitted. The spec implies we would have to
631 // resample to a common sample rate and/or bit-depth.
632 return absl::UnimplementedError(
633 "This implementation does not support mixing Codec Config OBUs with "
634 "different sample rates or bit-depths.");
635 }
636 RETURN_IF_NOT_OK(GenerateRenderingMetadataForLayouts(
637 renderer_factory, loudness_calculator_factory, sample_processor_factory,
638 mix_presentation_id, sub_mix, sub_mix_index, audio_elements_in_sub_mix,
639 submix_rendering_metadata.common_sample_rate, rendering_bit_depth,
640 common_num_samples_per_frame,
641 submix_rendering_metadata.layout_rendering_metadata));
642 }
643 return absl::OkStatus();
644 }
645
FlushPostProcessors(std::vector<SubmixRenderingMetadata> & rendering_metadata)646 absl::Status FlushPostProcessors(
647 std::vector<SubmixRenderingMetadata>& rendering_metadata) {
648 for (auto& submix_rendering_metadata : rendering_metadata) {
649 for (auto& layout_rendering_metadata :
650 submix_rendering_metadata.layout_rendering_metadata) {
651 if (layout_rendering_metadata.sample_processor != nullptr) {
652 RETURN_IF_NOT_OK(layout_rendering_metadata.sample_processor->Flush());
653 }
654 }
655 }
656
657 return absl::OkStatus();
658 }
659
FillLoudnessForMixPresentation(bool validate_loudness,std::vector<SubmixRenderingMetadata> & rendering_metadata,MixPresentationObu & mix_presentation_obu)660 absl::Status FillLoudnessForMixPresentation(
661 bool validate_loudness,
662 std::vector<SubmixRenderingMetadata>& rendering_metadata,
663 MixPresentationObu& mix_presentation_obu) {
664 bool loudness_matches_user_data = true;
665 int submix_index = 0;
666 for (auto& submix_rendering_metadata : rendering_metadata) {
667 int layout_index = 0;
668 for (auto& layout_rendering_metadata :
669 submix_rendering_metadata.layout_rendering_metadata) {
670 if (layout_rendering_metadata.loudness_calculator != nullptr) {
671 RETURN_IF_NOT_OK(UpdateLoudnessInfoForLayout(
672 validate_loudness,
673 mix_presentation_obu.sub_mixes_[submix_index]
674 .layouts[layout_index]
675 .loudness,
676 mix_presentation_obu.GetMixPresentationId(), submix_index,
677 layout_index, loudness_matches_user_data,
678 std::move(layout_rendering_metadata.loudness_calculator),
679 mix_presentation_obu.sub_mixes_[submix_index]
680 .layouts[layout_index]
681 .loudness));
682 }
683 layout_index++;
684 }
685 submix_index++;
686 }
687 if (!loudness_matches_user_data) {
688 return absl::InvalidArgumentError("Loudness does not match user data.");
689 }
690 return absl::OkStatus();
691 }
692
693 // Renders all submixes, layouts, and audio elements for a temporal unit. It
694 // then optionally writes the rendered samples to a wav file and/or calculates
695 // the loudness of the rendered samples.
RenderWriteAndCalculateLoudnessForTemporalUnit(const IdLabeledFrameMap & id_to_labeled_frame,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,const std::list<ParameterBlockWithData> & parameter_blocks,std::vector<SubmixRenderingMetadata> & rendering_metadata)696 absl::Status RenderWriteAndCalculateLoudnessForTemporalUnit(
697 const IdLabeledFrameMap& id_to_labeled_frame,
698 InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
699 const std::list<ParameterBlockWithData>& parameter_blocks,
700 std::vector<SubmixRenderingMetadata>& rendering_metadata) {
701 for (auto& submix_rendering_metadata : rendering_metadata) {
702 for (auto& layout_rendering_metadata :
703 submix_rendering_metadata.layout_rendering_metadata) {
704 if (!layout_rendering_metadata.can_render) {
705 continue;
706 }
707 if (submix_rendering_metadata.mix_gain == nullptr) {
708 return absl::InvalidArgumentError("Submix mix gain is null");
709 }
710
711 RETURN_IF_NOT_OK(RenderAllFramesForLayout(
712 layout_rendering_metadata.num_channels,
713 submix_rendering_metadata.audio_elements_in_sub_mix,
714 *submix_rendering_metadata.mix_gain, id_to_labeled_frame,
715 layout_rendering_metadata.audio_element_rendering_metadata,
716 start_timestamp, end_timestamp, parameter_blocks,
717 submix_rendering_metadata.common_sample_rate,
718 layout_rendering_metadata.rendered_samples,
719 layout_rendering_metadata.valid_rendered_samples));
720
721 // Calculate loudness based on the original rendered samples; we do not
722 // know what post-processing the end user will have.
723 if (layout_rendering_metadata.loudness_calculator != nullptr) {
724 RETURN_IF_NOT_OK(
725 layout_rendering_metadata.loudness_calculator
726 ->AccumulateLoudnessForSamples(
727 layout_rendering_metadata.valid_rendered_samples));
728 }
729
730 // Perform any post-processing.
731 if (layout_rendering_metadata.sample_processor != nullptr) {
732 RETURN_IF_NOT_OK(layout_rendering_metadata.sample_processor->PushFrame(
733 layout_rendering_metadata.valid_rendered_samples));
734 }
735 }
736 }
737 return absl::OkStatus();
738 }
739
740 absl::StatusOr<const LayoutRenderingMetadata*>
GetRenderedSamplesAndPostProcessor(const absl::flat_hash_map<DecodedUleb128,std::vector<SubmixRenderingMetadata>> & mix_presentation_id_to_sub_mix_rendering_metadata,DecodedUleb128 mix_presentation_id,size_t sub_mix_index,size_t layout_index)741 GetRenderedSamplesAndPostProcessor(
742 const absl::flat_hash_map<DecodedUleb128,
743 std::vector<SubmixRenderingMetadata>>&
744 mix_presentation_id_to_sub_mix_rendering_metadata,
745 DecodedUleb128 mix_presentation_id, size_t sub_mix_index,
746 size_t layout_index) {
747 // Lookup the requested layout in the requested mix presentation.
748 const auto sub_mix_rendering_metadata_it =
749 mix_presentation_id_to_sub_mix_rendering_metadata.find(
750 mix_presentation_id);
751 const auto mix_presentation_id_error_message =
752 absl::StrCat(" Mix Presentation ID ", mix_presentation_id);
753 if (sub_mix_rendering_metadata_it ==
754 mix_presentation_id_to_sub_mix_rendering_metadata.end()) {
755 return absl::NotFoundError(
756 absl::StrCat(mix_presentation_id_error_message,
757 " not found in rendering metadata."));
758 }
759
760 // Validate the sub mix and layout are in bounds, then retrieve it.
761 const auto& [unused_mix_presentation_id, sub_mix_rendering_metadatas] =
762 *sub_mix_rendering_metadata_it;
763 RETURN_IF_NOT_OK(Validate(
764 sub_mix_index, std::less<size_t>(), sub_mix_rendering_metadatas.size(),
765 absl::StrCat(mix_presentation_id_error_message, " sub_mix_index <")));
766 RETURN_IF_NOT_OK(Validate(
767 layout_index, std::less<size_t>(),
768 sub_mix_rendering_metadatas[sub_mix_index]
769 .layout_rendering_metadata.size(),
770 absl::StrCat(mix_presentation_id_error_message, " layout_index <")));
771 return &sub_mix_rendering_metadatas[sub_mix_index]
772 .layout_rendering_metadata[layout_index];
773 }
774
775 } // namespace
776
777 absl::StatusOr<RenderingMixPresentationFinalizer>
Create(absl::Nullable<const RendererFactoryBase * > renderer_factory,absl::Nullable<const LoudnessCalculatorFactoryBase * > loudness_calculator_factory,const absl::flat_hash_map<uint32_t,AudioElementWithData> & audio_elements,const SampleProcessorFactory & sample_processor_factory,const std::list<MixPresentationObu> & mix_presentation_obus)778 RenderingMixPresentationFinalizer::Create(
779 absl::Nullable<const RendererFactoryBase*> renderer_factory,
780 absl::Nullable<const LoudnessCalculatorFactoryBase*>
781 loudness_calculator_factory,
782 const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
783 const SampleProcessorFactory& sample_processor_factory,
784 const std::list<MixPresentationObu>& mix_presentation_obus) {
785 const bool rendering_enabled = renderer_factory != nullptr;
786 if (!rendering_enabled) {
787 LOG(INFO) << "Rendering is safely disabled.";
788 }
789 if (loudness_calculator_factory == nullptr) {
790 LOG(INFO) << "Loudness calculator factory is null so loudness will not be "
791 "calculated.";
792 }
793 absl::flat_hash_map<DecodedUleb128, std::vector<SubmixRenderingMetadata>>
794 mix_presentation_id_to_rendering_metadata;
795 std::list<MixPresentationObu> mix_presentation_obus_to_render;
796 for (const auto& mix_presentation_obu : mix_presentation_obus) {
797 // Copy all mix presentation OBUs, so they can be echoed back, even when
798 // rendering is disabled.
799 mix_presentation_obus_to_render.emplace_back(mix_presentation_obu);
800
801 // Fill in rendering metadata if rendering is enabled, and at least one
802 // layout can be rendered.
803 if (rendering_enabled) {
804 std::vector<SubmixRenderingMetadata> temp_sub_mix_rendering_metadata;
805 RETURN_IF_NOT_OK(GenerateRenderingMetadataForSubmixes(
806 *renderer_factory, loudness_calculator_factory,
807 sample_processor_factory, audio_elements, mix_presentation_obu,
808 temp_sub_mix_rendering_metadata));
809 if (CanRenderAnyLayout(temp_sub_mix_rendering_metadata)) {
810 mix_presentation_id_to_rendering_metadata.emplace(
811 mix_presentation_obu.GetMixPresentationId(),
812 std::move(temp_sub_mix_rendering_metadata));
813 }
814 }
815 }
816
817 return RenderingMixPresentationFinalizer(
818 std::move(mix_presentation_id_to_rendering_metadata),
819 std::move(mix_presentation_obus_to_render));
820 }
821
PushTemporalUnit(const IdLabeledFrameMap & id_to_labeled_frame,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,const std::list<ParameterBlockWithData> & parameter_blocks)822 absl::Status RenderingMixPresentationFinalizer::PushTemporalUnit(
823 const IdLabeledFrameMap& id_to_labeled_frame,
824 InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
825 const std::list<ParameterBlockWithData>& parameter_blocks) {
826 switch (state_) {
827 case kAcceptingTemporalUnits:
828 // Ok to push.
829 break;
830 case kFinalizePushTemporalUnitCalled:
831 return absl::FailedPreconditionError(
832 "PushTemporalUnit() should not be called after "
833 "FinalizePushingTemporalUnits() has been called.");
834 case kFlushedFinalizedMixPresentationObus:
835 return absl::FailedPreconditionError(
836 "PushTemporalUnit() should not be called after "
837 "GetFinalizedMixPresentationOBUs() has been called.");
838 }
839 for (auto& [mix_presentation_ids, sub_mix_rendering_metadata] :
840 mix_presentation_id_to_sub_mix_rendering_metadata_) {
841 RETURN_IF_NOT_OK(RenderWriteAndCalculateLoudnessForTemporalUnit(
842 id_to_labeled_frame, start_timestamp, end_timestamp, parameter_blocks,
843 sub_mix_rendering_metadata));
844 }
845 return absl::OkStatus();
846 }
847
848 absl::StatusOr<absl::Span<const std::vector<int32_t>>>
GetPostProcessedSamplesAsSpan(DecodedUleb128 mix_presentation_id,size_t sub_mix_index,size_t layout_index) const849 RenderingMixPresentationFinalizer::GetPostProcessedSamplesAsSpan(
850 DecodedUleb128 mix_presentation_id, size_t sub_mix_index,
851 size_t layout_index) const {
852 const auto layout_rendering_metadata = GetRenderedSamplesAndPostProcessor(
853 mix_presentation_id_to_sub_mix_rendering_metadata_, mix_presentation_id,
854 sub_mix_index, layout_index);
855 if (!layout_rendering_metadata.ok()) {
856 return layout_rendering_metadata.status();
857 }
858 // `absl::StatusOr<const T*> cannot hold a nullptr.
859 CHECK_NE(*layout_rendering_metadata, nullptr);
860
861 // Prioritize returning the post-processed samples if a post-processor is
862 // available. Otherwise, return the rendered samples.
863 return (*layout_rendering_metadata)->sample_processor != nullptr
864 ? (*layout_rendering_metadata)
865 ->sample_processor->GetOutputSamplesAsSpan()
866 : (*layout_rendering_metadata)->valid_rendered_samples;
867 }
868
FinalizePushingTemporalUnits()869 absl::Status RenderingMixPresentationFinalizer::FinalizePushingTemporalUnits() {
870 switch (state_) {
871 case kAcceptingTemporalUnits:
872 state_ = kFinalizePushTemporalUnitCalled;
873 break;
874 case kFinalizePushTemporalUnitCalled:
875 case kFlushedFinalizedMixPresentationObus:
876 return absl::FailedPreconditionError(
877 "FinalizePushingTemporalUnits() should not be called twice.");
878 }
879
880 for (auto& [mix_presentation_id, sub_mix_rendering_metadata] :
881 mix_presentation_id_to_sub_mix_rendering_metadata_) {
882 RETURN_IF_NOT_OK(FlushPostProcessors(sub_mix_rendering_metadata));
883 }
884 return absl::OkStatus();
885 }
886
887 absl::StatusOr<std::list<MixPresentationObu>>
GetFinalizedMixPresentationObus(bool validate_loudness)888 RenderingMixPresentationFinalizer::GetFinalizedMixPresentationObus(
889 bool validate_loudness) {
890 switch (state_) {
891 case kAcceptingTemporalUnits:
892 return absl::FailedPreconditionError(
893 "FinalizePushingTemporalUnits() should be called before "
894 "GetFinalizedMixPresentationOBUs().");
895 case kFinalizePushTemporalUnitCalled:
896 // Ok to finalize.
897 break;
898 case kFlushedFinalizedMixPresentationObus:
899 return absl::FailedPreconditionError(
900 "GetFinalizedMixPresentationOBUs() should not be called twice.");
901 }
902
903 // Finalize the OBUs in place.
904 for (auto& mix_presentation_obu : mix_presentation_obus_) {
905 const auto sub_mix_rendering_metadata_it =
906 mix_presentation_id_to_sub_mix_rendering_metadata_.find(
907 mix_presentation_obu.GetMixPresentationId());
908 if (sub_mix_rendering_metadata_it ==
909 mix_presentation_id_to_sub_mix_rendering_metadata_.end()) {
910 LOG(INFO) << "Rendering was disabled for Mix Presentation ID= "
911 << mix_presentation_obu.GetMixPresentationId()
912 << " echoing the input OBU.";
913 continue;
914 }
915
916 RETURN_IF_NOT_OK(FillLoudnessForMixPresentation(
917 validate_loudness, sub_mix_rendering_metadata_it->second,
918 mix_presentation_obu));
919 mix_presentation_obu.PrintObu();
920 }
921
922 // Flush the finalized OBUs and mark that this class should not use them
923 // again.
924 state_ = kFlushedFinalizedMixPresentationObus;
925 return std::move(mix_presentation_obus_);
926 }
927
928 } // namespace iamf_tools
929