blob: a653366096269b2a84e1edd1aaec467f478b7bc9 [file] [log] [blame]
/*
* Copyright (c) 2024, 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 <cstddef>
#include <optional>
#include <string>
#include <vector>
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "iamf/cli/channel_label.h"
#include "iamf/cli/demixing_module.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/map_utils.h"
#include "iamf/obu/mix_presentation.h"
#include "iamf/obu/types.h"
namespace iamf_tools {
namespace renderer_utils {
namespace {
// Returns the common number of time ticks to be rendered for the requested
// labels or associated demixed label in `labeled_frame`. This represents the
// number of time ticks in the rendered audio after trimming.
absl::StatusOr<size_t> GetCommonNumTrimmedTimeTicks(
const LabeledFrame& labeled_frame,
const std::vector<ChannelLabel::Label>& ordered_labels) {
std::optional<size_t> num_raw_time_ticks;
for (const auto& label : ordered_labels) {
if (label == ChannelLabel::kOmitted) {
continue;
}
const std::vector<InternalSampleType>* samples_to_render = nullptr;
RETURN_IF_NOT_OK(DemixingModule::FindSamplesOrDemixedSamples(
label, labeled_frame.label_to_samples, &samples_to_render));
if (samples_to_render == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Label ", label, " or D_", label, " not found."));
} else if (!num_raw_time_ticks.has_value()) {
num_raw_time_ticks = samples_to_render->size();
} else if (*num_raw_time_ticks != samples_to_render->size()) {
return absl::InvalidArgumentError(absl::StrCat(
"All labels must have the same number of samples ", label, " (",
samples_to_render->size(), " vs. ", *num_raw_time_ticks, ")"));
}
}
if (*num_raw_time_ticks < (labeled_frame.samples_to_trim_at_start +
labeled_frame.samples_to_trim_at_end)) {
return absl::InvalidArgumentError(absl::StrCat(
"Not enough samples to render samples",
". #Raw samples: ", *num_raw_time_ticks,
", samples to trim at start: ", labeled_frame.samples_to_trim_at_start,
", samples to trim at end: ", labeled_frame.samples_to_trim_at_end));
}
return *num_raw_time_ticks - labeled_frame.samples_to_trim_at_start -
labeled_frame.samples_to_trim_at_end;
}
} // namespace
absl::Status ArrangeSamplesToRender(
const LabeledFrame& labeled_frame,
const std::vector<ChannelLabel::Label>& ordered_labels,
std::vector<std::vector<InternalSampleType>>& samples_to_render,
size_t& num_valid_samples) {
if (ordered_labels.empty()) {
return absl::OkStatus();
}
const auto common_num_trimmed_time_ticks =
GetCommonNumTrimmedTimeTicks(labeled_frame, ordered_labels);
if (!common_num_trimmed_time_ticks.ok()) {
return common_num_trimmed_time_ticks.status();
}
num_valid_samples = *common_num_trimmed_time_ticks;
const auto num_channels = ordered_labels.size();
if (num_valid_samples > samples_to_render.size()) [[unlikely]] {
return absl::InvalidArgumentError(absl::StrCat(
"Number of time samples to render= ", num_valid_samples,
"does not fit into the output of size ", samples_to_render.size()));
}
for (int channel = 0; channel < num_channels; ++channel) {
const auto& channel_label = ordered_labels[channel];
if (channel_label == ChannelLabel::kOmitted) {
// Missing channels for mixed-order ambisonics representation will not be
// updated and will thus have the initialized zeros.
continue;
}
const std::vector<InternalSampleType>* channel_samples = nullptr;
RETURN_IF_NOT_OK(DemixingModule::FindSamplesOrDemixedSamples(
channel_label, labeled_frame.label_to_samples, &channel_samples));
// The lookup should not fail because its presence was already checked in
// `GetCommonNumTrimmedTimeTicks`.
CHECK_NE(channel_samples, nullptr);
// Grab the entire time axes for this label, Skip over any samples that
// should be trimmed.
for (int time = 0; time < num_valid_samples; ++time) {
samples_to_render[time][channel] =
(*channel_samples)[time + labeled_frame.samples_to_trim_at_start];
}
}
return absl::OkStatus();
}
absl::StatusOr<std::string> LookupOutputKeyFromPlaybackLayout(
const Layout& output_layout) {
switch (output_layout.layout_type) {
using enum Layout::LayoutType;
case kLayoutTypeLoudspeakersSsConvention: {
auto sound_system = std::get<LoudspeakersSsConventionLayout>(
output_layout.specific_layout)
.sound_system;
using enum LoudspeakersSsConventionLayout::SoundSystem;
static const absl::NoDestructor<absl::flat_hash_map<
LoudspeakersSsConventionLayout::SoundSystem, std::string>>
kSoundSystemToOutputKey({
{kSoundSystemA_0_2_0, "0+2+0"},
{kSoundSystemB_0_5_0, "0+5+0"},
{kSoundSystemC_2_5_0, "2+5+0"},
{kSoundSystemD_4_5_0, "4+5+0"},
{kSoundSystemE_4_5_1, "4+5+1"},
{kSoundSystemF_3_7_0, "3+7+0"},
{kSoundSystemG_4_9_0, "4+9+0"},
{kSoundSystemH_9_10_3, "9+10+3"},
{kSoundSystemI_0_7_0, "0+7+0"},
{kSoundSystemJ_4_7_0, "4+7+0"},
{kSoundSystem10_2_7_0, "7.1.2"},
{kSoundSystem11_2_3_0, "3.1.2"},
{kSoundSystem12_0_1_0, "0+1+0"},
{kSoundSystem13_6_9_0, "9.1.6"},
});
return LookupInMap(*kSoundSystemToOutputKey, sound_system,
"Output key for `SoundSystem`");
}
case kLayoutTypeBinaural:
return absl::UnimplementedError(
"Loudness layout key for BINAURAL not supported "
"yet.");
case kLayoutTypeReserved0:
case kLayoutTypeReserved1:
default:
return absl::UnimplementedError(
absl::StrCat("Unsupported layout_type= ", output_layout.layout_type));
}
return absl::OkStatus();
}
} // namespace renderer_utils
} // namespace iamf_tools