blob: e1fa5c0dfbbbf231dbc652dce4365087cd55d2c1 [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/obu_header.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "iamf/common/leb_generator.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/numeric_utils.h"
#include "iamf/common/utils/validation_utils.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/types.h"
namespace iamf_tools {
const absl::flat_hash_set<ObuType> kTemporalUnitObuTypes = {
kObuIaAudioFrame, kObuIaAudioFrameId0, kObuIaAudioFrameId1,
kObuIaAudioFrameId2, kObuIaAudioFrameId3, kObuIaAudioFrameId4,
kObuIaAudioFrameId5, kObuIaAudioFrameId6, kObuIaAudioFrameId7,
kObuIaAudioFrameId8, kObuIaAudioFrameId9, kObuIaAudioFrameId10,
kObuIaAudioFrameId11, kObuIaAudioFrameId12, kObuIaAudioFrameId13,
kObuIaAudioFrameId14, kObuIaAudioFrameId15, kObuIaAudioFrameId16,
kObuIaAudioFrameId17, kObuIaParameterBlock, kObuIaTemporalDelimiter};
namespace {
// Returns `true` if this `ObuType` is allowed to have the `obu_redundant_copy`
// flag set. `false` otherwise.
bool IsRedundantCopyAllowed(ObuType type) {
if (kObuIaAudioFrameId0 <= type && type <= kObuIaAudioFrameId17) {
return false;
}
switch (type) {
case kObuIaTemporalDelimiter:
case kObuIaAudioFrame:
case kObuIaParameterBlock:
return false;
default:
return true;
}
}
// Returns `true` if this `ObuType` is allowed to have the
// `obu_trimming_status_flag` flag set. `false` otherwise.
bool IsTrimmingStatusFlagAllowed(ObuType type) {
if (kObuIaAudioFrameId0 <= type && type <= kObuIaAudioFrameId17) {
return true;
}
switch (type) {
case kObuIaAudioFrame:
return true;
default:
return false;
}
}
// Validates the OBU and returns an error if anything is non-comforming.
// Requires that all fields including `obu_size_` are initialized.
absl::Status Validate(const ObuHeader& header) {
// Validate member fields are self-consistent.
if (!header.obu_extension_flag && header.extension_header_size > 0) {
return absl::InvalidArgumentError(
"`obu_extension_flag_` implied there was no extension header, "
"but `extension_header_size_` indicates there is one.");
}
RETURN_IF_NOT_OK(ValidateContainerSizeEqual("extension_header_bytes_",
header.extension_header_bytes,
header.extension_header_size));
// Validate IAMF imposed requirements.
if (header.obu_redundant_copy && !IsRedundantCopyAllowed(header.obu_type)) {
return absl::InvalidArgumentError(absl::StrCat(
"The redundant copy flag is not allowed to be set for obu_type= ",
header.obu_type));
}
if (header.obu_trimming_status_flag &&
!IsTrimmingStatusFlagAllowed(header.obu_type)) {
return absl::InvalidArgumentError(absl::StrCat(
"The trimming status flag flag is not allowed to be set for "
"obu_type= ",
header.obu_type));
}
return absl::OkStatus();
}
absl::Status WriteFieldsAfterObuSize(const ObuHeader& header,
WriteBitBuffer& wb) {
// These fields are conditionally in the OBU.
if (header.obu_trimming_status_flag) {
RETURN_IF_NOT_OK(wb.WriteUleb128(header.num_samples_to_trim_at_end));
RETURN_IF_NOT_OK(wb.WriteUleb128(header.num_samples_to_trim_at_start));
}
// These fields are conditionally in the OBU.
if (header.obu_extension_flag) {
RETURN_IF_NOT_OK(wb.WriteUleb128(header.extension_header_size));
RETURN_IF_NOT_OK(ValidateContainerSizeEqual("extension_header_bytes_",
header.extension_header_bytes,
header.extension_header_size));
RETURN_IF_NOT_OK(
wb.WriteUint8Span(absl::MakeConstSpan(header.extension_header_bytes)));
}
return absl::OkStatus();
}
// IAMF imposes two restrictions on the size of an entire OBU.
// - IAMF v1.1.0 imposes a maximum size of an entire OBU must be 2 MB or less.
// - IAMF v1.1.0 also imposes a maximum size of `obu_size` must be 2^21 - 4 or
// less.
//
// The second restriction is equivalent when `obu_size` is written using the
// minimal number of bytes. It is less strict than the first restriction if
// `obu_size` is written using padded bytes. Therefore the second restriction is
// irrelevant.
absl::Status ValidateObuIsUnderTwoMegabytes(DecodedUleb128 obu_size,
size_t size_of_obu_size) {
CHECK_LE(size_of_obu_size, kMaxLeb128Size);
// Subtract out `obu_size` and all preceding data (one byte).
const uint32_t max_obu_size =
kEntireObuSizeMaxTwoMegabytes - 1 - size_of_obu_size;
if (obu_size > max_obu_size) {
return absl::InvalidArgumentError(
absl::StrCat("obu_size= ", obu_size,
" results in an OBU greater than 2 MB in size."));
}
return absl::OkStatus();
}
absl::Status GetSizeOfEncodedLeb128(const LebGenerator& leb_generator,
DecodedUleb128 leb128, size_t& size) {
// Calculate how many bytes `obu_size` will take up based on the current leb
// generator.
WriteBitBuffer temp_wb_obu_size_only(8, leb_generator);
RETURN_IF_NOT_OK(temp_wb_obu_size_only.WriteUleb128(leb128));
size = temp_wb_obu_size_only.bit_buffer().size();
return absl::OkStatus();
}
// Validates the header and initializes the output argument. On success
// `obu_size` is set to imply the associated payload has a size of
// `payload_serialized_size`.
absl::Status GetObuSizeAndValidate(const LebGenerator& leb_generator,
const ObuHeader& header,
int64_t payload_serialized_size,
DecodedUleb128& obu_size) {
// Validate to avoid issues with the `static_cast` below.
if (0 > payload_serialized_size ||
payload_serialized_size > std::numeric_limits<uint32_t>::max()) {
return absl::InvalidArgumentError(absl::StrCat(
"Payload size must fit into a `uint32_t`. payload_serialized_size= ",
payload_serialized_size));
}
// Set `obu_size`. It depends on the size of all fields after `obu_size` and
// `payload_serialized_size`.
{
WriteBitBuffer temp_wb_after_obu_size(64, leb_generator);
RETURN_IF_NOT_OK(WriteFieldsAfterObuSize(header, temp_wb_after_obu_size));
if (!temp_wb_after_obu_size.IsByteAligned() ||
temp_wb_after_obu_size.bit_buffer().size() >
std::numeric_limits<uint32_t>::max()) {
return absl::UnknownError(absl::StrCat(
"Result from `WriteFieldsAfterObuSize()` was not byte-aligned ",
"or it did not fit into a `uint32_t`. `bit_offset` is ",
temp_wb_after_obu_size.bit_offset()));
}
// Get the size of fields after `obu_size`.
const uint32_t fields_after_obu_size =
temp_wb_after_obu_size.bit_buffer().size();
// `obu_size` represents the size of all fields after itself and
// serialized_size. Sum them to get `obu_size`, while ensuring they fit into
// a `uint32_t`.
RETURN_IF_NOT_OK(AddUint32CheckOverflow(
static_cast<uint32_t>(fields_after_obu_size),
static_cast<uint32_t>(payload_serialized_size), obu_size));
size_t size_of_obu_size;
RETURN_IF_NOT_OK(
GetSizeOfEncodedLeb128(leb_generator, obu_size, size_of_obu_size));
RETURN_IF_NOT_OK(
ValidateObuIsUnderTwoMegabytes(obu_size, size_of_obu_size));
}
// Validate the OBU.
RETURN_IF_NOT_OK(Validate(header));
return absl::OkStatus();
}
// Returns the size of the payload associated with the OBU, i.e. the number of
// bytes that contain payload data. See
// https://aomediacodec.github.io/iamf/#obu_size for more details.
int64_t GetObuPayloadSize(DecodedUleb128 obu_size,
uint8_t num_samples_to_trim_at_end_size,
uint8_t num_samples_to_trim_at_start_size,
uint8_t extension_header_size_size,
uint8_t extension_header_bytes_size) {
return static_cast<int64_t>(obu_size) -
(num_samples_to_trim_at_end_size + num_samples_to_trim_at_start_size +
extension_header_size_size + extension_header_bytes_size);
}
absl::Status FillHeaderMetadata(ReadBitBuffer& rb,
HeaderMetadata& output_header_metadata) {
uint64_t obu_type_uint64_t = 0;
RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, obu_type_uint64_t));
output_header_metadata.obu_type = static_cast<ObuType>(obu_type_uint64_t);
// We don't care about the next three bits.
bool dummy_bool;
RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
RETURN_IF_NOT_OK(rb.ReadBoolean(dummy_bool));
DecodedUleb128 obu_size;
int8_t size_of_obu_size = 0;
RETURN_IF_NOT_OK(rb.ReadULeb128(obu_size, size_of_obu_size));
// The extra byte is for the `obu_type` field + the three boolean fields.
output_header_metadata.total_obu_size = obu_size + size_of_obu_size + 1;
return absl::OkStatus();
}
} // namespace
bool ObuHeader::IsTemporalUnitObuType(const ObuType obu_type) {
return kTemporalUnitObuTypes.contains(obu_type);
}
absl::StatusOr<HeaderMetadata> ObuHeader::PeekObuTypeAndTotalObuSize(
ReadBitBuffer& rb) {
const uint64_t position_before_header = rb.Tell();
HeaderMetadata header_metadata;
auto header_metadata_status = FillHeaderMetadata(rb, header_metadata);
RETURN_IF_NOT_OK(rb.Seek(position_before_header));
if (!header_metadata_status.ok()) {
return header_metadata_status;
}
return header_metadata;
}
absl::Status ObuHeader::ValidateAndWrite(int64_t payload_serialized_size,
WriteBitBuffer& wb) const {
DecodedUleb128 obu_size;
RETURN_IF_NOT_OK(GetObuSizeAndValidate(wb.leb_generator_, *this,
payload_serialized_size, obu_size));
// Write the OBU Header to the buffer.
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_type, 5));
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_redundant_copy, 1));
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_trimming_status_flag, 1));
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(obu_extension_flag, 1));
RETURN_IF_NOT_OK(wb.WriteUleb128(obu_size));
RETURN_IF_NOT_OK(WriteFieldsAfterObuSize(*this, wb));
return absl::OkStatus();
}
// Reads all the fields of the OBU Header as defined in the IAMF spec
// (https://aomediacodec.github.io/iamf/#obu-header-syntax). Most of these
// fields are stored directly in the ObuHeader struct; however, for reasons
// relating to the existing encoder, `obu_type` and `obu_size` are not. We
// instead use `output_obu_type` and `output_payload_serialized_size` as output
// parameters. Note that `output_payload_serialized_size` is a derived value
// from `obu_size`, as this is the value the caller is more interested in.
absl::Status ObuHeader::ReadAndValidate(
ReadBitBuffer& rb, int64_t& output_payload_serialized_size) {
uint64_t obu_type_uint64_t = 0;
RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, obu_type_uint64_t));
obu_type = static_cast<ObuType>(obu_type_uint64_t);
RETURN_IF_NOT_OK(rb.ReadBoolean(obu_redundant_copy));
RETURN_IF_NOT_OK(rb.ReadBoolean(obu_trimming_status_flag));
RETURN_IF_NOT_OK(rb.ReadBoolean(obu_extension_flag));
DecodedUleb128 obu_size;
int8_t size_of_obu_size = 0;
RETURN_IF_NOT_OK(rb.ReadULeb128(obu_size, size_of_obu_size));
RETURN_IF_NOT_OK(ValidateObuIsUnderTwoMegabytes(obu_size, size_of_obu_size));
int8_t num_samples_to_trim_at_end_size = 0;
int8_t num_samples_to_trim_at_start_size = 0;
if (obu_trimming_status_flag) {
RETURN_IF_NOT_OK(rb.ReadULeb128(num_samples_to_trim_at_end,
num_samples_to_trim_at_end_size));
RETURN_IF_NOT_OK(rb.ReadULeb128(num_samples_to_trim_at_start,
num_samples_to_trim_at_start_size));
}
int8_t extension_header_size_size = 0;
if (obu_extension_flag) {
RETURN_IF_NOT_OK(
rb.ReadULeb128(extension_header_size, extension_header_size_size));
extension_header_bytes.resize(extension_header_size);
RETURN_IF_NOT_OK(rb.ReadUint8Span(absl::MakeSpan(extension_header_bytes)));
}
output_payload_serialized_size = GetObuPayloadSize(
obu_size, num_samples_to_trim_at_end_size,
num_samples_to_trim_at_start_size, extension_header_size_size,
extension_header_bytes.size());
if (output_payload_serialized_size < 0) {
return absl::InvalidArgumentError(
"obu_size not valid for OBU flags. Negative remaining payload size.");
}
RETURN_IF_NOT_OK(Validate(*this));
return absl::OkStatus();
}
void ObuHeader::Print(const LebGenerator& leb_generator,
int64_t payload_serialized_size) const {
// Generate the header. Solely for the purpose of getting the correct
// `obu_size`.
DecodedUleb128 obu_size;
if (!GetObuSizeAndValidate(leb_generator, *this, payload_serialized_size,
obu_size)
.ok()) {
LOG(ERROR) << "Error printing OBU header";
return;
}
LOG(INFO) << " obu_type= " << obu_type;
LOG(INFO) << " size_of(payload_) " << payload_serialized_size;
LOG(INFO) << " obu_type= " << absl::StrCat(obu_type);
LOG(INFO) << " obu_redundant_copy= " << obu_redundant_copy;
LOG(INFO) << " obu_trimming_status_flag= " << obu_trimming_status_flag;
LOG(INFO) << " obu_extension_flag= " << obu_extension_flag;
LOG(INFO) << " obu_size=" << obu_size;
if (obu_trimming_status_flag) {
LOG(INFO) << " num_samples_to_trim_at_end= " << num_samples_to_trim_at_end;
LOG(INFO) << " num_samples_to_trim_at_start= "
<< num_samples_to_trim_at_start;
}
if (obu_extension_flag) {
LOG(INFO) << " extension_header_size= " << extension_header_size;
LOG(INFO) << " extension_header_bytes omitted.";
}
}
} // namespace iamf_tools