• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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