1 /*
2 * Copyright (c) 2024, 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
13 #include "iamf/cli/adm_to_user_metadata/iamf/iamf.h"
14
15 #include <cstdint>
16 #include <map>
17 #include <string>
18 #include <vector>
19
20 #include "absl/base/no_destructor.h"
21 #include "absl/status/status_matchers.h"
22 #include "absl/strings/string_view.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include "iamf/cli/adm_to_user_metadata/adm/adm_elements.h"
26 #include "iamf/cli/user_metadata_builder/iamf_input_layout.h"
27
28 namespace iamf_tools {
29 namespace adm_to_user_metadata {
30 namespace {
31
32 using ::absl_testing::IsOk;
33
34 constexpr int32_t kFrameDurationMs = 10;
35 constexpr uint32_t kSamplesPerSec = 48000;
36
37 constexpr absl::string_view kStereoAudioPackFormatId = "AP_00010002";
38 constexpr absl::string_view kStereoAudioObjectId = "Stereo Audio Object";
39 constexpr absl::string_view kThirdOrderAmbisonicsAudioObjectId =
40 "TOA Audio Object";
41
GetStereoAudioObject(absl::string_view id=kStereoAudioObjectId)42 AudioObject GetStereoAudioObject(absl::string_view id = kStereoAudioObjectId) {
43 return AudioObject(
44 {.id = std::string(id),
45 .audio_pack_format_id_refs = {std::string(kStereoAudioPackFormatId)}});
46 }
47
GetThirdOrderAmbisonicsAudioObject()48 const AudioObject& GetThirdOrderAmbisonicsAudioObject() {
49 constexpr absl::string_view kThirdOrderAmbisonicsAudioPackFormatId =
50 "AP_00040003";
51
52 static const absl::NoDestructor<AudioObject> kThirdOrderAmbisonicsAudioObject(
53 {.id = std::string(kThirdOrderAmbisonicsAudioObjectId),
54 .audio_pack_format_id_refs = {
55 std::string(kThirdOrderAmbisonicsAudioPackFormatId)}});
56 return *kThirdOrderAmbisonicsAudioObject;
57 }
58
GetObjectBasedAudioObject()59 const AudioObject& GetObjectBasedAudioObject() {
60 constexpr absl::string_view kObjectBasedAudioObjectId =
61 "Object Based Audio Object";
62 constexpr absl::string_view kObjectBasedAudioPackFormatId = "AP_00030001";
63
64 static const absl::NoDestructor<AudioObject> kStereoAudioObject(
65 {.id = std::string(kObjectBasedAudioObjectId),
66 .audio_pack_format_id_refs = {
67 std::string(kObjectBasedAudioPackFormatId)}});
68 return *kStereoAudioObject;
69 }
70
GetAdmWithStereoAndToaObjectsWithoutAudioProgramme()71 const ADM& GetAdmWithStereoAndToaObjectsWithoutAudioProgramme() {
72 static const absl::NoDestructor<ADM> kAdmWithStereoAndToaObjects(
73 {.audio_objects = {GetStereoAudioObject(),
74 GetThirdOrderAmbisonicsAudioObject()}});
75 return *kAdmWithStereoAndToaObjects;
76 }
77
GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes()78 const ADM& GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes() {
79 static const absl::NoDestructor<ADM> kAdmWithStereoAndToaObjects(
80 {.audio_programmes =
81 {{
82 .id = "ProgrammeIdWithTwoAudioObjects",
83 .audio_content_id_refs = {"AudioContentIdWithTwoObjects"},
84 },
85 {
86 .id = "ProgrammeIdWithOneAudioObject",
87 .audio_content_id_refs = {"AudioContentIdWithOneObject"},
88 }},
89 .audio_contents = {{
90 .id = "AudioContentIdWithTwoObjects",
91 .audio_object_id_ref =
92 {std::string(kStereoAudioObjectId),
93 std::string(
94 kThirdOrderAmbisonicsAudioObjectId)},
95 },
96 {
97 .id = "AudioContentIdWithOneObject",
98 .audio_object_id_ref = {std::string(
99 kThirdOrderAmbisonicsAudioObjectId)},
100 }},
101 .audio_objects = {GetStereoAudioObject(),
102 GetThirdOrderAmbisonicsAudioObject()}});
103 return *kAdmWithStereoAndToaObjects;
104 }
105
106 const absl::string_view kStereoWithComplementaryGroupId =
107 "Stereo Object with Complementary Group";
GetAdmWithComplementaryGroups()108 const ADM& GetAdmWithComplementaryGroups() {
109 static const absl::NoDestructor<ADM> kAdmWithComplementaryGroups(
110 {.audio_programmes = {{
111 .id = "AudioProgramWithStereoWithComplementaryGroup",
112 .audio_content_id_refs = {"AudioContentIdWithComplemntaryGroup"},
113 }},
114 .audio_contents = {{
115 .id = "AudioContentIdWithComplemntaryGroup",
116 .audio_object_id_ref = {std::string(
117 kStereoWithComplementaryGroupId)},
118 }},
119 .audio_objects = {{.id = std::string(kStereoWithComplementaryGroupId),
120 .audio_pack_format_id_refs =
121 {
122 std::string(kStereoAudioPackFormatId),
123 },
124 .audio_comple_object_id_ref = {std::string(
125 kThirdOrderAmbisonicsAudioObjectId)}},
126 GetThirdOrderAmbisonicsAudioObject()}});
127 return *kAdmWithComplementaryGroups;
128 }
129
TEST(Create,WithNoAudioObjectsSucceeds)130 TEST(Create, WithNoAudioObjectsSucceeds) {
131 EXPECT_THAT(IAMF::Create(
132 /*adm=*/{}, kFrameDurationMs, kSamplesPerSec),
133 IsOk());
134 }
135
TEST(Create,WithObjectBasedAudioObjectFails)136 TEST(Create, WithObjectBasedAudioObjectFails) {
137 EXPECT_FALSE(IAMF::Create(
138 /*adm=*/{.audio_objects = {GetObjectBasedAudioObject()}},
139 kFrameDurationMs, kSamplesPerSec)
140 .ok());
141 }
142
TEST(Create,PopulatesIamfInputLayoutsFromObjects)143 TEST(Create, PopulatesIamfInputLayoutsFromObjects) {
144 const auto iamf =
145 IAMF::Create(GetAdmWithStereoAndToaObjectsWithoutAudioProgramme(),
146 kFrameDurationMs, kSamplesPerSec);
147 ASSERT_THAT(iamf, IsOk());
148
149 const std::vector<IamfInputLayout> kExpectedInputLayouts = {
150 IamfInputLayout::kStereo, IamfInputLayout::kAmbisonicsOrder3};
151
152 EXPECT_EQ(iamf->input_layouts_, kExpectedInputLayouts);
153
154 EXPECT_TRUE(iamf->audio_object_to_audio_element_.empty());
155 EXPECT_TRUE(iamf->mix_presentation_id_to_audio_objects_and_metadata_.empty());
156 }
157
TEST(Create,MapsAreEmptyWhenNoAudioProgramme)158 TEST(Create, MapsAreEmptyWhenNoAudioProgramme) {
159 const auto iamf =
160 IAMF::Create(GetAdmWithStereoAndToaObjectsWithoutAudioProgramme(),
161 kFrameDurationMs, kSamplesPerSec);
162 ASSERT_THAT(iamf, IsOk());
163
164 EXPECT_TRUE(iamf->audio_object_to_audio_element_.empty());
165 EXPECT_TRUE(iamf->mix_presentation_id_to_audio_objects_and_metadata_.empty());
166 }
167
TEST(Create,PopulatesAudioObjectToAudioElement)168 TEST(Create, PopulatesAudioObjectToAudioElement) {
169 const auto iamf =
170 IAMF::Create(GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes(),
171 kFrameDurationMs, kSamplesPerSec);
172 ASSERT_THAT(iamf, IsOk());
173
174 EXPECT_EQ(iamf->audio_object_to_audio_element_.size(), 2);
175 EXPECT_EQ(iamf->audio_object_to_audio_element_.at(
176 std::string(kStereoAudioObjectId)),
177 0);
178 EXPECT_EQ(iamf->audio_object_to_audio_element_.at(
179 std::string(kThirdOrderAmbisonicsAudioObjectId)),
180 1);
181 }
182
TEST(Create,PopulatesAudioProgrammeToAudioObjectsMap)183 TEST(Create, PopulatesAudioProgrammeToAudioObjectsMap) {
184 constexpr int32_t kExpectedFirstAudioProgramIndex = 0;
185 constexpr int32_t kExpectedFirstAudioProgramNumAudioObjects = 2;
186 constexpr int32_t kExpectedSecondAudioProgramIndex = 1;
187 constexpr int32_t kExpectedSecondAudioProgramNumAudioObjects = 1;
188
189 const auto iamf =
190 IAMF::Create(GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes(),
191 kFrameDurationMs, kSamplesPerSec);
192 ASSERT_THAT(iamf, IsOk());
193
194 EXPECT_EQ(iamf->mix_presentation_id_to_audio_objects_and_metadata_.size(), 2);
195 const auto& first_mix_presentation_data =
196 iamf->mix_presentation_id_to_audio_objects_and_metadata_.at(0);
197 EXPECT_EQ(first_mix_presentation_data.original_audio_programme_index,
198 kExpectedFirstAudioProgramIndex);
199 EXPECT_EQ(first_mix_presentation_data.audio_objects.size(),
200 kExpectedFirstAudioProgramNumAudioObjects);
201 EXPECT_EQ(first_mix_presentation_data.audio_objects.at(0).id,
202 std::string(kStereoAudioObjectId));
203 EXPECT_EQ(first_mix_presentation_data.audio_objects.at(1).id,
204 std::string(kThirdOrderAmbisonicsAudioObjectId));
205
206 const auto& second_mix_presentation_data =
207 iamf->mix_presentation_id_to_audio_objects_and_metadata_.at(1);
208 EXPECT_EQ(second_mix_presentation_data.original_audio_programme_index,
209 kExpectedSecondAudioProgramIndex);
210 EXPECT_EQ(second_mix_presentation_data.audio_objects.size(),
211 kExpectedSecondAudioProgramNumAudioObjects);
212 EXPECT_EQ(second_mix_presentation_data.audio_objects.at(0).id,
213 std::string(kThirdOrderAmbisonicsAudioObjectId));
214 }
215
TEST(Create,IgnoresAudioProgrammeWithMoreThanTwoAudioObjects)216 TEST(Create, IgnoresAudioProgrammeWithMoreThanTwoAudioObjects) {
217 const absl::string_view kSecondStereoObjectId = "Another Stereo Audio Object";
218 auto adm = GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes();
219 adm.audio_programmes.push_back(
220 {.id = "AudioProgrammeWithTooManyAudioObjects",
221 .audio_content_id_refs = {"AudioContentIdWithThreeObjects"}});
222 adm.audio_contents.push_back(
223 {.id = "AudioContentIdWithThreeObjects",
224 .audio_object_id_ref = {std::string(kStereoAudioObjectId),
225 std::string(kThirdOrderAmbisonicsAudioObjectId),
226 std::string(kSecondStereoObjectId)}});
227 adm.audio_objects.push_back(GetStereoAudioObject(kSecondStereoObjectId));
228 const auto iamf = IAMF::Create(adm, kFrameDurationMs, kSamplesPerSec);
229 ASSERT_THAT(iamf, IsOk());
230
231 EXPECT_EQ(iamf->mix_presentation_id_to_audio_objects_and_metadata_.size(), 2);
232 }
233
TEST(Create,CreatesAudioProgrammesForComplementaryGroups)234 TEST(Create, CreatesAudioProgrammesForComplementaryGroups) {
235 const auto iamf = IAMF::Create(GetAdmWithComplementaryGroups(),
236 kFrameDurationMs, kSamplesPerSec);
237 ASSERT_THAT(iamf, IsOk());
238
239 EXPECT_EQ(iamf->audio_object_to_audio_element_.size(), 2);
240
241 EXPECT_EQ(iamf->mix_presentation_id_to_audio_objects_and_metadata_.size(), 2);
242 const auto& first_mix_presentation_data =
243 iamf->mix_presentation_id_to_audio_objects_and_metadata_.at(0);
244 EXPECT_EQ(first_mix_presentation_data.original_audio_programme_index, 0);
245 EXPECT_EQ(first_mix_presentation_data.audio_objects.size(), 1);
246 EXPECT_EQ(first_mix_presentation_data.audio_objects.at(0).id,
247 kStereoWithComplementaryGroupId);
248
249 const auto& second_mix_presentation_data =
250 iamf->mix_presentation_id_to_audio_objects_and_metadata_.at(1);
251 EXPECT_EQ(second_mix_presentation_data.original_audio_programme_index, 0);
252 EXPECT_EQ(second_mix_presentation_data.audio_objects.size(), 1);
253 EXPECT_EQ(second_mix_presentation_data.audio_objects.at(0).id,
254 kThirdOrderAmbisonicsAudioObjectId);
255 }
256
257 struct FrameDurationInfoTestCase {
258 int32_t frame_duration_ms;
259 int64_t samples_per_sec;
260 int32_t expected_num_samples_per_frame = 0;
261 bool is_valid = true;
262 };
263
264 using FrameDurationInfo = ::testing::TestWithParam<FrameDurationInfoTestCase>;
265
TEST_P(FrameDurationInfo,TestWithParam)266 TEST_P(FrameDurationInfo, TestWithParam) {
267 const auto& test_case = GetParam();
268 // Many fields are irrelevant to calculating the number of samples per frame.
269 auto iamf = IAMF::Create(
270 /*adm=*/{}, test_case.frame_duration_ms, test_case.samples_per_sec);
271 ASSERT_EQ(iamf.ok(), test_case.is_valid);
272
273 if (test_case.is_valid) {
274 EXPECT_EQ(iamf->num_samples_per_frame_,
275 test_case.expected_num_samples_per_frame);
276 }
277 }
278
279 INSTANTIATE_TEST_SUITE_P(CalculatesNumSamplesPerFrame, FrameDurationInfo,
280 testing::ValuesIn<FrameDurationInfoTestCase>({
281 {.frame_duration_ms = 10,
282 .samples_per_sec = 48000,
283 .expected_num_samples_per_frame = 480},
284 {.frame_duration_ms = 20,
285 .samples_per_sec = 48000,
286 .expected_num_samples_per_frame = 960},
287 {.frame_duration_ms = 20,
288 .samples_per_sec = 96000,
289 .expected_num_samples_per_frame = 1920},
290 {.frame_duration_ms = 10,
291 .samples_per_sec = 44100,
292 .expected_num_samples_per_frame = 441},
293 }));
294
295 INSTANTIATE_TEST_SUITE_P(CalculatesNumSamplesPerFrameAndRoundsDown,
296 FrameDurationInfo,
297 testing::ValuesIn<FrameDurationInfoTestCase>({
298 {.frame_duration_ms = 9,
299 .samples_per_sec = 44100,
300 .expected_num_samples_per_frame = 396},
301
302 }));
303
304 INSTANTIATE_TEST_SUITE_P(
305 InvalidWheFrameDurationIsZero, FrameDurationInfo,
306 testing::ValuesIn<FrameDurationInfoTestCase>({
307 {.frame_duration_ms = 0, .samples_per_sec = 48000, .is_valid = false},
308 }));
309
310 INSTANTIATE_TEST_SUITE_P(
311 InvalidWhenSamplesPerSecIsZero, FrameDurationInfo,
312 testing::ValuesIn<FrameDurationInfoTestCase>({
313 {.frame_duration_ms = 10, .samples_per_sec = 0, .is_valid = false},
314 }));
315
316 } // namespace
317 } // namespace adm_to_user_metadata
318 } // namespace iamf_tools
319