blob: 3d72e5a422e4df55458453c9108077427f1fc529 [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/tests/cli_test_utils.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <ios>
#include <limits>
#include <list>
#include <memory>
#include <numbers>
#include <numeric>
#include <optional>
#include <string>
#include <system_error>
#include <utility>
#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/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "iamf/cli/audio_element_with_data.h"
#include "iamf/cli/audio_frame_with_data.h"
#include "iamf/cli/demixing_module.h"
#include "iamf/cli/obu_processor.h"
#include "iamf/cli/obu_with_data_generator.h"
#include "iamf/cli/parameter_block_with_data.h"
#include "iamf/cli/proto/mix_presentation.pb.h"
#include "iamf/cli/proto/user_metadata.pb.h"
#include "iamf/cli/proto_conversion/proto_to_obu/audio_element_generator.h"
#include "iamf/cli/proto_conversion/proto_to_obu/mix_presentation_generator.h"
#include "iamf/cli/renderer/audio_element_renderer_base.h"
#include "iamf/cli/user_metadata_builder/audio_element_metadata_builder.h"
#include "iamf/cli/user_metadata_builder/iamf_input_layout.h"
#include "iamf/cli/wav_reader.h"
#include "iamf/common/leb_generator.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/audio_element.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/decoder_config/aac_decoder_config.h"
#include "iamf/obu/decoder_config/flac_decoder_config.h"
#include "iamf/obu/decoder_config/lpcm_decoder_config.h"
#include "iamf/obu/decoder_config/opus_decoder_config.h"
#include "iamf/obu/demixing_info_parameter_data.h"
#include "iamf/obu/demixing_param_definition.h"
#include "iamf/obu/ia_sequence_header.h"
#include "iamf/obu/mix_presentation.h"
#include "iamf/obu/obu_base.h"
#include "iamf/obu/obu_header.h"
#include "iamf/obu/param_definitions.h"
#include "iamf/obu/types.h"
#include "src/google/protobuf/io/zero_copy_stream_impl.h"
#include "src/google/protobuf/repeated_ptr_field.h"
#include "src/google/protobuf/text_format.h"
namespace iamf_tools {
namespace {
constexpr bool kOverrideAudioRollDistance = true;
void SetParamDefinitionCommonFields(DecodedUleb128 parameter_id,
DecodedUleb128 parameter_rate,
DecodedUleb128 duration,
ParamDefinition& param_definition) {
param_definition.parameter_id_ = parameter_id;
param_definition.parameter_rate_ = parameter_rate;
param_definition.param_definition_mode_ = 0;
param_definition.reserved_ = 0;
param_definition.duration_ = duration;
param_definition.constant_subblock_duration_ = duration;
}
template <typename ParamDefinitionType>
void AddParamDefinition(DecodedUleb128 parameter_id,
DecodedUleb128 parameter_rate, DecodedUleb128 duration,
AudioElementObu& audio_element_obu,
ParamDefinitionType& param_definition) {
SetParamDefinitionCommonFields(parameter_id, parameter_rate, duration,
param_definition);
// Add to the Audio Element OBU.
audio_element_obu.InitializeParams(audio_element_obu.num_parameters_ + 1);
audio_element_obu.audio_element_params_.emplace_back(
AudioElementParam{param_definition});
}
} // namespace
using ::absl_testing::IsOk;
absl::Status CollectObusFromIaSequence(
ReadBitBuffer& read_bit_buffer, IASequenceHeaderObu& ia_sequence_header,
absl::flat_hash_map<DecodedUleb128, CodecConfigObu>& codec_config_obus,
absl::flat_hash_map<DecodedUleb128, AudioElementWithData>& audio_elements,
std::list<MixPresentationObu>& mix_presentations,
std::list<AudioFrameWithData>& audio_frames,
std::list<ParameterBlockWithData>& parameter_blocks) {
bool insufficient_data = false;
auto obu_processor = ObuProcessor::Create(
/*is_exhaustive_and_exact=*/false, &read_bit_buffer, insufficient_data);
EXPECT_FALSE(insufficient_data);
bool continue_processing = true;
int temporal_unit_count = 0;
LOG(INFO) << "Starting Temporal Unit OBU processing";
while (continue_processing) {
std::optional<ObuProcessor::OutputTemporalUnit> output_temporal_unit;
RETURN_IF_NOT_OK(obu_processor->ProcessTemporalUnit(
/*eos_is_end_of_sequence=*/true, output_temporal_unit,
continue_processing));
audio_frames.splice(audio_frames.end(),
output_temporal_unit->output_audio_frames);
parameter_blocks.splice(parameter_blocks.end(),
output_temporal_unit->output_parameter_blocks);
temporal_unit_count++;
}
LOG(INFO) << "Processed " << temporal_unit_count << " Temporal Unit OBUs";
// Move the processed data to the output.
ia_sequence_header = obu_processor->ia_sequence_header_;
codec_config_obus.swap(obu_processor->codec_config_obus_);
audio_elements.swap(obu_processor->audio_elements_);
mix_presentations.swap(obu_processor->mix_presentations_);
return absl::OkStatus();
}
void AddLpcmCodecConfig(
DecodedUleb128 codec_config_id, uint32_t num_samples_per_frame,
uint8_t sample_size, uint32_t sample_rate,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Initialize the Codec Config OBU.
ASSERT_EQ(codec_config_obus.find(codec_config_id), codec_config_obus.end());
CodecConfigObu obu(
ObuHeader(), codec_config_id,
{.codec_id = CodecConfig::kCodecIdLpcm,
.num_samples_per_frame = num_samples_per_frame,
.decoder_config = LpcmDecoderConfig{
.sample_format_flags_bitmask_ = LpcmDecoderConfig::kLpcmLittleEndian,
.sample_size_ = sample_size,
.sample_rate_ = sample_rate}});
EXPECT_THAT(obu.Initialize(kOverrideAudioRollDistance), IsOk());
codec_config_obus.emplace(codec_config_id, std::move(obu));
}
void AddLpcmCodecConfigWithIdAndSampleRate(
uint32_t codec_config_id, uint32_t sample_rate,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Many tests either don't care about the details. Or assumed these "default"
// values.
constexpr uint32_t kNumSamplesPerFrame = 8;
constexpr uint8_t kSampleSize = 16;
return AddLpcmCodecConfig(codec_config_id, kNumSamplesPerFrame, kSampleSize,
sample_rate, codec_config_obus);
}
void AddOpusCodecConfigWithId(
uint32_t codec_config_id,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Initialize the Codec Config OBU.
ASSERT_EQ(codec_config_obus.find(codec_config_id), codec_config_obus.end());
CodecConfigObu obu(
ObuHeader(), codec_config_id,
{.codec_id = CodecConfig::kCodecIdOpus,
.num_samples_per_frame = 8,
.decoder_config = OpusDecoderConfig{
.version_ = 1, .pre_skip_ = 312, .input_sample_rate_ = 0}});
ASSERT_THAT(obu.Initialize(kOverrideAudioRollDistance), IsOk());
codec_config_obus.emplace(codec_config_id, std::move(obu));
}
void AddFlacCodecConfigWithId(
uint32_t codec_config_id,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Initialize the Codec Config OBU.
ASSERT_EQ(codec_config_obus.find(codec_config_id), codec_config_obus.end());
CodecConfigObu obu(
ObuHeader(), codec_config_id,
{.codec_id = CodecConfig::kCodecIdFlac,
.num_samples_per_frame = 16,
.decoder_config = FlacDecoderConfig(
{{{.header = {.last_metadata_block_flag = true,
.block_type = FlacMetaBlockHeader::kFlacStreamInfo,
.metadata_data_block_length = 34},
.payload =
FlacMetaBlockStreamInfo{.minimum_block_size = 16,
.maximum_block_size = 16,
.sample_rate = 48000,
.bits_per_sample = 15,
.total_samples_in_stream = 0}}}})});
ASSERT_THAT(obu.Initialize(kOverrideAudioRollDistance), IsOk());
codec_config_obus.emplace(codec_config_id, std::move(obu));
}
void AddAacCodecConfigWithId(
uint32_t codec_config_id,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Initialize the Codec Config OBU.
ASSERT_EQ(codec_config_obus.find(codec_config_id), codec_config_obus.end());
CodecConfigObu obu(ObuHeader(), codec_config_id,
{.codec_id = CodecConfig::kCodecIdAacLc,
.num_samples_per_frame = 1024,
.decoder_config = AacDecoderConfig{}});
ASSERT_THAT(obu.Initialize(kOverrideAudioRollDistance), IsOk());
codec_config_obus.emplace(codec_config_id, std::move(obu));
}
void AddAmbisonicsMonoAudioElementWithSubstreamIds(
DecodedUleb128 audio_element_id, uint32_t codec_config_id,
absl::Span<const DecodedUleb128> substream_ids,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
absl::flat_hash_map<DecodedUleb128, AudioElementWithData>& audio_elements) {
// Check the `codec_config_id` is known and this is a new
// `audio_element_id`.
auto codec_config_iter = codec_config_obus.find(codec_config_id);
ASSERT_NE(codec_config_iter, codec_config_obus.end());
ASSERT_EQ(audio_elements.find(audio_element_id), audio_elements.end());
// Initialize the Audio Element OBU without any parameters.
AudioElementObu obu = AudioElementObu(
ObuHeader(), audio_element_id, AudioElementObu::kAudioElementSceneBased,
0, codec_config_id);
obu.InitializeParams(0);
obu.InitializeAudioSubstreams(substream_ids.size());
obu.audio_substream_ids_.assign(substream_ids.begin(), substream_ids.end());
// Initialize to n-th order ambisonics. Choose the lowest order that can fit
// all `substream_ids`. This may result in mixed-order ambisonics.
uint8_t next_valid_output_channel_count;
ASSERT_THAT(AmbisonicsConfig::GetNextValidOutputChannelCount(
substream_ids.size(), next_valid_output_channel_count),
IsOk());
EXPECT_THAT(obu.InitializeAmbisonicsMono(next_valid_output_channel_count,
substream_ids.size()),
IsOk());
auto& channel_mapping =
std::get<AmbisonicsMonoConfig>(
std::get<AmbisonicsConfig>(obu.config_).ambisonics_config)
.channel_mapping;
// Map the first n channels from [0, n] in input order. Leave the rest of
// the channels as unmapped.
std::fill(channel_mapping.begin(), channel_mapping.end(),
AmbisonicsMonoConfig::kInactiveAmbisonicsChannelNumber);
std::iota(channel_mapping.begin(),
channel_mapping.begin() + substream_ids.size(), 0);
AudioElementWithData audio_element = {
.obu = std::move(obu), .codec_config = &codec_config_iter->second};
ASSERT_THAT(ObuWithDataGenerator::FinalizeAmbisonicsConfig(
audio_element.obu, audio_element.substream_id_to_labels),
IsOk());
audio_elements.emplace(audio_element_id, std::move(audio_element));
}
// Adds a scalable Audio Element OBU based on the input arguments.
void AddScalableAudioElementWithSubstreamIds(
IamfInputLayout input_layout, DecodedUleb128 audio_element_id,
uint32_t codec_config_id, absl::Span<const DecodedUleb128> substream_ids,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
absl::flat_hash_map<DecodedUleb128, AudioElementWithData>& audio_elements) {
google::protobuf::RepeatedPtrField<
iamf_tools_cli_proto::AudioElementObuMetadata>
audio_element_metadatas;
AudioElementMetadataBuilder builder;
auto& new_audio_element_metadata = *audio_element_metadatas.Add();
ASSERT_THAT(builder.PopulateAudioElementMetadata(
audio_element_id, codec_config_id, input_layout,
new_audio_element_metadata),
IsOk());
// Check that this is a scalable Audio Element, and override the substream
// IDs.
ASSERT_TRUE(new_audio_element_metadata.has_scalable_channel_layout_config());
ASSERT_EQ(new_audio_element_metadata.num_substreams(), substream_ids.size());
for (int i = 0; i < substream_ids.size(); ++i) {
new_audio_element_metadata.mutable_audio_substream_ids()->Set(
i, substream_ids[i]);
}
// Generate the Audio Element OBU.
AudioElementGenerator generator(audio_element_metadatas);
ASSERT_THAT(generator.Generate(codec_config_obus, audio_elements), IsOk());
}
void AddMixPresentationObuWithAudioElementIds(
DecodedUleb128 mix_presentation_id,
const std::vector<DecodedUleb128>& audio_element_ids,
DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
std::list<MixPresentationObu>& mix_presentations) {
// Configure one of the simplest mix presentation. Mix presentations REQUIRE
// at least one sub-mix and a stereo layout.
AddMixPresentationObuWithConfigurableLayouts(
mix_presentation_id, audio_element_ids, common_parameter_id,
common_parameter_rate,
{LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0}, mix_presentations);
}
void AddMixPresentationObuWithConfigurableLayouts(
DecodedUleb128 mix_presentation_id,
const std::vector<DecodedUleb128>& audio_element_ids,
DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
const std::vector<LoudspeakersSsConventionLayout::SoundSystem>&
sound_system_layouts,
std::list<MixPresentationObu>& mix_presentations) {
MixGainParamDefinition common_mix_gain_param_definition;
common_mix_gain_param_definition.parameter_id_ = common_parameter_id;
common_mix_gain_param_definition.parameter_rate_ = common_parameter_rate;
common_mix_gain_param_definition.param_definition_mode_ = true;
common_mix_gain_param_definition.default_mix_gain_ = 0;
std::vector<MixPresentationLayout> layouts;
for (const auto& sound_system : sound_system_layouts) {
layouts.push_back(
{.loudness_layout = {.layout_type =
Layout::kLayoutTypeLoudspeakersSsConvention,
.specific_layout =
LoudspeakersSsConventionLayout{
.sound_system = sound_system,
.reserved = 0}},
.loudness = {
.info_type = 0, .integrated_loudness = 0, .digital_peak = 0}});
}
std::vector<MixPresentationSubMix> sub_mixes = {
{.output_mix_gain = common_mix_gain_param_definition,
.layouts = layouts}};
for (const auto& audio_element_id : audio_element_ids) {
sub_mixes[0].audio_elements.push_back({
.audio_element_id = audio_element_id,
.localized_element_annotations = {},
.rendering_config =
{.headphones_rendering_mode =
RenderingConfig::kHeadphonesRenderingModeStereo,
.reserved = 0,
.rendering_config_extension_size = 0,
.rendering_config_extension_bytes = {}},
.element_mix_gain = common_mix_gain_param_definition,
});
}
mix_presentations.push_back(
MixPresentationObu(ObuHeader(), mix_presentation_id,
/*count_label=*/0, {}, {}, sub_mixes));
}
void AddParamDefinitionWithMode0AndOneSubblock(
DecodedUleb128 parameter_id, DecodedUleb128 parameter_rate,
DecodedUleb128 duration,
absl::flat_hash_map<DecodedUleb128, MixGainParamDefinition>&
param_definitions) {
MixGainParamDefinition param_definition;
SetParamDefinitionCommonFields(parameter_id, parameter_rate, duration,
param_definition);
param_definitions.emplace(parameter_id, param_definition);
}
void AddDemixingParamDefinition(DecodedUleb128 parameter_id,
DecodedUleb128 parameter_rate,
DecodedUleb128 duration,
AudioElementObu& audio_element_obu) {
DemixingParamDefinition param_definition;
// Specific fields of demixing param definitions.
param_definition.default_demixing_info_parameter_data_.dmixp_mode =
DemixingInfoParameterData::kDMixPMode1;
param_definition.default_demixing_info_parameter_data_.reserved = 0;
param_definition.default_demixing_info_parameter_data_.default_w = 10;
param_definition.default_demixing_info_parameter_data_
.reserved_for_future_use = 0;
AddParamDefinition(parameter_id, parameter_rate, duration, audio_element_obu,
param_definition);
}
void AddReconGainParamDefinition(DecodedUleb128 parameter_id,
DecodedUleb128 parameter_rate,
DecodedUleb128 duration,
AudioElementObu& audio_element_obu) {
ReconGainParamDefinition param_definition(
audio_element_obu.GetAudioElementId());
AddParamDefinition(parameter_id, parameter_rate, duration, audio_element_obu,
param_definition);
}
WavReader CreateWavReaderExpectOk(const std::string& filename,
int num_samples_per_frame) {
auto wav_reader = WavReader::CreateFromFile(filename, num_samples_per_frame);
EXPECT_THAT(wav_reader, IsOk());
return std::move(*wav_reader);
}
void RenderAndFlushExpectOk(const LabeledFrame& labeled_frame,
AudioElementRendererBase* renderer,
std::vector<InternalSampleType>& output_samples) {
ASSERT_NE(renderer, nullptr);
EXPECT_THAT(renderer->RenderLabeledFrame(labeled_frame), IsOk());
EXPECT_THAT(renderer->Finalize(), IsOk());
EXPECT_TRUE(renderer->IsFinalized());
EXPECT_THAT(renderer->Flush(output_samples), IsOk());
}
std::string GetAndCleanupOutputFileName(absl::string_view suffix) {
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
std::string filename = absl::StrCat(test_info->name(), "-",
test_info->test_suite_name(), suffix);
// It is possible that the test suite name contains the '/' character.
// Replace it with '-' to form a legal file name.
absl::StrReplaceAll({{"/", "-"}}, &filename);
const std::filesystem::path test_specific_filename =
std::filesystem::path(::testing::TempDir()) / filename;
std::filesystem::remove(test_specific_filename);
return test_specific_filename.string();
}
std::string GetAndCreateOutputDirectory(absl::string_view suffix) {
const std::string output_directory = GetAndCleanupOutputFileName(suffix);
std::error_code error_code;
EXPECT_TRUE(
std::filesystem::create_directories(output_directory, error_code));
return output_directory;
}
std::vector<uint8_t> SerializeObusExpectOk(
const std::list<const ObuBase*>& obus, const LebGenerator& leb_generator) {
using ::absl_testing::IsOk;
WriteBitBuffer serialized_obus(0, leb_generator);
for (const auto* obu : obus) {
EXPECT_NE(obu, nullptr);
EXPECT_THAT(obu->ValidateAndWriteObu(serialized_obus), IsOk());
}
return serialized_obus.bit_buffer();
}
void ParseUserMetadataAssertSuccess(
const std::string& textproto_filename,
iamf_tools_cli_proto::UserMetadata& user_metadata) {
ASSERT_TRUE(std::filesystem::exists(textproto_filename));
std::ifstream user_metadata_file(textproto_filename, std::ios::in);
google::protobuf::io::IstreamInputStream input_stream(&user_metadata_file);
ASSERT_TRUE(
google::protobuf::TextFormat::Parse(&input_stream, &user_metadata));
}
double GetLogSpectralDistance(
const absl::Span<const InternalSampleType>& first_log_spectrum,
const absl::Span<const InternalSampleType>& second_log_spectrum) {
const int num_samples = first_log_spectrum.size();
if (num_samples != second_log_spectrum.size()) {
LOG(ERROR) << "Spectrum sizes are not equal.";
return false;
}
double log_spectral_distance = 0.0;
for (int i = 0; i < num_samples; ++i) {
log_spectral_distance += (first_log_spectrum[i] - second_log_spectrum[i]) *
(first_log_spectrum[i] - second_log_spectrum[i]);
}
return (10 * std::sqrt(log_spectral_distance / num_samples));
}
std::vector<DecodeSpecification> GetDecodeSpecifications(
const iamf_tools_cli_proto::UserMetadata& user_metadata) {
std::vector<DecodeSpecification> decode_specifications;
for (const auto& mix_presentation :
user_metadata.mix_presentation_metadata()) {
for (int i = 0; i < mix_presentation.sub_mixes_size(); ++i) {
for (int j = 0; j < mix_presentation.sub_mixes(i).layouts_size(); ++j) {
DecodeSpecification decode_specification;
decode_specification.mix_presentation_id =
mix_presentation.mix_presentation_id();
decode_specification.sub_mix_index = i;
if (mix_presentation.sub_mixes(i)
.layouts(j)
.loudness_layout()
.has_ss_layout()) {
auto sound_system_status = MixPresentationGenerator::CopySoundSystem(
mix_presentation.sub_mixes(i)
.layouts(j)
.loudness_layout()
.ss_layout()
.sound_system(),
decode_specification.sound_system);
if (!sound_system_status.ok()) {
LOG(ERROR) << "Failed to copy sound system: "
<< sound_system_status;
continue;
}
}
decode_specification.layout_index = j;
decode_specifications.push_back(decode_specification);
}
}
}
return decode_specifications;
}
std::vector<InternalSampleType> Int32ToInternalSampleType(
absl::Span<const int32_t> samples) {
std::vector<InternalSampleType> result(samples.size());
Int32ToInternalSampleType(samples, absl::MakeSpan(result));
return result;
}
std::vector<InternalSampleType> GenerateSineWav(uint64_t start_tick,
uint32_t num_samples,
uint32_t sample_rate_hz,
double frequency_hz,
double amplitude) {
std::vector<InternalSampleType> samples(num_samples, 0.0);
constexpr double kPi = std::numbers::pi_v<InternalSampleType>;
const double time_base = 1.0 / sample_rate_hz;
for (int frame_tick = 0; frame_tick < num_samples; ++frame_tick) {
const double t = start_tick + frame_tick;
samples[frame_tick] =
amplitude * sin(2.0 * kPi * frequency_hz * t * time_base);
}
return samples;
}
void AccumulateZeroCrossings(
absl::Span<const std::vector<int32_t>> samples,
std::vector<ZeroCrossingState>& zero_crossing_states,
std::vector<int>& zero_crossing_counts) {
using enum ZeroCrossingState;
const auto num_channels = samples.empty() ? 0 : samples[0].size();
// Seed the data structures, or check they contain the right number of
// channels.
if (zero_crossing_counts.empty()) {
zero_crossing_counts.resize(num_channels, 0);
} else {
ASSERT_EQ(num_channels, zero_crossing_counts.size());
}
if (zero_crossing_states.empty()) {
zero_crossing_states.resize(num_channels, ZeroCrossingState::kUnknown);
} else {
ASSERT_EQ(num_channels, zero_crossing_states.size());
}
// Zero crossing threshold determined empirically for -18 dB sine waves to
// skip encoding artifacts (e.g. a small ringing artifact < -40 dB after
// the sine wave stopped.) Note that -18 dB would correspond to dividing
// by 8, while dividing by 100 is -40 dB.
constexpr int32_t kThreshold = std::numeric_limits<int32_t>::max() / 100;
for (const auto& tick : samples) {
ASSERT_EQ(tick.size(), num_channels);
for (int i = 0; i < num_channels; ++i) {
ZeroCrossingState next_state = (tick[i] > kThreshold) ? kPositive
: (tick[i] < -kThreshold) ? kNegative
: kUnknown;
if (next_state == kUnknown) {
// Don't do anything if it's not clearly positive or negative.
continue;
} else if (zero_crossing_states[i] != next_state) {
// If we clearly flipped states, count it as a zero crossing.
zero_crossing_counts[i]++;
zero_crossing_states[i] = next_state;
}
}
}
}
absl::Status ReadFileToBytes(const std::filesystem::path& file_path,
std::vector<uint8_t>& buffer) {
if (!std::filesystem::exists(file_path)) {
return absl::NotFoundError("File not found.");
}
std::ifstream ifs(file_path, std::ios::binary | std::ios::in);
// Increase the size of the buffer. Write to the original end (before
// resizing).
const auto file_size = std::filesystem::file_size(file_path);
const auto original_buffer_size = buffer.size();
buffer.resize(original_buffer_size + file_size);
ifs.read(reinterpret_cast<char*>(buffer.data() + original_buffer_size),
file_size);
return absl::OkStatus();
}
absl::Status EverySecondTickResampler::PushFrameDerived(
absl::Span<const std::vector<int32_t>> time_channel_samples) {
EXPECT_EQ(num_valid_ticks_, 0); // `SampleProcessorBase` should ensure this.
for (size_t i = 0; i < time_channel_samples.size(); ++i) {
if (i % 2 == 1) {
output_time_channel_samples_[num_valid_ticks_] = time_channel_samples[i];
++num_valid_ticks_;
}
}
return absl::OkStatus();
}
absl::Status EverySecondTickResampler::FlushDerived() {
EXPECT_EQ(num_valid_ticks_, 0); // `SampleProcessorBase` should ensure this.
return absl::OkStatus();
}
absl::Status OneFrameDelayer::PushFrameDerived(
absl::Span<const std::vector<int32_t>> time_channel_samples) {
// Swap the delayed samples with the output samples from the base class.
std::swap(delayed_samples_, output_time_channel_samples_);
std::swap(num_delayed_ticks_, num_valid_ticks_);
// The fact that the input size is less than the output size should have
// already been validated in `SampleProcessorBase`, but for safety we can
// check it here.
EXPECT_LE(time_channel_samples.size(), delayed_samples_.size());
// Cache the new samples to delay.
std::copy(time_channel_samples.begin(), time_channel_samples.end(),
delayed_samples_.begin());
num_delayed_ticks_ = time_channel_samples.size();
return absl::OkStatus();
}
absl::Status OneFrameDelayer::FlushDerived() {
// Pushing in an empty frame will cause the delayed frame to be available.
return PushFrameDerived({});
}
} // namespace iamf_tools