| /* |
| * 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 <cstdint> |
| #include <filesystem> |
| #include <fstream> |
| #include <ios> |
| #include <sstream> |
| #include <string> |
| |
| #include "absl/flags/flag.h" |
| #include "absl/flags/parse.h" |
| #include "absl/flags/usage.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "iamf/cli/adm_to_user_metadata/app/adm_to_user_metadata_main_lib.h" |
| #include "iamf/cli/encoder_main_lib.h" |
| #include "iamf/cli/proto/test_vector_metadata.pb.h" |
| #include "iamf/cli/proto/user_metadata.pb.h" |
| #include "iamf/obu/ia_sequence_header.h" |
| #include "src/google/protobuf/text_format.h" |
| |
| // Flags to parse input user metadata. |
| ABSL_FLAG( |
| std::string, user_metadata_filename, "", |
| "Filename of the proto containing user metadata. It will be read as " |
| "a textproto if the file extension is `.txtpb or `.textproto`. It will " |
| "be read as a binary proto if the file extension is `.binpb`. Exactly one " |
| "of --adm_filename or --user_metadata_filename must be provided."); |
| ABSL_FLAG(std::string, input_wav_directory, "", |
| "Directory containing the input wav files. Used only if " |
| "--user_metadata_filename is provided."); |
| |
| // Flags to parse input ADM file. |
| ABSL_FLAG(std::string, adm_filename, "", |
| "Filename of the ADM BW64 file to use. Exactly one of --adm_filename " |
| "or --user_metadata_filename must be provided."); |
| ABSL_FLAG(std::string, adm_profile_version, "base", |
| "IAMF version to be used: (base/enhanced). Used only if " |
| "--adm_filename is provided."); |
| ABSL_FLAG(int32_t, adm_importance_threshold, 0, |
| "Importance value used to skip an audioObject. Clamped to [0, 10]. " |
| "Used only if --adm_filename is provided."); |
| ABSL_FLAG(int32_t, adm_frame_duration_ms, 10, |
| "Target frame duration in milliseconds. The actual frame duration " |
| "may vary slightly. Used only if --adm_filename is provided."); |
| |
| // Flags to control output directory for either type of input. |
| ABSL_FLAG(std::string, output_iamf_directory, "", |
| "Output directory for iamf files"); |
| // TODO(b/349504599): Add support to write output WAV files. |
| |
| namespace { |
| |
| // Reads in a user metadata proto from a binary or textproto file. |
| absl::StatusOr<iamf_tools_cli_proto::UserMetadata> ReadUserMetadataFromFile( |
| const std::filesystem::path& user_metadata_filename) { |
| std::ifstream user_metadata_file(user_metadata_filename.string(), |
| std::ios::binary | std::ios::in); |
| if (!user_metadata_file) { |
| return absl::FailedPreconditionError( |
| absl::StrCat("Error loading user_metadata_filename= ", |
| user_metadata_filename.string())); |
| } |
| |
| std::ostringstream user_metadata_stream; |
| user_metadata_stream << user_metadata_file.rdbuf(); |
| |
| bool is_parse_successful = false; |
| iamf_tools_cli_proto::UserMetadata user_metadata; |
| if (user_metadata_filename.extension() == ".binpb") { |
| is_parse_successful = |
| user_metadata.ParseFromString(user_metadata_stream.str()); |
| } else if (user_metadata_filename.extension() == ".textproto" || |
| user_metadata_filename.extension() == ".txtpb") { |
| is_parse_successful = google::protobuf::TextFormat::ParseFromString( |
| user_metadata_stream.str(), &user_metadata); |
| } |
| |
| if (!is_parse_successful) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Error parsing proto with user_metadata_filename= ", |
| user_metadata_filename.string())); |
| } |
| |
| return user_metadata; |
| } |
| |
| // Gets a user metadata proto and directory which the encoder will read wav |
| // files from. The proto may be read directly from a file or be generated based |
| // on an input ADM file. |
| absl::StatusOr<iamf_tools_cli_proto::UserMetadata> |
| GetUserMetadataAndInputWavDirectory( |
| const std::filesystem::path& input_user_metadata_filename, |
| const std::filesystem::path& adm_filename, |
| std::filesystem::path& encoder_input_wav_directory, |
| const iamf_tools::ProfileVersion profile_version) { |
| if (input_user_metadata_filename.empty() == adm_filename.empty()) { |
| return absl::InvalidArgumentError( |
| "Please provide exactly one of --user_metadata_filename or " |
| "--adm_filename."); |
| } else if (!input_user_metadata_filename.empty()) { |
| // The user directly provided a proto. Load it from the input file. |
| encoder_input_wav_directory = |
| absl::GetFlag(FLAGS_input_wav_directory).empty() |
| ? std::filesystem::path("iamf/cli/testdata/") |
| : std::filesystem::path(absl::GetFlag(FLAGS_input_wav_directory)); |
| return ReadUserMetadataFromFile(input_user_metadata_filename); |
| } else { |
| // Generate user metadata and wav files based on the input ADM file. |
| std::ifstream adm_file(adm_filename, std::ios::binary | std::ios::in); |
| |
| // Wav files associated with each audio object will be written to a |
| // temporary directory. The encoder will read back in the wav files from |
| // this temporary directory. |
| const auto temp_wav_file_directory = std::filesystem::temp_directory_path(); |
| encoder_input_wav_directory = temp_wav_file_directory; |
| return iamf_tools::adm_to_user_metadata:: |
| GenerateUserMetadataAndSpliceWavFiles( |
| std::filesystem::path(adm_filename).stem().string(), |
| absl::GetFlag(FLAGS_adm_frame_duration_ms), |
| absl::GetFlag(FLAGS_adm_importance_threshold), |
| temp_wav_file_directory, adm_file, profile_version); |
| } |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| absl::SetProgramUsageMessage(argv[0]); |
| absl::ParseCommandLine(argc, argv); |
| |
| // Log the profile version flag. |
| using enum iamf_tools::ProfileVersion; |
| std::string iamf_profile = absl::GetFlag(FLAGS_adm_profile_version); |
| iamf_tools::ProfileVersion profile_version; |
| if (iamf_profile == "base") { |
| profile_version = kIamfBaseProfile; |
| } else if (iamf_profile == "enhanced") { |
| profile_version = kIamfBaseEnhancedProfile; |
| } else { |
| LOG(ERROR) << "Invalid profile version: " << iamf_profile; |
| return static_cast<int>(absl::StatusCode::kInvalidArgument); |
| } |
| LOG(INFO) << "Using IAMF" << iamf_profile << "profile version."; |
| |
| // Prepare `user_metadata` and `input_wav_directory` depending on the |
| // input source. |
| std::filesystem::path input_wav_directory; |
| const auto& user_metadata = GetUserMetadataAndInputWavDirectory( |
| absl::GetFlag(FLAGS_user_metadata_filename), |
| absl::GetFlag(FLAGS_adm_filename), input_wav_directory, profile_version); |
| if (!user_metadata.ok()) { |
| LOG(ERROR) << user_metadata.status(); |
| return static_cast<int>(user_metadata.status().code()); |
| } |
| |
| LOG(INFO) << user_metadata; |
| |
| // Get the directory for the output .iamf files. |
| const auto& output_iamf_directory = |
| absl::GetFlag(FLAGS_output_iamf_directory).empty() |
| ? std::filesystem::temp_directory_path() |
| : std::filesystem::path(absl::GetFlag(FLAGS_output_iamf_directory)); |
| |
| absl::Status status = |
| iamf_tools::TestMain(*user_metadata, input_wav_directory.string(), |
| output_iamf_directory.string()); |
| |
| // Log success or failure. Success is defined as a valid test vector returning |
| // `absl::OkStatus()` or an invalid test vector returning a different status. |
| const bool test_vector_is_valid = |
| user_metadata->test_vector_metadata().is_valid(); |
| std::stringstream ss; |
| ss << "Test case expected to " << (test_vector_is_valid ? "pass" : "fail") |
| << ".\nstatus= " << status; |
| if (test_vector_is_valid == status.ok()) { |
| LOG(INFO) << "Success. " << ss.str(); |
| } else { |
| LOG(ERROR) << "Failure. " << ss.str(); |
| } |
| |
| return static_cast<int>(status.code()); |
| } |