• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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