blob: 0aac41e921eed402107a9ba8c959fd7a0174350c [file] [log] [blame]
/*
* Copyright (c) 2023, Alliance for Open Media. All rights reserved
*
* This source code is subject to the terms of the BSD 3-Clause Clear License
* and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
* License was not distributed with this source code in the LICENSE file, you
* can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
* Alliance for Open Media Patent License 1.0 was not distributed with this
* source code in the PATENTS file, you can obtain it at
* www.aomedia.org/license/patent.
*/
#include "iamf/obu/parameter_block.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <variant>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/obu_util.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/mix_gain_parameter_data.h"
#include "iamf/obu/obu_base.h"
#include "iamf/obu/obu_header.h"
#include "iamf/obu/param_definition_variant.h"
#include "iamf/obu/param_definitions.h"
#include "iamf/obu/parameter_data.h"
#include "iamf/obu/types.h"
namespace iamf_tools {
absl::Status ParameterSubblock::ReadAndValidate(
const ParamDefinition& param_definition, ReadBitBuffer& rb) {
if (subblock_duration.has_value()) {
RETURN_IF_NOT_OK(rb.ReadULeb128(*subblock_duration));
}
param_data = param_definition.CreateParameterData();
RETURN_IF_NOT_OK(param_data->ReadAndValidate(rb));
return absl::OkStatus();
}
absl::Status ParameterSubblock::Write(WriteBitBuffer& wb) const {
if (subblock_duration.has_value()) {
RETURN_IF_NOT_OK(wb.WriteUleb128(*subblock_duration));
}
// Write the specific parameter data depending on the specific type.
RETURN_IF_NOT_OK(param_data->Write(wb));
return absl::OkStatus();
}
void ParameterSubblock::Print() const {
if (subblock_duration.has_value()) {
LOG(INFO) << " subblock_duration= " << *subblock_duration;
}
param_data->Print();
}
absl::StatusOr<std::unique_ptr<ParameterBlockObu>>
ParameterBlockObu::CreateFromBuffer(
const ObuHeader& header, int64_t payload_size,
const absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant>&
param_definition_variants,
ReadBitBuffer& rb) {
DecodedUleb128 parameter_id;
int8_t encoded_uleb128_size = 0;
RETURN_IF_NOT_OK(rb.ReadULeb128(parameter_id, encoded_uleb128_size));
if (payload_size < encoded_uleb128_size) {
return absl::InvalidArgumentError(absl::StrCat(
"Read beyond the end of the OBU for parameter_id=", parameter_id));
}
const auto parameter_definition_it =
param_definition_variants.find(parameter_id);
if (parameter_definition_it == param_definition_variants.end()) {
return absl::InvalidArgumentError(
"Found a stray parameter block OBU (no matching parameter "
"definition).");
}
// TODO(b/359588455): Use `ReadBitBuffer::Seek` to go back to the start of the
// OBU. Update `ReadAndValidatePayload` to expect to read
// `parameter_id`.
const auto cast_to_base_pointer = [](const auto& param_definition) {
return static_cast<const ParamDefinition*>(&param_definition);
};
const int64_t remaining_payload_size = payload_size - encoded_uleb128_size;
auto parameter_block_obu = std::make_unique<ParameterBlockObu>(
header, parameter_id,
*std::visit(cast_to_base_pointer, parameter_definition_it->second));
// TODO(b/338474387): Test reading in extension parameters.
RETURN_IF_NOT_OK(
parameter_block_obu->ReadAndValidatePayload(remaining_payload_size, rb));
return parameter_block_obu;
}
ParameterBlockObu::ParameterBlockObu(const ObuHeader& header,
DecodedUleb128 parameter_id,
const ParamDefinition& param_definition)
: ObuBase(header, kObuIaParameterBlock),
parameter_id_(parameter_id),
param_definition_(param_definition) {}
absl::Status ParameterBlockObu::InterpolateMixGainParameterData(
const MixGainParameterData* mix_gain_parameter_data,
InternalTimestamp start_time, InternalTimestamp end_time,
InternalTimestamp target_time, float& target_mix_gain_db) {
return InterpolateMixGainValue(
mix_gain_parameter_data->animation_type,
MixGainParameterData::kAnimateStep, MixGainParameterData::kAnimateLinear,
MixGainParameterData::kAnimateBezier,
[&mix_gain_parameter_data]() {
return std::get<AnimationStepInt16>(mix_gain_parameter_data->param_data)
.start_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationLinearInt16>(
mix_gain_parameter_data->param_data)
.start_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationLinearInt16>(
mix_gain_parameter_data->param_data)
.end_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationBezierInt16>(
mix_gain_parameter_data->param_data)
.start_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationBezierInt16>(
mix_gain_parameter_data->param_data)
.end_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationBezierInt16>(
mix_gain_parameter_data->param_data)
.control_point_value;
},
[&mix_gain_parameter_data]() {
return std::get<AnimationBezierInt16>(
mix_gain_parameter_data->param_data)
.control_point_relative_time;
},
start_time, end_time, target_time, target_mix_gain_db);
}
DecodedUleb128 ParameterBlockObu::GetDuration() const {
if (param_definition_.param_definition_mode_ == 1) {
return duration_;
} else {
return param_definition_.duration_;
}
}
DecodedUleb128 ParameterBlockObu::GetConstantSubblockDuration() const {
if (param_definition_.param_definition_mode_ == 1) {
return constant_subblock_duration_;
} else {
return param_definition_.constant_subblock_duration_;
}
}
DecodedUleb128 ParameterBlockObu::GetNumSubblocks() const {
const DecodedUleb128 duration = GetDuration();
const DecodedUleb128 constant_subblock_duration =
GetConstantSubblockDuration();
DecodedUleb128 num_subblocks;
if (constant_subblock_duration != 0) {
// Get the implicit value of `num_subblocks` using `ceil(duration /
// constant_subblock_duration)`. Integer division with ceil is equivalent.
num_subblocks = duration / constant_subblock_duration;
if (duration % constant_subblock_duration != 0) {
num_subblocks += 1;
}
return num_subblocks;
}
// The subblocks is explicitly in the OBU or `param_definition_`.
if (param_definition_.param_definition_mode_ == 1) {
num_subblocks = num_subblocks_;
} else {
num_subblocks = param_definition_.GetNumSubblocks();
}
return num_subblocks;
}
absl::StatusOr<DecodedUleb128> ParameterBlockObu::GetSubblockDuration(
int subblock_index) const {
return GetParameterSubblockDuration<DecodedUleb128>(
subblock_index, GetNumSubblocks(), GetConstantSubblockDuration(),
GetDuration(), param_definition_.param_definition_mode_,
[this](int i) { return *this->subblocks_[i].subblock_duration; },
[this](int i) { return this->param_definition_.GetSubblockDuration(i); });
}
absl::Status ParameterBlockObu::SetSubblockDuration(int subblock_index,
DecodedUleb128 duration) {
CHECK_NE(param_definition_.param_definition_mode_, 0)
<< "Calling ParameterBlockObu::SetSubblockDuration() is disallowed when "
<< "`param_definition_mode_ == 0`";
const DecodedUleb128 num_subblocks = GetNumSubblocks();
if (subblock_index > num_subblocks) {
return absl::InvalidArgumentError(absl::StrCat(
"Setting subblock duration for subblock_index = ", subblock_index,
" but there are only num_subblocks = ", num_subblocks));
}
const DecodedUleb128 constant_subblock_duration =
GetConstantSubblockDuration();
// Resets the subblock duration to not holding any value.
subblocks_[subblock_index].subblock_duration.reset();
if (constant_subblock_duration == 0) {
// Overwrite the value in the parameter block.
subblocks_[subblock_index].subblock_duration = duration;
}
return absl::OkStatus();
}
absl::Status ParameterBlockObu::GetLinearMixGain(
InternalTimestamp obu_relative_time, float& linear_mix_gain) const {
if (param_definition_.GetType() !=
ParamDefinition::kParameterDefinitionMixGain) {
return absl::InvalidArgumentError("Expected Mix Gain Parameter Definition");
}
const DecodedUleb128 num_subblocks = GetNumSubblocks();
int target_subblock_index = -1;
InternalTimestamp target_subblock_start_time = -1;
InternalTimestamp subblock_relative_start_time = 0;
InternalTimestamp subblock_relative_end_time = 0;
for (int i = 0; i < num_subblocks; i++) {
const auto subblock_duration = GetSubblockDuration(i);
if (!subblock_duration.ok()) {
return subblock_duration.status();
}
if (subblock_relative_start_time <= obu_relative_time &&
obu_relative_time <
(subblock_relative_start_time + subblock_duration.value())) {
target_subblock_index = i;
target_subblock_start_time = subblock_relative_start_time;
subblock_relative_end_time =
subblock_relative_start_time + subblock_duration.value();
break;
}
subblock_relative_start_time += subblock_duration.value();
}
if (target_subblock_index == -1) {
return absl::UnknownError(
absl::StrCat("Trying to get mix gain for target_subblock_index = -1, "
"with num_subblocks = ",
num_subblocks));
}
float mix_gain_db = 0;
RETURN_IF_NOT_OK(InterpolateMixGainParameterData(
static_cast<const MixGainParameterData*>(
subblocks_[target_subblock_index].param_data.get()),
subblock_relative_start_time, subblock_relative_end_time,
obu_relative_time, mix_gain_db));
// Mix gain data is in dB and stored in Q7.8. Convert to the linear value.
linear_mix_gain = std::pow(10.0f, mix_gain_db / 20.0f);
return absl::OkStatus();
}
absl::Status ParameterBlockObu::InitializeSubblocks(
DecodedUleb128 duration, DecodedUleb128 constant_subblock_duration,
DecodedUleb128 num_subblocks) {
CHECK_EQ(param_definition_.param_definition_mode_, 1)
<< "InitializeSubblocks() with input arguments should only "
<< "be called when `param_definition_mode_ == 1`";
SetDuration(duration);
SetConstantSubblockDuration(constant_subblock_duration);
SetNumSubblocks(num_subblocks);
subblocks_.resize(static_cast<size_t>(GetNumSubblocks()));
init_status_ = absl::OkStatus();
return init_status_;
}
absl::Status ParameterBlockObu::InitializeSubblocks() {
CHECK_EQ(param_definition_.param_definition_mode_, 0)
<< "InitializeSubblocks() without input arguments should only "
<< "be called when `param_definition_mode_ == 0`";
subblocks_.resize(static_cast<size_t>(GetNumSubblocks()));
init_status_ = absl::OkStatus();
return absl::OkStatus();
}
void ParameterBlockObu::PrintObu() const {
if (!init_status_.ok()) {
LOG(ERROR) << "This OBU failed to initialize with error= " << init_status_;
}
LOG(INFO) << "Parameter Block OBU:";
LOG(INFO) << " // param_definition:";
param_definition_.Print();
LOG(INFO) << " parameter_id= " << parameter_id_;
if (param_definition_.param_definition_mode_ == 1) {
LOG(INFO) << " duration= " << duration_;
LOG(INFO) << " constant_subblock_duration= "
<< constant_subblock_duration_;
if (constant_subblock_duration_ == 0) {
LOG(INFO) << " num_subblocks= " << num_subblocks_;
}
}
const DecodedUleb128 num_subblocks = GetNumSubblocks();
for (int i = 0; i < num_subblocks; i++) {
LOG(INFO) << " subblocks[" << i << "]";
subblocks_[i].Print();
}
}
void ParameterBlockObu::SetDuration(DecodedUleb128 duration) {
CHECK_NE(param_definition_.param_definition_mode_, 0)
<< "Calling ParameterBlockObu::SetDuration() is disallowed when "
<< "`param_definition_mode_ == 0`";
duration_ = duration;
}
void ParameterBlockObu::SetConstantSubblockDuration(
DecodedUleb128 constant_subblock_duration) {
CHECK_NE(param_definition_.param_definition_mode_, 0)
<< "Calling ParameterBlockObu::SetConstantSubblockDuration() is "
<< "disallowed when `param_definition_mode_ == 0`";
constant_subblock_duration_ = constant_subblock_duration;
}
void ParameterBlockObu::SetNumSubblocks(DecodedUleb128 num_subblocks) {
CHECK_NE(param_definition_.param_definition_mode_, 0)
<< "Calling ParameterBlockObu::SetNumSubblocks() is "
<< "disallowed when `param_definition_mode_ == 0`";
if (GetConstantSubblockDuration() != 0) {
// Nothing to do. The field is implicit.
return;
}
num_subblocks_ = num_subblocks;
}
absl::Status ParameterBlockObu::ValidateAndWritePayload(
WriteBitBuffer& wb) const {
if (!init_status_.ok()) {
LOG(ERROR) << "Cannot write a Parameter Block OBU whose initialization "
<< "did not run successfully. init_status_= " << init_status_;
return init_status_;
}
RETURN_IF_NOT_OK(wb.WriteUleb128(parameter_id_));
// Initialized from OBU or `param_definition_` depending on
// `param_definition_mode_`.
// Write fields that are conditional on `param_definition_mode_`.
bool validate_total_subblock_durations = false;
if (param_definition_.param_definition_mode_ != 0) {
RETURN_IF_NOT_OK(wb.WriteUleb128(duration_));
RETURN_IF_NOT_OK(wb.WriteUleb128(constant_subblock_duration_));
if (constant_subblock_duration_ == 0) {
RETURN_IF_NOT_OK(wb.WriteUleb128(num_subblocks_));
validate_total_subblock_durations = true;
}
}
// Validate the associated `param_definition`.
RETURN_IF_NOT_OK(param_definition_.Validate());
// Loop through to write the `subblocks_` vector and validate the total
// subblock duration if needed.
int64_t total_subblock_durations = 0;
for (const auto& subblock : subblocks_) {
if (validate_total_subblock_durations) {
total_subblock_durations += *subblock.subblock_duration;
}
RETURN_IF_NOT_OK(subblock.Write(wb));
}
// Check total duration matches expected duration.
if (validate_total_subblock_durations &&
total_subblock_durations != duration_) {
return absl::InvalidArgumentError(absl::StrCat(
"Expected total_subblock_durations = ", total_subblock_durations,
" to match the expected duration_ = ", duration_));
}
return absl::OkStatus();
}
absl::Status ParameterBlockObu::ReadAndValidatePayloadDerived(
int64_t /*payload_size*/, ReadBitBuffer& rb) {
// Validate the associated `param_definition`.
RETURN_IF_NOT_OK(param_definition_.Validate());
if (param_definition_.param_definition_mode_) {
RETURN_IF_NOT_OK(rb.ReadULeb128(duration_));
RETURN_IF_NOT_OK(rb.ReadULeb128(constant_subblock_duration_));
if (constant_subblock_duration_ == 0) {
RETURN_IF_NOT_OK(rb.ReadULeb128(num_subblocks_));
}
}
const auto num_subblocks = GetNumSubblocks();
subblocks_.resize(num_subblocks);
// `subblock_duration` is conditionally included based on
// `param_definition_mode_` and `constant_subblock_duration_`.
const bool include_subblock_duration =
param_definition_.param_definition_mode_ &&
constant_subblock_duration_ == 0;
int64_t total_subblock_durations = 0;
for (auto& subblock : subblocks_) {
if (include_subblock_duration) {
// First make `subblock_duration` contain a value so it will be read in.
subblock.subblock_duration = 0;
}
RETURN_IF_NOT_OK(subblock.ReadAndValidate(param_definition_, rb));
if (include_subblock_duration) {
total_subblock_durations += *subblock.subblock_duration;
}
}
if (include_subblock_duration && total_subblock_durations != duration_) {
return absl::InvalidArgumentError(
"Subblock durations do not match the total duration.");
}
init_status_ = absl::OkStatus();
return absl::OkStatus();
}
} // namespace iamf_tools