blob: 038019cabb465adcd227f4f0f8c4fc44557e2772 [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/cli/obu_sequencer_base.h"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <list>
#include <optional>
#include <utility>
#include <vector>
#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/types/span.h"
#include "iamf/cli/audio_element_with_data.h"
#include "iamf/cli/audio_frame_with_data.h"
#include "iamf/cli/cli_util.h"
#include "iamf/cli/parameter_block_with_data.h"
#include "iamf/cli/profile_filter.h"
#include "iamf/cli/temporal_unit_view.h"
#include "iamf/common/leb_generator.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/arbitrary_obu.h"
#include "iamf/obu/audio_element.h"
#include "iamf/obu/audio_frame.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/ia_sequence_header.h"
#include "iamf/obu/mix_presentation.h"
#include "iamf/obu/obu_header.h"
#include "iamf/obu/parameter_block.h"
#include "iamf/obu/temporal_delimiter.h"
#include "iamf/obu/types.h"
namespace iamf_tools {
namespace {
// Write buffer. Let's start with 64 KB. The buffer will resize for larger
// OBUs if needed.
constexpr int64_t kBufferStartSize = 65536;
/*!\brief Map of start timestamp -> OBUs in that temporal unit.
*
* Map of temporal unit start time -> OBUs that overlap this temporal unit.
* Using absl::btree_map for convenience as this allows iterating by
* timestamp (which is the key).
*/
typedef absl::btree_map<int32_t, TemporalUnitView> TemporalUnitMap;
/*!\brief Helper class to abort an `ObuSequencerBase` on destruction.
*
* This class calls on an `ObuSequencerBase::Abort` on destruction. Or does
* nothing if `CancelAbort` is called.
*
* Typically, this is useful to create an instance of this class on the stack,
* in the scope of a function which has many locations where it may return an
* un-recoverable error. When those exit points are reached, the sequencer will
* automatically be aborted.
*
* Before any successful exit point, `CancelAbort` should be called, which will
* prevent the sequencer from being aborting.
*/
class AbortOnDestruct {
public:
/*!\brief Constructor
*
* \param obu_sequencer The `ObuSequencerBase` to abort on destruction.
*/
explicit AbortOnDestruct(ObuSequencerBase* obu_sequencer)
: obu_sequencer(obu_sequencer) {}
/*!\brief Destructor */
~AbortOnDestruct() {
if (obu_sequencer != nullptr) {
obu_sequencer->Abort();
}
}
/*!\brief Cancels the abort on destruction. */
void CancelAbort() { obu_sequencer = nullptr; }
private:
ObuSequencerBase* obu_sequencer;
};
template <typename KeyValueMap, typename KeyComparator>
std::vector<uint32_t> SortedKeys(const KeyValueMap& map,
const KeyComparator& comparator) {
std::vector<uint32_t> keys;
keys.reserve(map.size());
for (const auto& [key, value] : map) {
keys.push_back(key);
}
std::sort(keys.begin(), keys.end(), comparator);
return keys;
}
// Some IA Sequences can be "trivial" and missing descriptor OBUs or audio
// frames. These would decode to an empty stream. Fallback to some reasonable,
// but arbitrary default values, when the true value is undefined.
// Fallback number of samples per frame when there are no audio frames.
constexpr uint32_t kFallbackSamplesPerFrame = 1024;
// Fallback sample rate when there are no Codec Config OBUs.
constexpr uint32_t kFallbackSampleRate = 48000;
// Fallback bit-depth when there are no Codec Config OBUs.
constexpr uint8_t kFallbackBitDepth = 16;
// Fallback number of channels when there are no audio elements.
constexpr uint32_t kFallbackNumChannels = 2;
// Fallback first PTS when there are no audio frames.
constexpr int64_t kFallbackFirstPts = 0;
// Gets the sum of the number of channels for the given audio elements. Or falls
// back to a default value if there are no audio elements.
int32_t GetNumberOfChannels(
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements) {
if (audio_elements.empty()) {
// The muxer fails if we return the true value (0 channels).
return kFallbackNumChannels;
}
int32_t num_channels = 0;
for (const auto& [audio_element_id, audio_element] : audio_elements) {
// Add the number of channels for every substream in every audio element.
for (const auto& [substream_id, labels] :
audio_element.substream_id_to_labels) {
num_channels += static_cast<int32_t>(labels.size());
}
}
return num_channels;
}
// Gets the common sample rate and bit depth for the given codec config OBUs. Or
// falls back to default values if there are no codec configs.
absl::Status GetCommonSampleRateAndBitDepth(
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
uint32_t& common_sample_rate, uint8_t& common_bit_depth,
bool& requires_resampling) {
if (codec_config_obus.empty()) {
// The true value is undefined, but the muxer requires non-zero values.
common_sample_rate = kFallbackSampleRate;
common_bit_depth = kFallbackBitDepth;
requires_resampling = false;
return absl::OkStatus();
}
requires_resampling = false;
absl::flat_hash_set<uint32_t> sample_rates;
absl::flat_hash_set<uint8_t> bit_depths;
for (const auto& [unused_id, obu] : codec_config_obus) {
sample_rates.insert(obu.GetOutputSampleRate());
bit_depths.insert(obu.GetBitDepthToMeasureLoudness());
}
return ::iamf_tools::GetCommonSampleRateAndBitDepth(
sample_rates, bit_depths, common_sample_rate, common_bit_depth,
requires_resampling);
}
absl::Status WriteObusWithHook(
ArbitraryObu::InsertionHook insertion_hook,
const std::vector<const ArbitraryObu*>& arbitrary_obus,
WriteBitBuffer& wb) {
for (const auto& arbitrary_obu : arbitrary_obus) {
if (arbitrary_obu->insertion_hook_ == insertion_hook) {
RETURN_IF_NOT_OK(arbitrary_obu->ValidateAndWriteObu(wb));
}
}
return absl::OkStatus();
}
absl::Status FillDescriptorStatistics(
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
auto& descriptor_statistics) {
descriptor_statistics.common_samples_per_frame = kFallbackSamplesPerFrame;
descriptor_statistics.common_sample_rate = kFallbackSampleRate;
descriptor_statistics.common_bit_depth = kFallbackBitDepth;
descriptor_statistics.num_channels = kFallbackNumChannels;
bool requires_resampling = false;
RETURN_IF_NOT_OK(GetCommonSampleRateAndBitDepth(
codec_config_obus, descriptor_statistics.common_sample_rate,
descriptor_statistics.common_bit_depth, requires_resampling));
if (requires_resampling) {
return absl::UnimplementedError(
"Codec Config OBUs with different bit-depths and/or sample "
"rates are not in base-enhanced/base/simple profile; they are not "
"allowed in ISOBMFF.");
}
// This assumes all Codec Configs have the same sample rate and frame size.
// We may need to be more careful if IA Samples do not all (except the
// final) have the same duration in the future.
return GetCommonSamplesPerFrame(
codec_config_obus, descriptor_statistics.common_samples_per_frame);
}
[[deprecated("Remove this, and related types when `PickAndPlace` is removed.")]]
absl::Status GenerateTemporalUnitMap(
const std::list<AudioFrameWithData>& audio_frames,
const std::list<ParameterBlockWithData>& parameter_blocks,
const std::list<ArbitraryObu>& arbitrary_obus,
TemporalUnitMap& temporal_unit_map) {
// Initially, guess the temporal units by the start time. Deeper validation
// and sanitization occurs when creating the TemporalUnitView.
struct UnsanitizedTemporalUnit {
std::vector<const ParameterBlockWithData*> parameter_blocks;
std::vector<const AudioFrameWithData*> audio_frames;
std::vector<const ArbitraryObu*> arbitrary_obus;
};
typedef absl::flat_hash_map<InternalTimestamp, UnsanitizedTemporalUnit>
UnsanitizedTemporalUnitMap;
UnsanitizedTemporalUnitMap unsanitized_temporal_unit_map;
for (const auto& parameter_block : parameter_blocks) {
unsanitized_temporal_unit_map[parameter_block.start_timestamp]
.parameter_blocks.push_back(&parameter_block);
}
for (auto& audio_frame : audio_frames) {
unsanitized_temporal_unit_map[audio_frame.start_timestamp]
.audio_frames.push_back(&audio_frame);
}
for (const auto& arbitrary_obu : arbitrary_obus) {
if (arbitrary_obu.insertion_tick_ == std::nullopt) {
continue;
}
unsanitized_temporal_unit_map[*arbitrary_obu.insertion_tick_]
.arbitrary_obus.push_back(&arbitrary_obu);
}
// Sanitize and build a map on the sanitized temporal units.
for (const auto& [timestamp, unsanitized_temporal_unit] :
unsanitized_temporal_unit_map) {
auto temporal_unit_view = TemporalUnitView::CreateFromPointers(
unsanitized_temporal_unit.parameter_blocks,
unsanitized_temporal_unit.audio_frames,
unsanitized_temporal_unit.arbitrary_obus);
if (!temporal_unit_view.ok()) {
return temporal_unit_view.status();
}
temporal_unit_map.emplace(timestamp, *std::move(temporal_unit_view));
}
return absl::OkStatus();
}
} // namespace
absl::Status ObuSequencerBase::WriteTemporalUnit(
bool include_temporal_delimiters, const TemporalUnitView& temporal_unit,
WriteBitBuffer& wb, int& num_samples) {
num_samples += temporal_unit.num_untrimmed_samples_;
if (include_temporal_delimiters) {
// Temporal delimiter has no payload.
const TemporalDelimiterObu obu((ObuHeader()));
RETURN_IF_NOT_OK(obu.ValidateAndWriteObu(wb));
}
RETURN_IF_NOT_OK(
WriteObusWithHook(ArbitraryObu::kInsertionHookBeforeParameterBlocksAtTick,
temporal_unit.arbitrary_obus_, wb));
// Write the Parameter Block OBUs.
for (const auto& parameter_blocks : temporal_unit.parameter_blocks_) {
const auto& parameter_block = parameter_blocks;
RETURN_IF_NOT_OK(parameter_block->obu->ValidateAndWriteObu(wb));
}
RETURN_IF_NOT_OK(
WriteObusWithHook(ArbitraryObu::kInsertionHookAfterParameterBlocksAtTick,
temporal_unit.arbitrary_obus_, wb));
// Write Audio Frame OBUs.
for (const auto& audio_frame : temporal_unit.audio_frames_) {
RETURN_IF_NOT_OK(audio_frame->obu.ValidateAndWriteObu(wb));
LOG_FIRST_N(INFO, 10) << "wb.bit_offset= " << wb.bit_offset()
<< " after Audio Frame";
}
RETURN_IF_NOT_OK(
WriteObusWithHook(ArbitraryObu::kInsertionHookAfterAudioFramesAtTick,
temporal_unit.arbitrary_obus_, wb));
if (!wb.IsByteAligned()) {
return absl::InvalidArgumentError("Write buffer not byte-aligned");
}
return absl::OkStatus();
}
// Writes the descriptor OBUs. Section 5.1.1
// (https://aomediacodec.github.io/iamf/#standalone-descriptor-obus) orders the
// OBUs by type.
//
// For Codec Config OBUs and Audio Element OBUs, the order is arbitrary. For
// determinism this implementation orders them by ascending ID.
//
// For Mix Presentation OBUs, the order is the same as the original order.
// Because the original ordering may be used downstream when selecting the mix
// presentation
// (https://aomediacodec.github.io/iamf/#processing-mixpresentation-selection).
//
// For Arbitrary OBUs, they are inserted in an order implied by the insertion
// hook. Ties are broken by the original order, when multiple OBUs have the same
// hook.
absl::Status ObuSequencerBase::WriteDescriptorObus(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
const std::list<MixPresentationObu>& mix_presentation_obus,
const std::list<ArbitraryObu>& arbitrary_obus, WriteBitBuffer& wb) {
// Write IA Sequence Header OBU.
RETURN_IF_NOT_OK(ia_sequence_header_obu.ValidateAndWriteObu(wb));
LOG(INFO) << "wb.bit_offset= " << wb.bit_offset()
<< " after IA Sequence Header";
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterIaSequenceHeader, arbitrary_obus, wb));
// Write Codec Config OBUs in ascending order of Codec Config IDs.
// TODO(b/332956880): Support customizing the ordering.
const std::vector<uint32_t> codec_config_ids =
SortedKeys(codec_config_obus, std::less<uint32_t>());
for (const auto id : codec_config_ids) {
RETURN_IF_NOT_OK(codec_config_obus.at(id).ValidateAndWriteObu(wb));
LOG(INFO) << "wb.bit_offset= " << wb.bit_offset() << " after Codec Config";
}
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterCodecConfigs, arbitrary_obus, wb));
// Write Audio Element OBUs in ascending order of Audio Element IDs.
// TODO(b/332956880): Support customizing the ordering.
const std::vector<uint32_t> audio_element_ids =
SortedKeys(audio_elements, std::less<uint32_t>());
for (const auto id : audio_element_ids) {
RETURN_IF_NOT_OK(audio_elements.at(id).obu.ValidateAndWriteObu(wb));
LOG(INFO) << "wb.bit_offset= " << wb.bit_offset() << " after Audio Element";
}
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterAudioElements, arbitrary_obus, wb));
// TODO(b/269708630): Ensure at least one the profiles in the IA Sequence
// Header supports all of the layers for scalable audio
// elements.
// Maintain the original order of Mix Presentation OBUs.
for (const auto& mix_presentation_obu : mix_presentation_obus) {
// Make sure the mix presentation is valid for at least one of the profiles
// in the sequence header before writing it.
absl::flat_hash_set<ProfileVersion> profile_version = {
ia_sequence_header_obu.GetPrimaryProfile(),
ia_sequence_header_obu.GetAdditionalProfile()};
RETURN_IF_NOT_OK(ProfileFilter::FilterProfilesForMixPresentation(
audio_elements, mix_presentation_obu, profile_version));
RETURN_IF_NOT_OK(mix_presentation_obu.ValidateAndWriteObu(wb));
LOG(INFO) << "wb.bit_offset= " << wb.bit_offset()
<< " after Mix Presentation";
}
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterMixPresentations, arbitrary_obus, wb));
return absl::OkStatus();
}
ObuSequencerBase::ObuSequencerBase(
const LebGenerator& leb_generator, bool include_temporal_delimiters,
bool delay_descriptors_until_first_untrimmed_sample)
: leb_generator_(leb_generator),
delay_descriptors_until_first_untrimmed_sample_(
delay_descriptors_until_first_untrimmed_sample),
include_temporal_delimiters_(include_temporal_delimiters),
wb_(kBufferStartSize, leb_generator) {}
ObuSequencerBase::~ObuSequencerBase() {
switch (state_) {
case kInitialized:
return;
case kPushDescriptorObusCalled:
case kPushSerializedDescriptorsCalled:
LOG(ERROR) << "OBUs have been pushed, but `ObuSequencerBase` is being "
"destroyed without calling `Close` or `Abort`.";
return;
case kClosed:
return;
}
// The above switch is exhaustive.
LOG(FATAL) << "Unexpected state: " << static_cast<int>(state_);
};
absl::Status ObuSequencerBase::PushDescriptorObus(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
const std::list<MixPresentationObu>& mix_presentation_obus,
const std::list<ArbitraryObu>& arbitrary_obus) {
// Many failure points should call `Abort`. We want to avoid leaving
// sequencers open if they may have invalid or corrupted IAMF data.
AbortOnDestruct abort_on_destruct(this);
switch (state_) {
case kInitialized:
break;
case kPushDescriptorObusCalled:
case kPushSerializedDescriptorsCalled:
return absl::FailedPreconditionError(
"`PushDescriptorObus` can only be called once.");
case kClosed:
return absl::FailedPreconditionError(
"`PushDescriptorObus` cannot be called after `Close` or `Abort`.");
}
state_ = kPushDescriptorObusCalled;
wb_.Reset();
// Serialize descriptor OBUS and adjacent arbitrary OBUs.
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookBeforeDescriptors, arbitrary_obus, wb_));
// Write out the descriptor OBUs.
RETURN_IF_NOT_OK(ObuSequencerBase::WriteDescriptorObus(
ia_sequence_header_obu, codec_config_obus, audio_elements,
mix_presentation_obus, arbitrary_obus, wb_));
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterDescriptors, arbitrary_obus, wb_));
// Cache the descriptor OBUs, so we can validate "functional" equivalence if
// the user calls `UpdateDescriptorObusAndClose`.
DescriptorStatistics descriptor_statistics{.descriptor_obus =
wb_.bit_buffer()};
RETURN_IF_NOT_OK(
FillDescriptorStatistics(codec_config_obus, descriptor_statistics));
descriptor_statistics_.emplace(std::move(descriptor_statistics));
if (!delay_descriptors_until_first_untrimmed_sample_) {
// Avoid unnecessary delay, for concrete classes that don't need
// `first_pts`.
RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
descriptor_statistics_->common_samples_per_frame,
descriptor_statistics_->common_sample_rate,
descriptor_statistics_->common_bit_depth,
descriptor_statistics_->first_untrimmed_timestamp,
descriptor_statistics_->num_channels,
descriptor_statistics_->descriptor_obus));
state_ = kPushSerializedDescriptorsCalled;
}
abort_on_destruct.CancelAbort();
return absl::OkStatus();
}
absl::Status ObuSequencerBase::PushTemporalUnit(
const TemporalUnitView& temporal_unit) {
// Many failure points should call `Abort`. We want to avoid leaving
// sequencers open if they may have invalid or corrupted IAMF data.
AbortOnDestruct abort_on_destruct(this);
switch (state_) {
case kInitialized:
return absl::FailedPreconditionError(
"PushDescriptorObus must be called before PushTemporalUnit.");
break;
case kPushDescriptorObusCalled:
case kPushSerializedDescriptorsCalled:
break;
case kClosed:
return absl::FailedPreconditionError(
"PushTemporalUnit can only be called before `Close` or `Abort`.");
}
wb_.Reset();
// Cache the frame for later
const int64_t start_timestamp =
static_cast<int64_t>(temporal_unit.start_timestamp_);
int num_samples = 0;
RETURN_IF_NOT_OK(WriteTemporalUnit(include_temporal_delimiters_,
temporal_unit, wb_, num_samples));
cumulative_num_samples_for_logging_ += num_samples;
num_temporal_units_for_logging_++;
if (!descriptor_statistics_->first_untrimmed_timestamp.has_value()) {
// Treat the initial temporal units as a special case, this helps gather
// statistics about the first untrimmed sample.
RETURN_IF_NOT_OK(HandleInitialTemporalUnits(
temporal_unit, absl::MakeConstSpan(wb_.bit_buffer())));
} else if (temporal_unit.num_samples_to_trim_at_start_ > 0) {
return absl::InvalidArgumentError(
"A unit has samples to trim at start, but the first untrimmed sample "
"was already found.");
} else [[likely]] {
// This is by far the most common case, after we have seen the first real
// frame of audio, we can handle this simply.
RETURN_IF_NOT_OK(PushSerializedTemporalUnit(
start_timestamp, num_samples, absl::MakeConstSpan(wb_.bit_buffer())));
}
abort_on_destruct.CancelAbort();
return absl::OkStatus();
}
absl::Status ObuSequencerBase::PickAndPlace(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
const std::list<MixPresentationObu>& mix_presentation_obus,
const std::list<AudioFrameWithData>& audio_frames,
const std::list<ParameterBlockWithData>& parameter_blocks,
const std::list<ArbitraryObu>& arbitrary_obus) {
RETURN_IF_NOT_OK(PushDescriptorObus(ia_sequence_header_obu, codec_config_obus,
audio_elements, mix_presentation_obus,
arbitrary_obus));
TemporalUnitMap temporal_unit_map;
RETURN_IF_NOT_OK(GenerateTemporalUnitMap(audio_frames, parameter_blocks,
arbitrary_obus, temporal_unit_map));
for (const auto& [timestamp, temporal_unit] : temporal_unit_map) {
RETURN_IF_NOT_OK(PushTemporalUnit(temporal_unit));
}
return Close();
}
absl::Status ObuSequencerBase::UpdateDescriptorObusAndClose(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
const std::list<MixPresentationObu>& mix_presentation_obus,
const std::list<ArbitraryObu>& arbitrary_obus) {
// Many failure points should call `Abort`. We want to avoid leaving
// sequencers open if they may have invalid or corrupted IAMF data.
AbortOnDestruct abort_on_destruct(this);
switch (state_) {
case kInitialized:
return absl::FailedPreconditionError(
"`UpdateDescriptorObusAndClose` must be called after "
"`PushDescriptorObus`.");
case kPushDescriptorObusCalled:
case kPushSerializedDescriptorsCalled:
break;
case kClosed:
return absl::FailedPreconditionError(
"`Abort` or `Close` previously called.");
}
wb_.Reset();
// Serialize descriptor OBUS and adjacent arbitrary OBUs.
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookBeforeDescriptors, arbitrary_obus, wb_));
// Write out the descriptor OBUs.
RETURN_IF_NOT_OK(ObuSequencerBase::WriteDescriptorObus(
ia_sequence_header_obu, codec_config_obus, audio_elements,
mix_presentation_obus, arbitrary_obus, wb_));
RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
ArbitraryObu::kInsertionHookAfterDescriptors, arbitrary_obus, wb_));
// We're a bit loose with what types of metadata we allow to change. Check
// at least the "functional" statistics are equivalent.
DescriptorStatistics descriptor_statistics{.descriptor_obus =
wb_.bit_buffer()};
RETURN_IF_NOT_OK(
FillDescriptorStatistics(codec_config_obus, descriptor_statistics));
if (descriptor_statistics_->common_samples_per_frame !=
descriptor_statistics.common_samples_per_frame ||
descriptor_statistics_->common_sample_rate !=
descriptor_statistics.common_sample_rate ||
descriptor_statistics_->common_bit_depth !=
descriptor_statistics.common_bit_depth ||
descriptor_statistics_->num_channels !=
descriptor_statistics.num_channels) {
return absl::FailedPreconditionError(
"Descriptor OBUs have changed size between finalizing and "
"closing.");
}
if (descriptor_statistics_->descriptor_obus.size() !=
descriptor_statistics.descriptor_obus.size()) {
return absl::UnimplementedError(
"Descriptor OBUs have changed size between finalizing and closing.");
}
RETURN_IF_NOT_OK(
PushFinalizedDescriptorObus(absl::MakeConstSpan(wb_.bit_buffer())));
state_ = kPushSerializedDescriptorsCalled;
RETURN_IF_NOT_OK(Close());
abort_on_destruct.CancelAbort();
return absl::OkStatus();
}
absl::Status ObuSequencerBase::Close() {
switch (state_) {
case kInitialized:
break;
case kPushDescriptorObusCalled: {
// Ok, trivial IA sequences don't have a first untrimmed timestamp. So
// we will simply push the descriptors with a fallback PTS of 0.
descriptor_statistics_->first_untrimmed_timestamp = kFallbackFirstPts;
RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
descriptor_statistics_->common_samples_per_frame,
descriptor_statistics_->common_sample_rate,
descriptor_statistics_->common_bit_depth,
descriptor_statistics_->first_untrimmed_timestamp,
descriptor_statistics_->num_channels,
descriptor_statistics_->descriptor_obus));
state_ = kPushSerializedDescriptorsCalled;
break;
}
case kPushSerializedDescriptorsCalled:
break;
case kClosed:
return absl::FailedPreconditionError(
"`Abort` or `Close` previously called.");
}
CloseDerived();
state_ = kClosed;
return absl::OkStatus();
}
void ObuSequencerBase::Abort() {
AbortDerived();
state_ = kClosed;
}
absl::Status ObuSequencerBase::HandleInitialTemporalUnits(
const TemporalUnitView& temporal_unit,
absl::Span<const uint8_t> serialized_temporal_unit) {
const bool found_first_untrimmed_sample =
temporal_unit.num_untrimmed_samples_ != 0;
if (found_first_untrimmed_sample) {
// Gather the PTS. For internal accuracy, we store this even if we don't
// need to delay the descriptors.
descriptor_statistics_->first_untrimmed_timestamp =
temporal_unit.start_timestamp_ +
temporal_unit.num_samples_to_trim_at_start_;
}
// Push immediately if we don't need to delay the descriptors.
if (!delay_descriptors_until_first_untrimmed_sample_) {
return PushSerializedTemporalUnit(temporal_unit.start_timestamp_,
temporal_unit.num_untrimmed_samples_,
serialized_temporal_unit);
}
if (!found_first_untrimmed_sample) {
// This frame is fully trimmed. Cache it for later.
delayed_temporal_units_.push_back(SerializedTemporalUnit{
.start_timestamp = temporal_unit.start_timestamp_,
.num_untrimmed_samples = temporal_unit.num_untrimmed_samples_,
.data = std::vector<uint8_t>(serialized_temporal_unit.begin(),
serialized_temporal_unit.end())});
return absl::OkStatus();
}
// Found the first untrimmed sample. Push out all delayed OBUs.
RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
descriptor_statistics_->common_samples_per_frame,
descriptor_statistics_->common_sample_rate,
descriptor_statistics_->common_bit_depth,
descriptor_statistics_->first_untrimmed_timestamp,
descriptor_statistics_->num_channels,
descriptor_statistics_->descriptor_obus));
state_ = kPushSerializedDescriptorsCalled;
// Flush any delayed temporal units.
for (const auto& delayed_temporal_unit : delayed_temporal_units_) {
RETURN_IF_NOT_OK(PushSerializedTemporalUnit(
delayed_temporal_unit.start_timestamp,
delayed_temporal_unit.num_untrimmed_samples,
absl::MakeConstSpan(delayed_temporal_unit.data)));
}
delayed_temporal_units_.clear();
// Then finally, flush the current temporal unit.
return PushSerializedTemporalUnit(temporal_unit.start_timestamp_,
temporal_unit.num_untrimmed_samples_,
serialized_temporal_unit);
}
} // namespace iamf_tools