1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "OpusHeaderTest"
19 #include <utils/Log.h>
20
21 #include <fstream>
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <media/stagefright/foundation/OpusHeader.h>
26
27 #include "OpusHeaderTestEnvironment.h"
28
29 using namespace android;
30
31 #define OUTPUT_FILE_NAME "/data/local/tmp/OpusOutput"
32
33 // Opus in WebM is a well-known, yet under-documented, format. The codec private data
34 // of the track is an Opus Ogg header (https://tools.ietf.org/html/rfc7845#section-5.1)
35 // channel mapping offset in opus header
36 constexpr size_t kOpusHeaderStreamMapOffset = 21;
37 constexpr size_t kMaxOpusHeaderSize = 100;
38 // AOPUSHDR + AOPUSHDRLength +
39 // (8 + 8 ) +
40 // Header(csd) + num_streams + num_coupled + 1
41 // (19 + 1 + 1 + 1) +
42 // AOPUSDLY + AOPUSDLYLength + DELAY + AOPUSPRL + AOPUSPRLLength + PRL
43 // (8 + 8 + 8 + 8 + 8 + 8)
44 // = 86
45 constexpr size_t kOpusHeaderChannelMapOffset = 86;
46 constexpr uint32_t kOpusSampleRate = 48000;
47 constexpr uint64_t kOpusSeekPrerollNs = 80000000;
48 constexpr int64_t kNsecPerSec = 1000000000ll;
49
50 // Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
51 // mappings for up to 8 channels. This information is part of the Vorbis I
52 // Specification:
53 // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
54 constexpr int kMaxChannels = 8;
55 constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
56 {0},
57 {0, 1},
58 {0, 2, 1},
59 {0, 1, 2, 3},
60 {0, 4, 1, 2, 3},
61 {0, 4, 1, 2, 3, 5},
62 {0, 4, 1, 2, 3, 5, 6},
63 {0, 6, 1, 2, 3, 4, 5, 7},
64 };
65
66 static OpusHeaderTestEnvironment *gEnv = nullptr;
67
68 class OpusHeaderTest {
69 public:
OpusHeaderTest()70 OpusHeaderTest() : mInputBuffer(nullptr) {}
71
~OpusHeaderTest()72 ~OpusHeaderTest() {
73 if (mEleStream.is_open()) mEleStream.close();
74 if (mInputBuffer) {
75 free(mInputBuffer);
76 mInputBuffer = nullptr;
77 }
78 }
79 ifstream mEleStream;
80 uint8_t *mInputBuffer;
81 };
82
83 class OpusHeaderParseTest : public OpusHeaderTest,
84 public ::testing::TestWithParam<
85 tuple<string /* InputFileName */, int32_t /* ChannelCount */,
86 bool /* isHeaderValid */, bool /* isCodecDelayValid */,
87 bool /* isSeekPreRollValid */, bool /* isInputValid */>> {
88 };
89
90 class OpusHeaderWriteTest
91 : public OpusHeaderTest,
92 public ::testing::TestWithParam<tuple<int32_t /* ChannelCount */, int32_t /* skipSamples */,
93 string /* referenceFile */>> {};
94
TEST_P(OpusHeaderWriteTest,WriteTest)95 TEST_P(OpusHeaderWriteTest, WriteTest) {
96 tuple<int32_t, int32_t, string> params = GetParam();
97 OpusHeader writtenHeader;
98 memset(&writtenHeader, 0, sizeof(writtenHeader));
99 int32_t channels = get<0>(params);
100 writtenHeader.channels = channels;
101 writtenHeader.num_streams = channels;
102 writtenHeader.channel_mapping = ((channels > 8) ? 255 : (channels > 2));
103 int32_t skipSamples = get<1>(params);
104 string referenceFileName = gEnv->getRes() + get<2>(params);
105 writtenHeader.skip_samples = skipSamples;
106 uint64_t codecDelayNs = skipSamples * kNsecPerSec / kOpusSampleRate;
107 uint8_t headerData[kMaxOpusHeaderSize];
108 int32_t headerSize = WriteOpusHeaders(writtenHeader, kOpusSampleRate, headerData,
109 sizeof(headerData), codecDelayNs, kOpusSeekPrerollNs);
110 ASSERT_GT(headerSize, 0) << "failed to generate Opus header";
111 ASSERT_LE(headerSize, kMaxOpusHeaderSize)
112 << "Invalid header written. Header size can't exceed kMaxOpusHeaderSize";
113
114 ofstream ostrm;
115 ostrm.open(OUTPUT_FILE_NAME, ofstream::binary);
116 ASSERT_TRUE(ostrm.is_open()) << "Failed to open output file " << OUTPUT_FILE_NAME;
117 ostrm.write(reinterpret_cast<char *>(headerData), headerSize);
118 ostrm.close();
119
120 mEleStream.open(referenceFileName, ifstream::binary);
121 ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open referenceFileName " << get<2>(params);
122
123 struct stat buf;
124 int32_t statStatus = stat(referenceFileName.c_str(), &buf);
125 ASSERT_EQ(statStatus, 0) << "Unable to get file properties";
126
127 size_t fileSize = buf.st_size;
128 mInputBuffer = (uint8_t *)malloc(fileSize);
129 ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory. Malloc failed for size " << fileSize;
130
131 mEleStream.read(reinterpret_cast<char *>(mInputBuffer), fileSize);
132 ASSERT_EQ(mEleStream.gcount(), fileSize) << "mEleStream.gcount() != bytesCount";
133
134 ASSERT_EQ(fileSize, headerSize)
135 << "Mismatch in size between header generated and reference header";
136 int32_t match = memcmp(reinterpret_cast<char *>(mInputBuffer),
137 reinterpret_cast<char *>(headerData), fileSize);
138 ASSERT_EQ(match, 0) << "Opus header does not match reference file: " << referenceFileName;
139
140 size_t opusHeadSize = 0;
141 size_t codecDelayBufSize = 0;
142 size_t seekPreRollBufSize = 0;
143 void *opusHeadBuf = nullptr;
144 void *codecDelayBuf = nullptr;
145 void *seekPreRollBuf = nullptr;
146 bool status = GetOpusHeaderBuffers(headerData, headerSize, &opusHeadBuf, &opusHeadSize,
147 &codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
148 &seekPreRollBufSize);
149 ASSERT_TRUE(status) << "Encountered error in GetOpusHeaderBuffers";
150
151 uint64_t value = *((uint64_t *)codecDelayBuf);
152 ASSERT_EQ(value, codecDelayNs);
153
154 value = *((uint64_t *)seekPreRollBuf);
155 ASSERT_EQ(value, kOpusSeekPrerollNs);
156
157 OpusHeader parsedHeader;
158 status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &parsedHeader);
159 ASSERT_TRUE(status) << "Encountered error while Parsing Opus Header.";
160
161 ASSERT_EQ(writtenHeader.channels, parsedHeader.channels)
162 << "Invalid header generated. Mismatch between channel counts";
163
164 ASSERT_EQ(writtenHeader.skip_samples, parsedHeader.skip_samples)
165 << "Mismatch between no of skipSamples written "
166 "and no of skipSamples got after parsing";
167
168 ASSERT_EQ(writtenHeader.channel_mapping, parsedHeader.channel_mapping)
169 << "Mismatch between channelMapping written "
170 "and channelMapping got after parsing";
171
172 if (parsedHeader.channel_mapping) {
173 ASSERT_GT(parsedHeader.channels, 2);
174 ASSERT_EQ(writtenHeader.num_streams, parsedHeader.num_streams)
175 << "Invalid header generated. Mismatch between channel counts";
176
177 ASSERT_EQ(writtenHeader.num_coupled, parsedHeader.num_coupled)
178 << "Invalid header generated. Mismatch between channel counts";
179
180 ASSERT_EQ(parsedHeader.num_coupled + parsedHeader.num_streams, parsedHeader.channels);
181
182 ASSERT_LE(parsedHeader.num_coupled, parsedHeader.num_streams)
183 << "Invalid header generated. Number of coupled streams cannot be greater than "
184 "number "
185 "of streams.";
186
187 ASSERT_EQ(headerSize, kOpusHeaderChannelMapOffset + writtenHeader.channels)
188 << "Invalid header written. Header size should be equal to 86 + "
189 "writtenHeader.channels";
190
191 uint8_t mappedChannelNumber;
192 for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
193 mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
194 kOpusHeaderStreamMapOffset + channelNumber);
195 ASSERT_LT(mappedChannelNumber, channels) << "Invalid header generated. Channel mapping "
196 "cannot be greater than channel count.";
197
198 ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
199 << "Invalid header generated. Channel mapping is not as per specification.";
200 }
201 } else {
202 ASSERT_LE(parsedHeader.channels, 2);
203 }
204 }
205
TEST_P(OpusHeaderParseTest,ParseTest)206 TEST_P(OpusHeaderParseTest, ParseTest) {
207 tuple<string, int32_t, bool, bool, bool, bool> params = GetParam();
208 string inputFileName = gEnv->getRes() + get<0>(params);
209 mEleStream.open(inputFileName, ifstream::binary);
210 ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open inputfile " << get<0>(params);
211 bool isHeaderValid = get<2>(params);
212 bool isCodecDelayValid = get<3>(params);
213 bool isSeekPreRollValid = get<4>(params);
214 bool isInputValid = get<5>(params);
215
216 struct stat buf;
217 stat(inputFileName.c_str(), &buf);
218 size_t fileSize = buf.st_size;
219 mInputBuffer = (uint8_t *)malloc(fileSize);
220 ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory. Malloc failed for size " << fileSize;
221
222 mEleStream.read(reinterpret_cast<char *>(mInputBuffer), fileSize);
223 ASSERT_EQ(mEleStream.gcount(), fileSize) << "mEleStream.gcount() != bytesCount";
224
225 OpusHeader header;
226 size_t opusHeadSize = 0;
227 size_t codecDelayBufSize = 0;
228 size_t seekPreRollBufSize = 0;
229 void *opusHeadBuf = nullptr;
230 void *codecDelayBuf = nullptr;
231 void *seekPreRollBuf = nullptr;
232 bool status = GetOpusHeaderBuffers(mInputBuffer, fileSize, &opusHeadBuf, &opusHeadSize,
233 &codecDelayBuf, &codecDelayBufSize, &seekPreRollBuf,
234 &seekPreRollBufSize);
235 if (!isHeaderValid) {
236 ASSERT_EQ(opusHeadBuf, nullptr);
237 } else {
238 ASSERT_NE(opusHeadBuf, nullptr);
239 }
240 if (!isCodecDelayValid) {
241 ASSERT_EQ(codecDelayBuf, nullptr);
242 } else {
243 ASSERT_NE(codecDelayBuf, nullptr);
244 }
245 if (!isSeekPreRollValid) {
246 ASSERT_EQ(seekPreRollBuf, nullptr);
247 } else {
248 ASSERT_NE(seekPreRollBuf, nullptr);
249 }
250 if (!status) {
251 ASSERT_FALSE(isInputValid) << "GetOpusHeaderBuffers failed";
252 return;
253 }
254
255 status = ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &header);
256
257 if (status) {
258 ASSERT_TRUE(isInputValid) << "Parse opus header didn't fail for invalid input";
259 } else {
260 ASSERT_FALSE(isInputValid);
261 return;
262 }
263
264 int32_t channels = get<1>(params);
265 ASSERT_EQ(header.channels, channels) << "Parser returned invalid channel count";
266 ASSERT_LE(header.channels, kMaxChannels);
267
268 ASSERT_LE(header.num_coupled, header.num_streams)
269 << "Invalid header generated. Number of coupled streams cannot be greater than number "
270 "of streams.";
271
272 ASSERT_EQ(header.num_coupled + header.num_streams, header.channels);
273
274 if (header.channel_mapping) {
275 uint8_t mappedChannelNumber;
276 for (int32_t channelNumber = 0; channelNumber < channels; channelNumber++) {
277 mappedChannelNumber = *(reinterpret_cast<uint8_t *>(opusHeadBuf) +
278 kOpusHeaderStreamMapOffset + channelNumber);
279 ASSERT_LT(mappedChannelNumber, channels)
280 << "Invalid header. Channel mapping cannot be greater than channel count.";
281
282 ASSERT_EQ(mappedChannelNumber, kOpusChannelMap[channels - 1][channelNumber])
283 << "Invalid header generated. Channel mapping "
284 "is not as per specification.";
285 }
286 }
287 }
288
289 INSTANTIATE_TEST_SUITE_P(
290 OpusHeaderTestAll, OpusHeaderWriteTest,
291 ::testing::Values(make_tuple(1, 312, "output_channels_1skipSamples_312.opus"),
292 make_tuple(2, 312, "output_channels_2skipSamples_312.opus"),
293 make_tuple(5, 312, "output_channels_5skipSamples_312.opus"),
294 make_tuple(6, 312, "output_channels_6skipSamples_312.opus"),
295 make_tuple(1, 0, "output_channels_1skipSamples_0.opus"),
296 make_tuple(2, 0, "output_channels_2skipSamples_0.opus"),
297 make_tuple(5, 0, "output_channels_5skipSamples_0.opus"),
298 make_tuple(6, 0, "output_channels_6skipSamples_0.opus"),
299 make_tuple(1, 624, "output_channels_1skipSamples_624.opus"),
300 make_tuple(2, 624, "output_channels_2skipSamples_624.opus"),
301 make_tuple(5, 624, "output_channels_5skipSamples_624.opus"),
302 make_tuple(6, 624, "output_channels_6skipSamples_624.opus")));
303
304 INSTANTIATE_TEST_SUITE_P(
305 OpusHeaderTestAll, OpusHeaderParseTest,
306 ::testing::Values(
307 make_tuple("2ch_valid_size83B.opus", 2, true, true, true, true),
308 make_tuple("3ch_valid_size88B.opus", 3, true, true, true, true),
309 make_tuple("5ch_valid.opus", 5, true, false, false, true),
310 make_tuple("6ch_valid.opus", 6, true, false, false, true),
311 make_tuple("1ch_valid.opus", 1, true, false, false, true),
312 make_tuple("2ch_valid.opus", 2, true, false, false, true),
313 make_tuple("3ch_invalid_size.opus", 3, true, true, true, false),
314 make_tuple("3ch_invalid_streams.opus", 3, true, true, true, false),
315 make_tuple("5ch_invalid_channelmapping.opus", 5, true, false, false, false),
316 make_tuple("5ch_invalid_coupledstreams.opus", 5, true, false, false, false),
317 make_tuple("6ch_invalid_channelmapping.opus", 6, true, false, false, false),
318 make_tuple("9ch_invalid_channels.opus", 9, true, true, true, false),
319 make_tuple("2ch_invalid_header.opus", 2, false, false, false, false),
320 make_tuple("2ch_invalid_headerlength_16.opus", 2, false, false, false, false),
321 make_tuple("2ch_invalid_headerlength_256.opus", 2, false, false, false, false),
322 make_tuple("2ch_invalid_size.opus", 2, false, false, false, false),
323 make_tuple("3ch_invalid_channelmapping_0.opus", 3, true, true, true, false),
324 make_tuple("3ch_invalid_coupledstreams.opus", 3, true, true, true, false),
325 make_tuple("3ch_invalid_headerlength.opus", 3, true, true, true, false),
326 make_tuple("3ch_invalid_headerSize1.opus", 3, false, false, false, false),
327 make_tuple("3ch_invalid_headerSize2.opus", 3, false, false, false, false),
328 make_tuple("3ch_invalid_headerSize3.opus", 3, false, false, false, false),
329 make_tuple("3ch_invalid_nodelay.opus", 3, false, false, false, false),
330 make_tuple("3ch_invalid_nopreroll.opus", 3, false, false, false, false)));
331
main(int argc,char ** argv)332 int main(int argc, char **argv) {
333 gEnv = new OpusHeaderTestEnvironment();
334 ::testing::AddGlobalTestEnvironment(gEnv);
335 ::testing::InitGoogleTest(&argc, argv);
336 int status = gEnv->initFromOptions(argc, argv);
337 if (status == 0) {
338 status = RUN_ALL_TESTS();
339 ALOGD("Opus Header Test Result = %d\n", status);
340 }
341 return status;
342 }
343