• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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       [&param_data]() {
49         return static_cast<int16_t>(param_data.step().start_point_value());
50       },
51       [&param_data]() {
52         return static_cast<int16_t>(param_data.linear().start_point_value());
53       },
54       [&param_data]() {
55         return static_cast<int16_t>(param_data.linear().end_point_value());
56       },
57       [&param_data]() {
58         return static_cast<int16_t>(param_data.bezier().start_point_value());
59       },
60       [&param_data]() {
61         return static_cast<int16_t>(param_data.bezier().end_point_value());
62       },
63       [&param_data]() {
64         return static_cast<int16_t>(param_data.bezier().control_point_value());
65       },
66       [&param_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