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/parameter_block_partitioner.h"
13
14 #include <algorithm>
15 #include <cstdint>
16 #include <list>
17 #include <vector>
18
19 #include "absl/log/log.h"
20 #include "absl/status/status.h"
21 #include "absl/strings/str_cat.h"
22 #include "iamf/cli/cli_util.h"
23 #include "iamf/cli/proto/ia_sequence_header.pb.h"
24 #include "iamf/cli/proto/obu_header.pb.h"
25 #include "iamf/cli/proto/parameter_block.pb.h"
26 #include "iamf/cli/proto/parameter_data.pb.h"
27 #include "iamf/common/utils/macros.h"
28 #include "iamf/common/utils/numeric_utils.h"
29 #include "iamf/common/utils/obu_util.h"
30 #include "iamf/obu/types.h"
31
32 namespace iamf_tools {
33
34 using iamf_tools_cli_proto::ParameterBlockObuMetadata;
35
36 namespace {
37
InterpolateMixGainParameterData(const iamf_tools_cli_proto::MixGainParameterData & mix_gain_parameter_data,InternalTimestamp start_time,InternalTimestamp end_time,InternalTimestamp target_time,int16_t & target_mix_gain)38 absl::Status InterpolateMixGainParameterData(
39 const iamf_tools_cli_proto::MixGainParameterData& mix_gain_parameter_data,
40 InternalTimestamp start_time, InternalTimestamp end_time,
41 InternalTimestamp target_time, int16_t& target_mix_gain) {
42 const auto& param_data = mix_gain_parameter_data.param_data();
43 float target_mix_gain_db = 0;
44 RETURN_IF_NOT_OK(InterpolateMixGainValue(
45 mix_gain_parameter_data.animation_type(),
46 iamf_tools_cli_proto::ANIMATE_STEP, iamf_tools_cli_proto::ANIMATE_LINEAR,
47 iamf_tools_cli_proto::ANIMATE_BEZIER,
48 [¶m_data]() {
49 return static_cast<int16_t>(param_data.step().start_point_value());
50 },
51 [¶m_data]() {
52 return static_cast<int16_t>(param_data.linear().start_point_value());
53 },
54 [¶m_data]() {
55 return static_cast<int16_t>(param_data.linear().end_point_value());
56 },
57 [¶m_data]() {
58 return static_cast<int16_t>(param_data.bezier().start_point_value());
59 },
60 [¶m_data]() {
61 return static_cast<int16_t>(param_data.bezier().end_point_value());
62 },
63 [¶m_data]() {
64 return static_cast<int16_t>(param_data.bezier().control_point_value());
65 },
66 [¶m_data]() {
67 return static_cast<int16_t>(
68 param_data.bezier().control_point_relative_time());
69 },
70 start_time, end_time, target_time, target_mix_gain_db));
71
72 // Interpolated values are in dB in float; convert back to Q7.8 to store in
73 // the partitioned parameter block.
74 return FloatToQ7_8(target_mix_gain_db, target_mix_gain);
75 }
76
77 /*!\brief Partitions a `MixGainParameterData`
78 *
79 * Partitions a `MixGainParameterData` including the nested fields that describe
80 * animation.
81 *
82 * \param subblock_mix_gain Input mix gain to partition.
83 * \param subblock_start_time Start time of the subblock being partitioned.
84 * \param subblock_end_time End time of the subblock being partitioned.
85 * \param partition_start_time Start time of the output partitioned parameter
86 * block.
87 * \param partitioned_end_time End time of the output partitioned parameter
88 * block.
89 * \param partitioned_subblock Output argument with `MixGainParameterData`
90 * filled in.
91 * \return `absl::OkStatus()` on success. A specific status on failure.
92 */
PartitionMixGain(const iamf_tools_cli_proto::MixGainParameterData & subblock_mix_gain,InternalTimestamp subblock_start_time,InternalTimestamp subblock_end_time,InternalTimestamp partitioned_start_time,InternalTimestamp partitioned_end_time,iamf_tools_cli_proto::ParameterSubblock & partitioned_subblock)93 absl::Status PartitionMixGain(
94 const iamf_tools_cli_proto::MixGainParameterData& subblock_mix_gain,
95 InternalTimestamp subblock_start_time, InternalTimestamp subblock_end_time,
96 InternalTimestamp partitioned_start_time,
97 InternalTimestamp partitioned_end_time,
98 iamf_tools_cli_proto::ParameterSubblock& partitioned_subblock) {
99 // Copy over the animation type.
100 auto* mix_gain_param_data =
101 partitioned_subblock.mutable_mix_gain_parameter_data();
102 mix_gain_param_data->set_animation_type(subblock_mix_gain.animation_type());
103
104 // Partition the animated parameter.
105 switch (subblock_mix_gain.animation_type()) {
106 using enum iamf_tools_cli_proto::AnimationType;
107 case ANIMATE_STEP: {
108 int16_t start_point_value;
109 RETURN_IF_NOT_OK(InterpolateMixGainParameterData(
110 subblock_mix_gain, subblock_start_time, subblock_end_time,
111 partitioned_start_time, start_point_value));
112 mix_gain_param_data->mutable_param_data()
113 ->mutable_step()
114 ->set_start_point_value(static_cast<int32_t>(start_point_value));
115 return absl::OkStatus();
116 }
117 case ANIMATE_LINEAR: {
118 // Set partitioned start time to the value of the parameter at that time.
119 LOG_FIRST_N(INFO, 3) << subblock_start_time << " " << subblock_end_time
120 << " " << partitioned_start_time << " "
121 << partitioned_end_time;
122 // Calculate the subblock's parameter value at the start of the partition.
123 int16_t start_point_value;
124 int16_t end_point_value;
125 auto status = InterpolateMixGainParameterData(
126 subblock_mix_gain, subblock_start_time, subblock_end_time,
127 partitioned_start_time, start_point_value);
128
129 // Calculate the subblock's parameter value at the end of the partition.
130 status.Update(InterpolateMixGainParameterData(
131 subblock_mix_gain, subblock_start_time, subblock_end_time,
132 partitioned_end_time, end_point_value));
133
134 if (!status.ok()) {
135 return absl::Status(
136 status.code(),
137 absl::StrCat("Failed to interpolate mix gain values. "
138 "`InterpolateMixGainParameterData` returned: ",
139 status.message()));
140 }
141 auto* linear =
142 mix_gain_param_data->mutable_param_data()->mutable_linear();
143 linear->set_start_point_value(static_cast<int32_t>(start_point_value));
144 linear->set_end_point_value(static_cast<int32_t>(end_point_value));
145 return absl::OkStatus();
146 }
147 case ANIMATE_BEZIER: {
148 if (subblock_start_time == partitioned_start_time &&
149 subblock_end_time == partitioned_end_time) {
150 // Handle the simplest case where the subblock is aligned and does not
151 // need partitioning.
152 *mix_gain_param_data->mutable_param_data() =
153 subblock_mix_gain.param_data();
154 return absl::OkStatus();
155 }
156 // TODO(b/279581032): Carefully split the bezier curve. Be careful with
157 // Q7.8 format.
158 return absl::InvalidArgumentError(absl::StrCat(
159 "The encoder does not fully support partitioning bezier ",
160 "parameters yet."));
161 }
162 default:
163 return absl::InvalidArgumentError(
164 absl::StrCat("Unrecognized animation type = ",
165 subblock_mix_gain.animation_type()));
166 }
167 }
168
169 /*!\brief Gets the subblocks that overlap with the input times.
170 *
171 * Finds all subblocks in the input Parameter Block that overlap with
172 * `partitioned_start_time` and `partitioned_end_time.
173 *
174 * \param full_parameter_block Input full parameter block.
175 * \param partitioned_start_time Start time of the output partitioned parameter
176 * block.
177 * \param partitioned_end_time End time of the output partitioned parameter
178 * block.
179 * \param partitioned_subblocks Output partitioned subblocks.
180 * \param constant_subblock_duration Output argument which corresponds to the
181 * value of `constant_subblock_duration` in the OBU or metadata.
182 * \return `absl::OkStatus()` on success. A specific status on failure.
183 */
GetPartitionedSubblocks(const ParameterBlockObuMetadata & full_parameter_block,InternalTimestamp partitioned_start_time,InternalTimestamp partitioned_end_time,std::list<iamf_tools_cli_proto::ParameterSubblock> & partitioned_subblocks,uint32_t & constant_subblock_duration)184 absl::Status GetPartitionedSubblocks(
185 const ParameterBlockObuMetadata& full_parameter_block,
186 InternalTimestamp partitioned_start_time,
187 InternalTimestamp partitioned_end_time,
188 std::list<iamf_tools_cli_proto::ParameterSubblock>& partitioned_subblocks,
189 uint32_t& constant_subblock_duration) {
190 LOG_FIRST_N(INFO, 1) << " full_parameter_block=\n" << full_parameter_block;
191
192 InternalTimestamp current_time = full_parameter_block.start_timestamp();
193
194 // Track that the split subblocks cover the whole partition.
195 int32_t total_covered_duration = 0;
196
197 // Loop through all subblocks in the original Parameter Block.
198 const auto num_subblocks = full_parameter_block.num_subblocks();
199 for (int i = 0; i < num_subblocks; ++i) {
200 // Get the start and end time of this subblock.
201 const int32_t subblock_start_time = current_time;
202
203 // The partitioner works directly on the parameter block OBU metadata and
204 // assumes all needed information (e.g. subblock duration) is in the
205 // metadata themselves and does not support getting the information from
206 // parameter definitions (i.e. parameter definition mode == 0).
207 constexpr uint8_t kParamDefinitionModeOne = 1;
208 const auto subblock_duration = GetParameterSubblockDuration<uint32_t>(
209 i, full_parameter_block.num_subblocks(),
210 full_parameter_block.constant_subblock_duration(),
211 full_parameter_block.duration(), kParamDefinitionModeOne,
212 [&full_parameter_block](int i) {
213 return full_parameter_block.subblocks(i).subblock_duration();
214 },
215 [](int i) {
216 return absl::InvalidArgumentError(
217 "Parameter Block Partitioner does not support the case where "
218 "`param_definition_mode == 0");
219 });
220 if (!subblock_duration.ok()) {
221 return subblock_duration.status();
222 }
223 const int32_t subblock_end_time =
224 subblock_start_time + static_cast<int32_t>(*subblock_duration);
225 current_time = subblock_end_time;
226
227 if (subblock_end_time <= partitioned_start_time ||
228 partitioned_end_time <= subblock_start_time) {
229 // The subblock ends before the partition or starts after. It can't
230 // overlap.
231 continue;
232 }
233
234 // Found an overlapping subblock. Create a new one for the partition that
235 // represents the overlapped time.
236 iamf_tools_cli_proto::ParameterSubblock partitioned_subblock;
237 const int partitioned_subblock_start =
238 std::max(partitioned_start_time, subblock_start_time);
239 const int partitioned_subblock_end =
240 std::min(partitioned_end_time, subblock_end_time);
241 uint32_t partitioned_subblock_duration =
242 partitioned_subblock_end - partitioned_subblock_start;
243 total_covered_duration += partitioned_subblock_duration;
244 partitioned_subblock.set_subblock_duration(partitioned_subblock_duration);
245
246 const auto& subblock_i = full_parameter_block.subblocks(i);
247 if (subblock_i.has_mix_gain_parameter_data()) {
248 // Mix Gain animated parameters need to be partitioned.
249 RETURN_IF_NOT_OK(PartitionMixGain(
250 subblock_i.mix_gain_parameter_data(), subblock_start_time,
251 subblock_end_time, partitioned_subblock_start,
252 partitioned_subblock_end, partitioned_subblock));
253 } else if (subblock_i.has_demixing_info_parameter_data()) {
254 if (!partitioned_subblocks.empty()) {
255 return absl::InvalidArgumentError(
256 "There should only be one subblock for demixing info.");
257 }
258 *partitioned_subblock.mutable_demixing_info_parameter_data() =
259 subblock_i.demixing_info_parameter_data();
260 } else if (subblock_i.has_recon_gain_info_parameter_data()) {
261 if (!partitioned_subblocks.empty()) {
262 return absl::InvalidArgumentError(
263 "There should only be one subblock for recon gain info.");
264 }
265 *partitioned_subblock.mutable_recon_gain_info_parameter_data() =
266 subblock_i.recon_gain_info_parameter_data();
267 } else {
268 return absl::InvalidArgumentError("Unknown subblock type.");
269 }
270 partitioned_subblocks.push_back(partitioned_subblock);
271
272 if (subblock_end_time >= partitioned_end_time) {
273 // Subblock overlap is over. No more to find for this partition.
274 break;
275 }
276 }
277
278 const auto status = CompareTimestamps(
279 partitioned_end_time - partitioned_start_time, total_covered_duration);
280 if (!status.ok()) {
281 return absl::Status(
282 status.code(),
283 absl::StrCat(
284 "Unable to find enough subblocks to totally cover the duration "
285 "of the partitioned Parameter Block OBU. Possible gap in the "
286 "sequence. `CompareTimestamps` returned: ",
287 status.message()));
288 }
289
290 // Get the value of `constant_subblock_duration`.
291 std::vector<uint32_t> subblock_durations;
292 subblock_durations.reserve(partitioned_subblocks.size());
293 for (const auto& subblock : partitioned_subblocks) {
294 subblock_durations.push_back(subblock.subblock_duration());
295 }
296
297 constant_subblock_duration =
298 ParameterBlockPartitioner::FindConstantSubblockDuration(
299 subblock_durations);
300
301 return absl::OkStatus();
302 }
303
304 } // namespace
305
FindConstantSubblockDuration(const std::vector<uint32_t> & subblock_durations)306 uint32_t ParameterBlockPartitioner::FindConstantSubblockDuration(
307 const std::vector<uint32_t>& subblock_durations) {
308 if (subblock_durations.empty()) {
309 return 0;
310 }
311 const auto constant_subblock_duration = subblock_durations[0];
312
313 for (int i = 0; i < subblock_durations.size(); ++i) {
314 if (i == subblock_durations.size() - 1 &&
315 subblock_durations[i] <= constant_subblock_duration) {
316 // The duration of the final subblock can be implicitly calculated. As
317 // long as it has equal or smaller duration than
318 // `constant_subblock_duration`.
319 continue;
320 }
321
322 // Other than the special case all subblocks must have the same duration.
323 if (subblock_durations[i] != constant_subblock_duration) {
324 return 0;
325 }
326 }
327
328 return constant_subblock_duration;
329 }
330
FindPartitionDuration(const iamf_tools_cli_proto::ProfileVersion primary_profile,const iamf_tools_cli_proto::CodecConfigObuMetadata & codec_config_obu_metadata,uint32_t & partition_duration)331 absl::Status ParameterBlockPartitioner::FindPartitionDuration(
332 const iamf_tools_cli_proto::ProfileVersion primary_profile,
333 const iamf_tools_cli_proto::CodecConfigObuMetadata&
334 codec_config_obu_metadata,
335 uint32_t& partition_duration) {
336 using enum iamf_tools_cli_proto::ProfileVersion;
337 if (primary_profile != PROFILE_VERSION_SIMPLE &&
338 primary_profile != PROFILE_VERSION_BASE &&
339 primary_profile != PROFILE_VERSION_BASE_ENHANCED) {
340 // This function only implements limitations described in IAMF v1.1.0 for
341 // simple, base, base-enhanced profile.
342 return absl::InvalidArgumentError(
343 absl::StrCat("FindPartitionDuration() only works with Simple, Base, or "
344 "Base-Enhanced profile"));
345 }
346
347 // TODO(b/283281856): Set the duration to a different value when
348 // `parameter_rate != sample rate`.
349 partition_duration =
350 codec_config_obu_metadata.codec_config().num_samples_per_frame();
351 return absl::OkStatus();
352 }
353
PartitionParameterBlock(const ParameterBlockObuMetadata & full_parameter_block,int32_t partitioned_start_time,int32_t partitioned_end_time,ParameterBlockObuMetadata & partitioned)354 absl::Status ParameterBlockPartitioner::PartitionParameterBlock(
355 const ParameterBlockObuMetadata& full_parameter_block,
356 int32_t partitioned_start_time, int32_t partitioned_end_time,
357 ParameterBlockObuMetadata& partitioned) {
358 if (partitioned_start_time >= partitioned_end_time) {
359 return absl::InvalidArgumentError(
360 absl::StrCat("Cannot partition a parameter block with < 1 duration. "
361 "(partitioned_start_time= ",
362 partitioned_start_time,
363 " partitioned_end_time=", partitioned_end_time));
364 }
365
366 // Find the subblocks that overlap this partition.
367 std::list<iamf_tools_cli_proto::ParameterSubblock> partitioned_subblocks;
368
369 uint32_t constant_subblock_duration;
370 RETURN_IF_NOT_OK(GetPartitionedSubblocks(
371 full_parameter_block, partitioned_start_time, partitioned_end_time,
372 partitioned_subblocks, constant_subblock_duration));
373
374 // Create the partitioned parameter block OBU metadata. The first few fields
375 // are always the same as the full metadata.
376 partitioned.set_parameter_id(full_parameter_block.parameter_id());
377 partitioned.set_duration(
378 static_cast<uint32_t>(partitioned_end_time - partitioned_start_time));
379 partitioned.set_num_subblocks(partitioned_subblocks.size());
380 partitioned.set_constant_subblock_duration(constant_subblock_duration);
381 *partitioned.mutable_obu_header() = full_parameter_block.obu_header();
382 for (const auto& subblock : partitioned_subblocks) {
383 *partitioned.add_subblocks() = subblock;
384 }
385 partitioned.set_start_timestamp(partitioned_start_time);
386
387 return absl::OkStatus();
388 }
389
PartitionFrameAligned(uint32_t partition_duration,const ParameterBlockObuMetadata & full_parameter_block,std::list<ParameterBlockObuMetadata> & partitioned_parameter_blocks)390 absl::Status ParameterBlockPartitioner::PartitionFrameAligned(
391 uint32_t partition_duration,
392 const ParameterBlockObuMetadata& full_parameter_block,
393 std::list<ParameterBlockObuMetadata>& partitioned_parameter_blocks) {
394 // Partition this parameter block into several blocks with the same duration.
395 const int32_t end_timestamp =
396 full_parameter_block.start_timestamp() + full_parameter_block.duration();
397 for (int32_t t = full_parameter_block.start_timestamp(); t < end_timestamp;
398 t += partition_duration) {
399 LOG(INFO) << "Partitioning parameter blocks at timestamp= " << t;
400 ParameterBlockObuMetadata partitioned_parameter_block;
401 RETURN_IF_NOT_OK(PartitionParameterBlock(full_parameter_block, t,
402 t + partition_duration,
403 partitioned_parameter_block));
404 partitioned_parameter_blocks.push_back(partitioned_parameter_block);
405 }
406
407 return absl::OkStatus();
408 }
409
410 } // namespace iamf_tools
411