| /* |
| * 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 "iamf/cli/channel_label.h" |
| |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/base/no_destructor.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "iamf/common/utils/macros.h" |
| #include "iamf/common/utils/map_utils.h" |
| #include "iamf/common/utils/validation_utils.h" |
| #include "iamf/obu/audio_element.h" |
| #include "iamf/obu/recon_gain_info_parameter_data.h" |
| |
| namespace iamf_tools { |
| |
| namespace { |
| |
| absl::StatusOr<std::vector<ChannelLabel::Label>> |
| LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| const ChannelAudioLayerConfig::LoudspeakerLayout& loudspeaker_layout) { |
| using enum ChannelAudioLayerConfig::LoudspeakerLayout; |
| using enum ChannelLabel::Label; |
| static const absl::NoDestructor< |
| absl::flat_hash_map<ChannelAudioLayerConfig::LoudspeakerLayout, |
| std::vector<ChannelLabel::Label>>> |
| kSoundSystemToLoudspeakerLayout({ |
| {kLayoutMono, {kMono}}, |
| {kLayoutStereo, {kL2, kR2}}, |
| {kLayout5_1_ch, {kL5, kR5, kCentre, kLFE, kLs5, kRs5}}, |
| {kLayout5_1_2_ch, |
| {kL5, kR5, kCentre, kLFE, kLs5, kRs5, kLtf2, kRtf2}}, |
| {kLayout5_1_4_ch, |
| {kL5, kR5, kCentre, kLFE, kLs5, kRs5, kLtf4, kRtf4, kLtb4, kRtb4}}, |
| {kLayout7_1_ch, |
| {kL7, kR7, kCentre, kLFE, kLss7, kRss7, kLrs7, kRrs7}}, |
| {kLayout7_1_2_ch, |
| {kL7, kR7, kCentre, kLFE, kLss7, kRss7, kLrs7, kRrs7, kLtf2, kRtf2}}, |
| {kLayout7_1_4_ch, |
| {kL7, kR7, kCentre, kLFE, kLss7, kRss7, kLrs7, kRrs7, kLtf4, kRtf4, |
| kLtb4, kRtb4}}, |
| {kLayout3_1_2_ch, {kL3, kR3, kCentre, kLFE, kLtf3, kRtf3}}, |
| {kLayoutBinaural, {kL2, kR2}}, |
| }); |
| |
| return LookupInMap(*kSoundSystemToLoudspeakerLayout, loudspeaker_layout, |
| "`ChannelLabel::Label` for `LoudspeakerLayout`"); |
| } |
| |
| void SetLabelsToOmittedExceptFor( |
| const absl::flat_hash_set<ChannelLabel::Label>& labels_to_keep, |
| std::vector<ChannelLabel::Label>& ordered_labels) { |
| for (auto& label : ordered_labels) { |
| if (!labels_to_keep.contains(label)) { |
| label = ChannelLabel::kOmitted; |
| } |
| } |
| } |
| |
| absl::StatusOr<std::vector<ChannelLabel::Label>> |
| LookupEarChannelOrderFromExpandedLoudspeakerLayout( |
| const ChannelAudioLayerConfig::ExpandedLoudspeakerLayout& |
| expanded_loudspeaker_layout) { |
| using enum ChannelLabel::Label; |
| static const absl::NoDestructor<std::vector<ChannelLabel::Label>> |
| k9_1_6ChannelOrder(std::vector<ChannelLabel::Label>( |
| {kFL, kFR, kFC, kLFE, kBL, kBR, kFLc, kFRc, kSiL, kSiR, kTpFL, kTpFR, |
| kTpBL, kTpBR, kTpSiL, kTpSiR})); |
| // Determine the related layout and then omit any irrelevant channels. This |
| // ensures the permitted channels are in the same slot and allows downstream |
| // processing to use the related layout's EAR matrix. |
| absl::StatusOr<std::vector<ChannelLabel::Label>> related_labels; |
| absl::flat_hash_set<ChannelLabel::Label> labels_to_keep; |
| switch (expanded_loudspeaker_layout) { |
| using enum ChannelAudioLayerConfig::ExpandedLoudspeakerLayout; |
| using enum ChannelAudioLayerConfig::LoudspeakerLayout; |
| case kExpandedLayoutLFE: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLFE}; |
| break; |
| case kExpandedLayoutStereoS: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout5_1_4_ch); |
| labels_to_keep = {kLs5, kRs5}; |
| break; |
| case kExpandedLayoutStereoSS: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLss7, kRss7}; |
| break; |
| case kExpandedLayoutStereoRS: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLrs7, kRrs7}; |
| break; |
| case kExpandedLayoutStereoTF: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLtf4, kRtf4}; |
| break; |
| case kExpandedLayoutStereoTB: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLtb4, kRtb4}; |
| break; |
| case kExpandedLayoutTop4Ch: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kLtf4, kRtf4, kLtb4, kRtb4}; |
| break; |
| case kExpandedLayout3_0_ch: |
| related_labels = LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| kLayout7_1_4_ch); |
| labels_to_keep = {kL7, kR7, kCentre}; |
| break; |
| case kExpandedLayout9_1_6_ch: |
| return *k9_1_6ChannelOrder; |
| case kExpandedLayoutStereoF: |
| related_labels = *k9_1_6ChannelOrder; |
| labels_to_keep = {kFL, kFR}; |
| break; |
| case kExpandedLayoutStereoSi: |
| related_labels = *k9_1_6ChannelOrder; |
| labels_to_keep = {kSiL, kSiR}; |
| break; |
| case kExpandedLayoutStereoTpSi: |
| related_labels = *k9_1_6ChannelOrder; |
| labels_to_keep = {kTpSiL, kTpSiR}; |
| break; |
| case kExpandedLayoutTop6Ch: |
| related_labels = *k9_1_6ChannelOrder; |
| labels_to_keep = {kTpFL, kTpFR, kTpSiL, kTpSiR, kTpBL, kTpBR}; |
| break; |
| default: |
| return absl::InvalidArgumentError( |
| absl::StrCat("Reserved or unknown expanded_loudspeaker_layout= ", |
| expanded_loudspeaker_layout)); |
| } |
| |
| // Leave the labels to keep in their original slot, but filter out all other |
| // labels. |
| if (!related_labels.ok()) { |
| return related_labels.status(); |
| } |
| SetLabelsToOmittedExceptFor(labels_to_keep, *related_labels); |
| return related_labels; |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<ChannelLabel::Label> |
| ChannelLabel::AmbisonicsChannelNumberToLabel(int ambisonics_channel_number) { |
| return ChannelLabel::DeprecatedStringBasedLabelToLabel( |
| absl::StrCat("A", ambisonics_channel_number)); |
| } |
| |
| absl::StatusOr<ChannelLabel::Label> |
| ChannelLabel::DeprecatedStringBasedLabelToLabel(absl::string_view label) { |
| using enum ChannelLabel::Label; |
| static const absl::NoDestructor< |
| absl::flat_hash_map<absl::string_view, ChannelLabel::Label>> |
| kStringToChannelLabel({ |
| {"Omitted", kOmitted}, |
| {"M", kMono}, |
| {"L2", kL2}, |
| {"R2", kR2}, |
| {"DemixedR2", kDemixedR2}, |
| {"C", kCentre}, |
| {"LFE", kLFE}, |
| {"L3", kL3}, |
| {"R3", kR3}, |
| {"Rtf3", kRtf3}, |
| {"Ltf3", kLtf3}, |
| {"DemixedL3", kDemixedL3}, |
| {"DemixedR3", kDemixedR3}, |
| {"L5", kL5}, |
| {"R5", kR5}, |
| {"Ls5", kLs5}, |
| {"Rs5", kRs5}, |
| {"DemixedL5", kDemixedL5}, |
| {"DemixedR5", kDemixedR5}, |
| {"DemixedLs5", kDemixedLs5}, |
| {"DemixedRs5", kDemixedRs5}, |
| {"Ltf2", kLtf2}, |
| {"Rtf2", kRtf2}, |
| {"DemixedRtf2", kDemixedRtf2}, |
| {"DemixedLtf2", kDemixedLtf2}, |
| {"Ltf4", kLtf4}, |
| {"Rtf4", kRtf4}, |
| {"Ltb4", kLtb4}, |
| {"Rtb4", kRtb4}, |
| {"DemixedLtb4", kDemixedLtb4}, |
| {"DemixedRtb4", kDemixedRtb4}, |
| {"L7", kL7}, |
| {"R7", kR7}, |
| {"Lss7", kLss7}, |
| {"Rss7", kRss7}, |
| {"Lrs7", kLrs7}, |
| {"Rrs7", kRrs7}, |
| {"DemixedL7", kDemixedL7}, |
| {"DemixedR7", kDemixedR7}, |
| {"DemixedLrs7", kDemixedLrs7}, |
| {"DemixedRrs7", kDemixedRrs7}, |
| {"FLc", kFLc}, |
| {"FC", kFC}, |
| {"FRc", kFRc}, |
| {"FL", kFL}, |
| {"FR", kFR}, |
| {"SiL", kSiL}, |
| {"SiR", kSiR}, |
| {"BL", kBL}, |
| {"BR", kBR}, |
| {"TpFL", kTpFL}, |
| {"TpFR", kTpFR}, |
| {"TpSiL", kTpSiL}, |
| {"TpSiR", kTpSiR}, |
| {"TpBL", kTpBL}, |
| {"TpBR", kTpBR}, |
| {"A0", kA0}, |
| {"A1", kA1}, |
| {"A2", kA2}, |
| {"A3", kA3}, |
| {"A4", kA4}, |
| {"A5", kA5}, |
| {"A6", kA6}, |
| {"A7", kA7}, |
| {"A8", kA8}, |
| {"A9", kA9}, |
| {"A10", kA10}, |
| {"A11", kA11}, |
| {"A12", kA12}, |
| {"A13", kA13}, |
| {"A14", kA14}, |
| {"A15", kA15}, |
| {"A16", kA16}, |
| {"A17", kA17}, |
| {"A18", kA18}, |
| {"A19", kA19}, |
| {"A20", kA20}, |
| {"A21", kA21}, |
| {"A22", kA22}, |
| {"A23", kA23}, |
| {"A24", kA24}, |
| }); |
| |
| return LookupInMap(*kStringToChannelLabel, label, |
| "`Channel::Label` for string-based label"); |
| } |
| |
| std::string ChannelLabel::LabelToStringForDebugging(Label label) { |
| using enum ChannelLabel::Label; |
| switch (label) { |
| case kOmitted: |
| return "Omitted"; |
| case kMono: |
| return "M"; |
| case kL2: |
| return "L2"; |
| case kR2: |
| return "R2"; |
| case kDemixedR2: |
| return "DemixedR2"; |
| case kCentre: |
| return "C"; |
| case kLFE: |
| return "LFE"; |
| case kL3: |
| return "L3"; |
| case kR3: |
| return "R3"; |
| case kRtf3: |
| return "Rtf3"; |
| case kLtf3: |
| return "Ltf3"; |
| case kDemixedL3: |
| return "DemixedL3"; |
| case kDemixedR3: |
| return "DemixedR3"; |
| case kL5: |
| return "L5"; |
| case kR5: |
| return "R5"; |
| case kLs5: |
| return "Ls5"; |
| case kRs5: |
| return "Rs5"; |
| case kDemixedL5: |
| return "DemixedL5"; |
| case kDemixedR5: |
| return "DemixedR5"; |
| case kDemixedLs5: |
| return "DemixedLs5"; |
| case kDemixedRs5: |
| return "DemixedRs5"; |
| case kLtf2: |
| return "Ltf2"; |
| case kRtf2: |
| return "Rtf2"; |
| case kDemixedRtf2: |
| return "DemixedRtf2"; |
| case kDemixedLtf2: |
| return "DemixedLtf2"; |
| case kLtf4: |
| return "Ltf4"; |
| case kRtf4: |
| return "Rtf4"; |
| case kLtb4: |
| return "Ltb4"; |
| case kRtb4: |
| return "Rtb4"; |
| case kDemixedLtb4: |
| return "DemixedLtb4"; |
| case kDemixedRtb4: |
| return "DemixedRtb4"; |
| case kL7: |
| return "L7"; |
| case kR7: |
| return "R7"; |
| case kLss7: |
| return "Lss7"; |
| case kRss7: |
| return "Rss7"; |
| case kLrs7: |
| return "Lrs7"; |
| case kRrs7: |
| return "Rrs7"; |
| case kDemixedL7: |
| return "DemixedL7"; |
| case kDemixedR7: |
| return "DemixedR7"; |
| case kDemixedLrs7: |
| return "DemixedLrs7"; |
| case kDemixedRrs7: |
| return "DemixedRrs7"; |
| case kFLc: |
| return "FLc"; |
| case kFC: |
| return "FC"; |
| case kFRc: |
| return "FRc"; |
| case kFL: |
| return "FL"; |
| case kFR: |
| return "FR"; |
| case kSiL: |
| return "SiL"; |
| case kSiR: |
| return "SiR"; |
| case kBL: |
| return "BL"; |
| case kBR: |
| return "BR"; |
| case kTpFL: |
| return "TpFL"; |
| case kTpFR: |
| return "TpFR"; |
| case kTpSiL: |
| return "TpSiL"; |
| case kTpSiR: |
| return "TpSiR"; |
| case kTpBL: |
| return "TpBL"; |
| case kTpBR: |
| return "TpBR"; |
| case kA0: |
| return "A0"; |
| case kA1: |
| return "A1"; |
| case kA2: |
| return "A2"; |
| case kA3: |
| return "A3"; |
| case kA4: |
| return "A4"; |
| case kA5: |
| return "A5"; |
| case kA6: |
| return "A6"; |
| case kA7: |
| return "A7"; |
| case kA8: |
| return "A8"; |
| case kA9: |
| return "A9"; |
| case kA10: |
| return "A10"; |
| case kA11: |
| return "A11"; |
| case kA12: |
| return "A12"; |
| case kA13: |
| return "A13"; |
| case kA14: |
| return "A14"; |
| case kA15: |
| return "A15"; |
| case kA16: |
| return "A16"; |
| case kA17: |
| return "A17"; |
| case kA18: |
| return "A18"; |
| case kA19: |
| return "A19"; |
| case kA20: |
| return "A20"; |
| case kA21: |
| return "A21"; |
| case kA22: |
| return "A22"; |
| case kA23: |
| return "A23"; |
| case kA24: |
| return "A24"; |
| } |
| |
| // The above switch statement is exhaustive. |
| LOG(FATAL) << "Enum out of range."; |
| } |
| |
| absl::StatusOr<ChannelLabel::Label> ChannelLabel::GetDemixedLabel( |
| ChannelLabel::Label label) { |
| using enum ChannelLabel::Label; |
| static const absl::NoDestructor< |
| absl::flat_hash_map<ChannelLabel::Label, ChannelLabel::Label>> |
| kChannelLabelToDemixedLabel({{kR2, kDemixedR2}, |
| {kL3, kDemixedL3}, |
| {kR3, kDemixedR3}, |
| {kL5, kDemixedL5}, |
| {kR5, kDemixedR5}, |
| {kLs5, kDemixedLs5}, |
| {kRs5, kDemixedRs5}, |
| {kLtf2, kDemixedLtf2}, |
| {kRtf2, kDemixedRtf2}, |
| {kLtb4, kDemixedLtb4}, |
| {kRtb4, kDemixedRtb4}, |
| {kL7, kDemixedL7}, |
| {kR7, kDemixedR7}, |
| {kLrs7, kDemixedLrs7}, |
| {kRrs7, kDemixedRrs7}}); |
| return LookupInMap(*kChannelLabelToDemixedLabel, label, |
| "Demixed label for `ChannelLabel::Label`"); |
| } |
| |
| absl::StatusOr<std::vector<ChannelLabel::Label>> |
| ChannelLabel::LookupEarChannelOrderFromScalableLoudspeakerLayout( |
| ChannelAudioLayerConfig::LoudspeakerLayout loudspeaker_layout, |
| const std::optional<ChannelAudioLayerConfig::ExpandedLoudspeakerLayout>& |
| expanded_loudspeaker_layout) { |
| if (loudspeaker_layout == ChannelAudioLayerConfig::kLayoutExpanded) { |
| RETURN_IF_NOT_OK(ValidateHasValue(expanded_loudspeaker_layout, |
| "expanded_loudspeaker_layout")); |
| return LookupEarChannelOrderFromExpandedLoudspeakerLayout( |
| *expanded_loudspeaker_layout); |
| } else { |
| return LookupEarChannelOrderFromNonExpandedLoudspeakerLayout( |
| loudspeaker_layout); |
| } |
| } |
| |
| absl::StatusOr<absl::flat_hash_set<ChannelLabel::Label>> |
| ChannelLabel::LookupLabelsToReconstructFromScalableLoudspeakerLayout( |
| ChannelAudioLayerConfig::LoudspeakerLayout loudspeaker_layout, |
| const std::optional<ChannelAudioLayerConfig::ExpandedLoudspeakerLayout>& |
| expanded_loudspeaker_layout) { |
| if (loudspeaker_layout == ChannelAudioLayerConfig::kLayoutExpanded) { |
| RETURN_IF_NOT_OK(ValidateHasValue(expanded_loudspeaker_layout, |
| "expanded_loudspeaker_layout")); |
| // OK. Expanded layouts may only exist in a single-layer and thus never need |
| // to be reconstructed as of IAMF v1.1.0. |
| return absl::flat_hash_set<ChannelLabel::Label>{}; |
| } |
| // Reconstruct the highest layer. |
| const auto ordered_labels = |
| ChannelLabel::LookupEarChannelOrderFromScalableLoudspeakerLayout( |
| loudspeaker_layout, expanded_loudspeaker_layout); |
| if (!ordered_labels.ok()) { |
| return ordered_labels.status(); |
| } else { |
| return absl::flat_hash_set<ChannelLabel::Label>(ordered_labels->begin(), |
| ordered_labels->end()); |
| } |
| } |
| |
| absl::StatusOr<ChannelLabel::Label> |
| ChannelLabel::GetDemixedChannelLabelForReconGain( |
| const ChannelAudioLayerConfig::LoudspeakerLayout& layout, |
| const ReconGainElement::ReconGainFlagBitmask& flag) { |
| switch (flag) { |
| using enum ReconGainElement::ReconGainFlagBitmask; |
| using enum ChannelLabel::Label; |
| using enum ChannelAudioLayerConfig::LoudspeakerLayout; |
| case kReconGainFlagL: |
| if (layout == kLayout5_1_ch || layout == kLayout5_1_2_ch || |
| layout == kLayout5_1_4_ch) { |
| return kDemixedL5; |
| } else if (layout == kLayout7_1_ch || layout == kLayout7_1_2_ch || |
| layout == kLayout7_1_4_ch) { |
| return kDemixedL7; |
| } else if (layout == kLayout3_1_2_ch) { |
| return kDemixedL3; |
| } else { |
| LOG(WARNING) |
| << "Unexpected recon gain flag. No corresponding channel label."; |
| return absl::InvalidArgumentError("Unexpected recon gain flag."); |
| } |
| case kReconGainFlagC: |
| LOG(WARNING) |
| << "Unexpected recon gain flag. No corresponding channel label."; |
| return absl::InvalidArgumentError("Unexpected recon gain flag."); |
| case kReconGainFlagR: |
| if (layout == kLayoutStereo) { |
| return kDemixedR2; |
| } else if (layout == kLayout5_1_ch || layout == kLayout5_1_2_ch || |
| layout == kLayout5_1_4_ch) { |
| return kDemixedR5; |
| } else if (layout == kLayout7_1_ch || layout == kLayout7_1_2_ch || |
| layout == kLayout7_1_4_ch) { |
| return kDemixedR7; |
| } else if (layout == kLayout3_1_2_ch) { |
| return kDemixedR3; |
| } else { |
| LOG(WARNING) |
| << "Unexpected recon gain flag. No corresponding channel label."; |
| return absl::InvalidArgumentError("Unexpected recon gain flag."); |
| } |
| case kReconGainFlagLss: |
| return kDemixedLs5; |
| case kReconGainFlagRss: |
| return kDemixedRs5; |
| case kReconGainFlagLtf: |
| return kDemixedLtf2; |
| case kReconGainFlagRtf: |
| return kDemixedRtf2; |
| case kReconGainFlagLrs: |
| return kDemixedLrs7; |
| case kReconGainFlagRrs: |
| return kDemixedRrs7; |
| case kReconGainFlagLtb: |
| return kDemixedLtb4; |
| case kReconGainFlagRtb: |
| return kDemixedRtb4; |
| case kReconGainFlagLfe: |
| default: |
| LOG(WARNING) |
| << "Unexpected recon gain flag. No corresponding channel label."; |
| return absl::InvalidArgumentError("Unexpected recon gain flag."); |
| } |
| } |
| |
| } // namespace iamf_tools |