1 /*
2 * Copyright (c) 2025, Alliance for Open Media. All rights reserved
3 *
4 * This source code is subject to the terms of the BSD 3-Clause Clear License
5 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6 * License was not distributed with this source code in the LICENSE file, you
7 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8 * Alliance for Open Media Patent License 1.0 was not distributed with this
9 * source code in the PATENTS file, you can obtain it at
10 * www.aomedia.org/license/patent.
11 */
12
13 #include "iamf/cli/temporal_unit_view.h"
14
15 #include <cstdint>
16 #include <functional>
17 #include <optional>
18 #include <utility>
19 #include <vector>
20
21 #include "absl/algorithm/container.h"
22 #include "absl/container/flat_hash_set.h"
23 #include "absl/log/check.h"
24 #include "absl/log/log.h"
25 #include "absl/status/status.h"
26 #include "absl/status/statusor.h"
27 #include "absl/strings/string_view.h"
28 #include "absl/types/span.h"
29 #include "iamf/cli/audio_frame_with_data.h"
30 #include "iamf/cli/parameter_block_with_data.h"
31 #include "iamf/common/utils/macros.h"
32 #include "iamf/common/utils/validation_utils.h"
33 #include "iamf/obu/arbitrary_obu.h"
34 #include "iamf/obu/types.h"
35
36 namespace iamf_tools {
37
38 namespace {
39
40 // Common statistics about a temporal unit.
41 struct TemporalUnitStatistics {
42 uint32_t num_samples_per_frame;
43 uint32_t num_samples_to_trim_at_end;
44 uint32_t num_samples_to_trim_at_start;
45 uint32_t num_untrimmed_samples;
46 InternalTimestamp start_timestamp;
47 InternalTimestamp end_timestamp;
48 };
49
ComputeTemporalUnitStatistics(const AudioFrameWithData * first_audio_frame)50 absl::StatusOr<TemporalUnitStatistics> ComputeTemporalUnitStatistics(
51 const AudioFrameWithData* first_audio_frame) {
52 RETURN_IF_NOT_OK(ValidateNotNull(first_audio_frame, "`audio_frames`"));
53 RETURN_IF_NOT_OK(ValidateNotNull(first_audio_frame->audio_element_with_data,
54 "`audio_frame.audio_element_with_data`"));
55 RETURN_IF_NOT_OK(
56 ValidateNotNull(first_audio_frame->audio_element_with_data->codec_config,
57 "`audio_frame.audio_element_with_data.codec_config`"));
58 TemporalUnitStatistics statistics{
59 .num_samples_per_frame = first_audio_frame->audio_element_with_data
60 ->codec_config->GetNumSamplesPerFrame(),
61 .num_samples_to_trim_at_end =
62 first_audio_frame->obu.header_.num_samples_to_trim_at_end,
63 .num_samples_to_trim_at_start =
64 first_audio_frame->obu.header_.num_samples_to_trim_at_start,
65 .start_timestamp = first_audio_frame->start_timestamp,
66 .end_timestamp = first_audio_frame->end_timestamp,
67 };
68
69 // Check the trim in the first frame is plausible. I.e. there are not at least
70 // 0 samples. This also prevents underflow when subtracting later.
71 const uint32_t cumulative_trim = statistics.num_samples_to_trim_at_start +
72 statistics.num_samples_to_trim_at_end;
73 RETURN_IF_NOT_OK(Validate(cumulative_trim, std::less_equal<uint32_t>(),
74 statistics.num_samples_per_frame,
75 "cumulative trim is <= `num_samples_per_frame`"));
76 statistics.num_untrimmed_samples =
77 statistics.num_samples_per_frame - cumulative_trim;
78
79 return statistics;
80 }
81
ValidateAllParameterBlocksMatchStatistics(absl::Span<const ParameterBlockWithData * const> parameter_blocks,const TemporalUnitStatistics & statistics)82 absl::Status ValidateAllParameterBlocksMatchStatistics(
83 absl::Span<const ParameterBlockWithData* const> parameter_blocks,
84 const TemporalUnitStatistics& statistics) {
85 absl::flat_hash_set<uint32_t> seen_parameter_ids;
86 for (const auto* parameter_block : parameter_blocks) {
87 RETURN_IF_NOT_OK(ValidateNotNull(parameter_block, "`parameter_block`"));
88 const auto& [unused_iter, inserted] =
89 seen_parameter_ids.insert(parameter_block->obu->parameter_id_);
90 if (!inserted) {
91 return absl::InvalidArgumentError(
92 "A temporal unit must not have multiple parameter blocks with the "
93 "same parameter ID.");
94 }
95
96 RETURN_IF_NOT_OK(ValidateEqual(
97 parameter_block->start_timestamp, statistics.start_timestamp,
98 "`start_timestamp` must be the same for all parameter blocks"));
99 RETURN_IF_NOT_OK(ValidateEqual(
100 parameter_block->end_timestamp, statistics.end_timestamp,
101 "`end_timestamp` must be the same for all parameter blocks"));
102 }
103 return absl::OkStatus();
104 }
105
ValidateAllAudioFramesMatchStatistics(absl::Span<const AudioFrameWithData * const> audio_frames,const TemporalUnitStatistics & statistics)106 absl::Status ValidateAllAudioFramesMatchStatistics(
107 absl::Span<const AudioFrameWithData* const> audio_frames,
108 const TemporalUnitStatistics& statistics) {
109 absl::flat_hash_set<uint32_t> seen_substream_ids;
110 for (const auto* audio_frame : audio_frames) {
111 RETURN_IF_NOT_OK(ValidateNotNull(audio_frame, "`audio_frame`"));
112 const auto& [unused_iter, inserted] =
113 seen_substream_ids.insert(audio_frame->obu.GetSubstreamId());
114 if (!inserted) {
115 return absl::InvalidArgumentError(
116 "A temporal unit must not have multiple audio with the same "
117 "substream ID.");
118 }
119
120 RETURN_IF_NOT_OK(ValidateNotNull(audio_frame->audio_element_with_data,
121 "`audio_frame.audio_element_with_data`"));
122 RETURN_IF_NOT_OK(
123 ValidateNotNull(audio_frame->audio_element_with_data->codec_config,
124 "`audio_frame.audio_element_with_data.codec_config`"));
125 RETURN_IF_NOT_OK(ValidateEqual(
126 audio_frame->obu.header_.num_samples_to_trim_at_end,
127 statistics.num_samples_to_trim_at_end,
128 "`num_samples_to_trim_at_end` must be the same for all audio frames"));
129 RETURN_IF_NOT_OK(
130 ValidateEqual(audio_frame->obu.header_.num_samples_to_trim_at_start,
131 statistics.num_samples_to_trim_at_start,
132 "`num_samples_to_trim_at_start` must be the same for all "
133 "audio frames"));
134 RETURN_IF_NOT_OK(ValidateEqual(
135 audio_frame->start_timestamp, statistics.start_timestamp,
136 "`start_timestamp` must be the same for all audio frames"));
137 RETURN_IF_NOT_OK(
138 ValidateEqual(audio_frame->end_timestamp, statistics.end_timestamp,
139 "`end_timestamp` must be the same for all audio frames"));
140 }
141 return absl::OkStatus();
142 }
143
ValidateAllArbitraryObusMatchStatistics(absl::Span<const ArbitraryObu * const> arbitrary_obus,const TemporalUnitStatistics & statistics)144 absl::Status ValidateAllArbitraryObusMatchStatistics(
145 absl::Span<const ArbitraryObu* const> arbitrary_obus,
146 const TemporalUnitStatistics& statistics) {
147 for (const auto* arbitrary_obu : arbitrary_obus) {
148 RETURN_IF_NOT_OK(ValidateNotNull(arbitrary_obu, "`arbitrary_obu`"));
149 RETURN_IF_NOT_OK(ValidateEqual(
150 *arbitrary_obu->insertion_tick_,
151 static_cast<int64_t>(statistics.start_timestamp),
152 "`insertion_tick` must be the same for all arbitrary OBUs"));
153 }
154 return absl::OkStatus();
155 }
156
CompareParameterId(const ParameterBlockWithData * a,const ParameterBlockWithData * b)157 bool CompareParameterId(const ParameterBlockWithData* a,
158 const ParameterBlockWithData* b) {
159 // These were sanitized elsewhere in the class.
160 CHECK_NE(a, nullptr);
161 CHECK_NE(b, nullptr);
162 return a->obu->parameter_id_ < b->obu->parameter_id_;
163 }
164
CompareAudioElementIdAudioSubstreamId(const AudioFrameWithData * a,const AudioFrameWithData * b)165 bool CompareAudioElementIdAudioSubstreamId(const AudioFrameWithData* a,
166 const AudioFrameWithData* b) {
167 // These were sanitized elsewhere in the class.
168 CHECK_NE(a, nullptr);
169 CHECK_NE(a->audio_element_with_data, nullptr);
170 CHECK_NE(b, nullptr);
171 CHECK_NE(b->audio_element_with_data, nullptr);
172 CHECK_NE(b, nullptr);
173 if (a->audio_element_with_data->obu.GetAudioElementId() !=
174 b->audio_element_with_data->obu.GetAudioElementId()) {
175 return a->audio_element_with_data->obu.GetAudioElementId() <
176 b->audio_element_with_data->obu.GetAudioElementId();
177 }
178 return a->obu.GetSubstreamId() < b->obu.GetSubstreamId();
179 }
180
181 } // namespace
182
CreateFromPointers(absl::Span<const ParameterBlockWithData * const> parameter_blocks,absl::Span<const AudioFrameWithData * const> audio_frames,absl::Span<const ArbitraryObu * const> arbitrary_obus)183 absl::StatusOr<TemporalUnitView> TemporalUnitView::CreateFromPointers(
184 absl::Span<const ParameterBlockWithData* const> parameter_blocks,
185 absl::Span<const AudioFrameWithData* const> audio_frames,
186 absl::Span<const ArbitraryObu* const> arbitrary_obus) {
187 if (audio_frames.empty()) {
188 // Exit early even when `IGNORE_ERRORS_USE_ONLY_FOR_IAMF_TEST_SUITE` is set.
189 return absl::InvalidArgumentError(
190 "Every temporal unit must have an audio frame.");
191 }
192
193 // Infer some statistics based on the first audio frame
194 const auto statistics = ComputeTemporalUnitStatistics(*audio_frames.begin());
195 if (!statistics.ok()) {
196 return statistics.status();
197 }
198
199 // Check that all OBUs agree with the statistics. All frames must have the
200 // same trimming information and timestamps as of IAMF v1.1.0.
201 RETURN_IF_NOT_OK(
202 ValidateAllAudioFramesMatchStatistics(audio_frames, *statistics));
203 RETURN_IF_NOT_OK(
204 ValidateAllParameterBlocksMatchStatistics(parameter_blocks, *statistics));
205 RETURN_IF_NOT_OK(
206 ValidateAllArbitraryObusMatchStatistics(arbitrary_obus, *statistics));
207
208 // Sort the OBUS into a canonical order.
209 // TODO(b/332956880): Support a custom ordering of parameter blocks and
210 // substreams.
211 std::vector<const ParameterBlockWithData*> sorted_parameter_blocks(
212 parameter_blocks.begin(), parameter_blocks.end());
213 std::vector<const AudioFrameWithData*> sorted_audio_frames(
214 audio_frames.begin(), audio_frames.end());
215 std::vector<const ArbitraryObu*> copied_arbitrary_obus(arbitrary_obus.begin(),
216 arbitrary_obus.end());
217 absl::c_sort(sorted_parameter_blocks, CompareParameterId);
218 absl::c_sort(sorted_audio_frames, CompareAudioElementIdAudioSubstreamId);
219 return TemporalUnitView(
220 std::move(sorted_parameter_blocks), std::move(sorted_audio_frames),
221 std::move(copied_arbitrary_obus), statistics->start_timestamp,
222 statistics->end_timestamp, statistics->num_samples_to_trim_at_start,
223 statistics->num_untrimmed_samples);
224 }
225
TemporalUnitView(std::vector<const ParameterBlockWithData * > && parameter_blocks,std::vector<const AudioFrameWithData * > && audio_frames,std::vector<const ArbitraryObu * > && arbitrary_obus,InternalTimestamp start_timestamp,InternalTimestamp end_timestamp,uint32_t num_samples_to_trim_at_start,uint32_t num_untrimmed_samples)226 TemporalUnitView::TemporalUnitView(
227 std::vector<const ParameterBlockWithData*>&& parameter_blocks,
228 std::vector<const AudioFrameWithData*>&& audio_frames,
229 std::vector<const ArbitraryObu*>&& arbitrary_obus,
230 InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
231 uint32_t num_samples_to_trim_at_start, uint32_t num_untrimmed_samples)
232 : parameter_blocks_(std::move(parameter_blocks)),
233 audio_frames_(std::move(audio_frames)),
234 arbitrary_obus_(std::move(arbitrary_obus)),
235 start_timestamp_(start_timestamp),
236 end_timestamp_(end_timestamp),
237 num_samples_to_trim_at_start_(num_samples_to_trim_at_start),
238 num_untrimmed_samples_(num_untrimmed_samples) {}
239
240 } // namespace iamf_tools
241