1 /*
2 * Copyright (c) 2023, Alliance for Open Media. All rights reserved
3 *
4 * This source code is subject to the terms of the BSD 3-Clause Clear License
5 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
6 * License was not distributed with this source code in the LICENSE file, you
7 * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
8 * Alliance for Open Media Patent License 1.0 was not distributed with this
9 * source code in the PATENTS file, you can obtain it at
10 * www.aomedia.org/license/patent.
11 */
12 #include <cstdint>
13 #include <filesystem>
14 #include <fstream>
15 #include <ios>
16 #include <sstream>
17 #include <string>
18
19 #include "absl/flags/flag.h"
20 #include "absl/flags/parse.h"
21 #include "absl/flags/usage.h"
22 #include "absl/log/log.h"
23 #include "absl/status/status.h"
24 #include "absl/strings/str_cat.h"
25 #include "absl/strings/string_view.h"
26 #include "iamf/cli/adm_to_user_metadata/app/adm_to_user_metadata_main_lib.h"
27 #include "iamf/cli/encoder_main_lib.h"
28 #include "iamf/cli/proto/test_vector_metadata.pb.h"
29 #include "iamf/cli/proto/user_metadata.pb.h"
30 #include "iamf/obu/ia_sequence_header.h"
31 #include "src/google/protobuf/text_format.h"
32
33 // Flags to parse input user metadata.
34 ABSL_FLAG(
35 std::string, user_metadata_filename, "",
36 "Filename of the proto containing user metadata. It will be read as "
37 "a textproto if the file extension is `.txtpb or `.textproto`. It will "
38 "be read as a binary proto if the file extension is `.binpb`. Exactly one "
39 "of --adm_filename or --user_metadata_filename must be provided.");
40 ABSL_FLAG(std::string, input_wav_directory, "",
41 "Directory containing the input wav files. Used only if "
42 "--user_metadata_filename is provided.");
43
44 // Flags to parse input ADM file.
45 ABSL_FLAG(std::string, adm_filename, "",
46 "Filename of the ADM BW64 file to use. Exactly one of --adm_filename "
47 "or --user_metadata_filename must be provided.");
48 ABSL_FLAG(std::string, adm_profile_version, "base",
49 "IAMF version to be used: (base/enhanced). Used only if "
50 "--adm_filename is provided.");
51 ABSL_FLAG(int32_t, adm_importance_threshold, 0,
52 "Importance value used to skip an audioObject. Clamped to [0, 10]. "
53 "Used only if --adm_filename is provided.");
54 ABSL_FLAG(int32_t, adm_frame_duration_ms, 10,
55 "Target frame duration in milliseconds. The actual frame duration "
56 "may vary slightly. Used only if --adm_filename is provided.");
57
58 // Flags to control output directory for either type of input.
59 ABSL_FLAG(std::string, output_iamf_directory, "",
60 "Output directory for iamf files");
61 // TODO(b/349504599): Add support to write output WAV files.
62
63 namespace {
64
65 // Reads in a user metadata proto from a binary or textproto file.
ReadUserMetadataFromFile(const std::filesystem::path & user_metadata_filename)66 absl::StatusOr<iamf_tools_cli_proto::UserMetadata> ReadUserMetadataFromFile(
67 const std::filesystem::path& user_metadata_filename) {
68 std::ifstream user_metadata_file(user_metadata_filename.string(),
69 std::ios::binary | std::ios::in);
70 if (!user_metadata_file) {
71 return absl::FailedPreconditionError(
72 absl::StrCat("Error loading user_metadata_filename= ",
73 user_metadata_filename.string()));
74 }
75
76 std::ostringstream user_metadata_stream;
77 user_metadata_stream << user_metadata_file.rdbuf();
78
79 bool is_parse_successful = false;
80 iamf_tools_cli_proto::UserMetadata user_metadata;
81 if (user_metadata_filename.extension() == ".binpb") {
82 is_parse_successful =
83 user_metadata.ParseFromString(user_metadata_stream.str());
84 } else if (user_metadata_filename.extension() == ".textproto" ||
85 user_metadata_filename.extension() == ".txtpb") {
86 is_parse_successful = google::protobuf::TextFormat::ParseFromString(
87 user_metadata_stream.str(), &user_metadata);
88 }
89
90 if (!is_parse_successful) {
91 return absl::InvalidArgumentError(
92 absl::StrCat("Error parsing proto with user_metadata_filename= ",
93 user_metadata_filename.string()));
94 }
95
96 return user_metadata;
97 }
98
99 // Gets a user metadata proto and directory which the encoder will read wav
100 // files from. The proto may be read directly from a file or be generated based
101 // on an input ADM file.
102 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)103 GetUserMetadataAndInputWavDirectory(
104 const std::filesystem::path& input_user_metadata_filename,
105 const std::filesystem::path& adm_filename,
106 std::filesystem::path& encoder_input_wav_directory,
107 const iamf_tools::ProfileVersion profile_version) {
108 if (input_user_metadata_filename.empty() == adm_filename.empty()) {
109 return absl::InvalidArgumentError(
110 "Please provide exactly one of --user_metadata_filename or "
111 "--adm_filename.");
112 } else if (!input_user_metadata_filename.empty()) {
113 // The user directly provided a proto. Load it from the input file.
114 encoder_input_wav_directory =
115 absl::GetFlag(FLAGS_input_wav_directory).empty()
116 ? std::filesystem::path("iamf/cli/testdata/")
117 : std::filesystem::path(absl::GetFlag(FLAGS_input_wav_directory));
118 return ReadUserMetadataFromFile(input_user_metadata_filename);
119 } else {
120 // Generate user metadata and wav files based on the input ADM file.
121 std::ifstream adm_file(adm_filename, std::ios::binary | std::ios::in);
122
123 // Wav files associated with each audio object will be written to a
124 // temporary directory. The encoder will read back in the wav files from
125 // this temporary directory.
126 const auto temp_wav_file_directory = std::filesystem::temp_directory_path();
127 encoder_input_wav_directory = temp_wav_file_directory;
128 return iamf_tools::adm_to_user_metadata::
129 GenerateUserMetadataAndSpliceWavFiles(
130 std::filesystem::path(adm_filename).stem().string(),
131 absl::GetFlag(FLAGS_adm_frame_duration_ms),
132 absl::GetFlag(FLAGS_adm_importance_threshold),
133 temp_wav_file_directory, adm_file, profile_version);
134 }
135 }
136
137 } // namespace
138
main(int argc,char ** argv)139 int main(int argc, char** argv) {
140 absl::SetProgramUsageMessage(argv[0]);
141 absl::ParseCommandLine(argc, argv);
142
143 // Log the profile version flag.
144 using enum iamf_tools::ProfileVersion;
145 std::string iamf_profile = absl::GetFlag(FLAGS_adm_profile_version);
146 iamf_tools::ProfileVersion profile_version;
147 if (iamf_profile == "base") {
148 profile_version = kIamfBaseProfile;
149 } else if (iamf_profile == "enhanced") {
150 profile_version = kIamfBaseEnhancedProfile;
151 } else {
152 LOG(ERROR) << "Invalid profile version: " << iamf_profile;
153 return static_cast<int>(absl::StatusCode::kInvalidArgument);
154 }
155 LOG(INFO) << "Using IAMF" << iamf_profile << "profile version.";
156
157 // Prepare `user_metadata` and `input_wav_directory` depending on the
158 // input source.
159 std::filesystem::path input_wav_directory;
160 const auto& user_metadata = GetUserMetadataAndInputWavDirectory(
161 absl::GetFlag(FLAGS_user_metadata_filename),
162 absl::GetFlag(FLAGS_adm_filename), input_wav_directory, profile_version);
163 if (!user_metadata.ok()) {
164 LOG(ERROR) << user_metadata.status();
165 return static_cast<int>(user_metadata.status().code());
166 }
167
168 LOG(INFO) << user_metadata;
169
170 // Get the directory for the output .iamf files.
171 const auto& output_iamf_directory =
172 absl::GetFlag(FLAGS_output_iamf_directory).empty()
173 ? std::filesystem::temp_directory_path()
174 : std::filesystem::path(absl::GetFlag(FLAGS_output_iamf_directory));
175
176 absl::Status status =
177 iamf_tools::TestMain(*user_metadata, input_wav_directory.string(),
178 output_iamf_directory.string());
179
180 // Log success or failure. Success is defined as a valid test vector returning
181 // `absl::OkStatus()` or an invalid test vector returning a different status.
182 const bool test_vector_is_valid =
183 user_metadata->test_vector_metadata().is_valid();
184 std::stringstream ss;
185 ss << "Test case expected to " << (test_vector_is_valid ? "pass" : "fail")
186 << ".\nstatus= " << status;
187 if (test_vector_is_valid == status.ok()) {
188 LOG(INFO) << "Success. " << ss.str();
189 } else {
190 LOG(ERROR) << "Failure. " << ss.str();
191 }
192
193 return static_cast<int>(status.code());
194 }
195