1 /*
2 * Copyright (C) 2019 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 "ExtractorUnitTest"
19 #include <utils/Log.h>
20
21 #include <datasource/FileSource.h>
22 #include <media/stagefright/MediaBufferGroup.h>
23 #include <media/stagefright/MediaDefs.h>
24 #include <media/stagefright/MetaDataUtils.h>
25
26 #include "aac/AACExtractor.h"
27 #include "amr/AMRExtractor.h"
28 #include "flac/FLACExtractor.h"
29 #include "midi/MidiExtractor.h"
30 #include "mkv/MatroskaExtractor.h"
31 #include "mp3/MP3Extractor.h"
32 #include "mp4/MPEG4Extractor.h"
33 #include "mp4/SampleTable.h"
34 #include "mpeg2/MPEG2PSExtractor.h"
35 #include "mpeg2/MPEG2TSExtractor.h"
36 #include "ogg/OggExtractor.h"
37 #include "wav/WAVExtractor.h"
38
39 #include "ExtractorUnitTestEnvironment.h"
40
41 using namespace android;
42
43 #define OUTPUT_DUMP_FILE "/data/local/tmp/extractorOutput"
44
45 constexpr int32_t kMaxCount = 10;
46 constexpr int32_t kOpusSeekPreRollUs = 80000; // 80 ms;
47
48 static ExtractorUnitTestEnvironment *gEnv = nullptr;
49
50 class ExtractorUnitTest : public ::testing::TestWithParam<pair<string, string>> {
51 public:
ExtractorUnitTest()52 ExtractorUnitTest() : mInputFp(nullptr), mDataSource(nullptr), mExtractor(nullptr) {}
53
~ExtractorUnitTest()54 ~ExtractorUnitTest() {
55 if (mInputFp) {
56 fclose(mInputFp);
57 mInputFp = nullptr;
58 }
59 if (mDataSource) {
60 mDataSource.clear();
61 mDataSource = nullptr;
62 }
63 if (mExtractor) {
64 delete mExtractor;
65 mExtractor = nullptr;
66 }
67 }
68
SetUp()69 virtual void SetUp() override {
70 mExtractorName = unknown_comp;
71 mDisableTest = false;
72
73 static const std::map<std::string, standardExtractors> mapExtractor = {
74 {"aac", AAC}, {"amr", AMR}, {"mp3", MP3}, {"ogg", OGG},
75 {"wav", WAV}, {"mkv", MKV}, {"flac", FLAC}, {"midi", MIDI},
76 {"mpeg4", MPEG4}, {"mpeg2ts", MPEG2TS}, {"mpeg2ps", MPEG2PS}};
77 // Find the component type
78 string writerFormat = GetParam().first;
79 if (mapExtractor.find(writerFormat) != mapExtractor.end()) {
80 mExtractorName = mapExtractor.at(writerFormat);
81 }
82 if (mExtractorName == standardExtractors::unknown_comp) {
83 cout << "[ WARN ] Test Skipped. Invalid extractor\n";
84 mDisableTest = true;
85 }
86 }
87
88 int32_t setDataSource(string inputFileName);
89
90 int32_t createExtractor();
91
92 enum standardExtractors {
93 AAC,
94 AMR,
95 FLAC,
96 MIDI,
97 MKV,
98 MP3,
99 MPEG4,
100 MPEG2PS,
101 MPEG2TS,
102 OGG,
103 WAV,
104 unknown_comp,
105 };
106
107 bool mDisableTest;
108 standardExtractors mExtractorName;
109
110 FILE *mInputFp;
111 sp<DataSource> mDataSource;
112 MediaExtractorPluginHelper *mExtractor;
113 };
114
setDataSource(string inputFileName)115 int32_t ExtractorUnitTest::setDataSource(string inputFileName) {
116 mInputFp = fopen(inputFileName.c_str(), "rb");
117 if (!mInputFp) {
118 ALOGE("Unable to open input file for reading");
119 return -1;
120 }
121 struct stat buf;
122 stat(inputFileName.c_str(), &buf);
123 int32_t fd = fileno(mInputFp);
124 mDataSource = new FileSource(dup(fd), 0, buf.st_size);
125 if (!mDataSource) return -1;
126 return 0;
127 }
128
createExtractor()129 int32_t ExtractorUnitTest::createExtractor() {
130 switch (mExtractorName) {
131 case AAC:
132 mExtractor = new AACExtractor(new DataSourceHelper(mDataSource->wrap()), 0);
133 break;
134 case AMR:
135 mExtractor = new AMRExtractor(new DataSourceHelper(mDataSource->wrap()));
136 break;
137 case MP3:
138 mExtractor = new MP3Extractor(new DataSourceHelper(mDataSource->wrap()), nullptr);
139 break;
140 case OGG:
141 mExtractor = new OggExtractor(new DataSourceHelper(mDataSource->wrap()));
142 break;
143 case WAV:
144 mExtractor = new WAVExtractor(new DataSourceHelper(mDataSource->wrap()));
145 break;
146 case MKV:
147 mExtractor = new MatroskaExtractor(new DataSourceHelper(mDataSource->wrap()));
148 break;
149 case FLAC:
150 mExtractor = new FLACExtractor(new DataSourceHelper(mDataSource->wrap()));
151 break;
152 case MPEG4:
153 mExtractor = new MPEG4Extractor(new DataSourceHelper(mDataSource->wrap()));
154 break;
155 case MPEG2TS:
156 mExtractor = new MPEG2TSExtractor(new DataSourceHelper(mDataSource->wrap()));
157 break;
158 case MPEG2PS:
159 mExtractor = new MPEG2PSExtractor(new DataSourceHelper(mDataSource->wrap()));
160 break;
161 case MIDI:
162 mExtractor = new MidiExtractor(mDataSource->wrap());
163 break;
164 default:
165 return -1;
166 }
167 if (!mExtractor) return -1;
168 return 0;
169 }
170
getSeekablePoints(vector<int64_t> & seekablePoints,MediaTrackHelper * track)171 void getSeekablePoints(vector<int64_t> &seekablePoints, MediaTrackHelper *track) {
172 int32_t status = 0;
173 if (!seekablePoints.empty()) {
174 seekablePoints.clear();
175 }
176 int64_t timeStamp;
177 while (status != AMEDIA_ERROR_END_OF_STREAM) {
178 MediaBufferHelper *buffer = nullptr;
179 status = track->read(&buffer);
180 if (buffer) {
181 AMediaFormat *metaData = buffer->meta_data();
182 int32_t isSync = 0;
183 AMediaFormat_getInt32(metaData, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, &isSync);
184 if (isSync) {
185 AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
186 seekablePoints.push_back(timeStamp);
187 }
188 buffer->release();
189 }
190 }
191 }
192
TEST_P(ExtractorUnitTest,CreateExtractorTest)193 TEST_P(ExtractorUnitTest, CreateExtractorTest) {
194 if (mDisableTest) return;
195
196 ALOGV("Checks if a valid extractor is created for a given input file");
197 string inputFileName = gEnv->getRes() + GetParam().second;
198
199 ASSERT_EQ(setDataSource(inputFileName), 0)
200 << "SetDataSource failed for" << GetParam().first << "extractor";
201
202 ASSERT_EQ(createExtractor(), 0)
203 << "Extractor creation failed for" << GetParam().first << "extractor";
204
205 // A valid extractor instace should return success for following calls
206 ASSERT_GT(mExtractor->countTracks(), 0);
207
208 AMediaFormat *format = AMediaFormat_new();
209 ASSERT_NE(format, nullptr) << "AMediaFormat_new returned null AMediaformat";
210
211 ASSERT_EQ(mExtractor->getMetaData(format), AMEDIA_OK);
212 AMediaFormat_delete(format);
213 }
214
TEST_P(ExtractorUnitTest,ExtractorTest)215 TEST_P(ExtractorUnitTest, ExtractorTest) {
216 if (mDisableTest) return;
217
218 ALOGV("Validates %s Extractor for a given input file", GetParam().first.c_str());
219 string inputFileName = gEnv->getRes() + GetParam().second;
220
221 int32_t status = setDataSource(inputFileName);
222 ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
223
224 status = createExtractor();
225 ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
226
227 int32_t numTracks = mExtractor->countTracks();
228 ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
229
230 for (int32_t idx = 0; idx < numTracks; idx++) {
231 MediaTrackHelper *track = mExtractor->getTrack(idx);
232 ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
233
234 CMediaTrack *cTrack = wrap(track);
235 ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
236
237 MediaBufferGroup *bufferGroup = new MediaBufferGroup();
238 status = cTrack->start(track, bufferGroup->wrap());
239 ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
240
241 FILE *outFp = fopen((OUTPUT_DUMP_FILE + to_string(idx)).c_str(), "wb");
242 if (!outFp) {
243 ALOGW("Unable to open output file for dumping extracted stream");
244 }
245
246 while (status != AMEDIA_ERROR_END_OF_STREAM) {
247 MediaBufferHelper *buffer = nullptr;
248 status = track->read(&buffer);
249 ALOGV("track->read Status = %d buffer %p", status, buffer);
250 if (buffer) {
251 ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
252 buffer->data(), buffer->size(), buffer->range_length());
253 if (outFp) fwrite(buffer->data(), 1, buffer->range_length(), outFp);
254 buffer->release();
255 }
256 }
257 if (outFp) fclose(outFp);
258 status = cTrack->stop(track);
259 ASSERT_EQ(OK, status) << "Failed to stop the track";
260 delete bufferGroup;
261 delete track;
262 }
263 }
264
TEST_P(ExtractorUnitTest,MetaDataComparisonTest)265 TEST_P(ExtractorUnitTest, MetaDataComparisonTest) {
266 if (mDisableTest) return;
267
268 ALOGV("Validates Extractor's meta data for a given input file");
269 string inputFileName = gEnv->getRes() + GetParam().second;
270
271 int32_t status = setDataSource(inputFileName);
272 ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
273
274 status = createExtractor();
275 ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
276
277 int32_t numTracks = mExtractor->countTracks();
278 ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
279
280 AMediaFormat *extractorFormat = AMediaFormat_new();
281 ASSERT_NE(extractorFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
282 AMediaFormat *trackFormat = AMediaFormat_new();
283 ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null AMediaformat";
284
285 for (int32_t idx = 0; idx < numTracks; idx++) {
286 MediaTrackHelper *track = mExtractor->getTrack(idx);
287 ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
288
289 CMediaTrack *cTrack = wrap(track);
290 ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
291
292 MediaBufferGroup *bufferGroup = new MediaBufferGroup();
293 status = cTrack->start(track, bufferGroup->wrap());
294 ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
295
296 status = mExtractor->getTrackMetaData(extractorFormat, idx, 1);
297 ASSERT_EQ(OK, (media_status_t)status) << "Failed to get trackMetaData";
298
299 status = track->getFormat(trackFormat);
300 ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
301
302 const char *extractorMime, *trackMime;
303 AMediaFormat_getString(extractorFormat, AMEDIAFORMAT_KEY_MIME, &extractorMime);
304 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &trackMime);
305 ASSERT_TRUE(!strcmp(extractorMime, trackMime))
306 << "Extractor's format doesn't match track format";
307
308 if (!strncmp(extractorMime, "audio/", 6)) {
309 int32_t exSampleRate, exChannelCount;
310 int32_t trackSampleRate, trackChannelCount;
311 ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
312 &exChannelCount));
313 ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
314 &exSampleRate));
315 ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
316 &trackChannelCount));
317 ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE,
318 &trackSampleRate));
319 ASSERT_EQ(exChannelCount, trackChannelCount) << "ChannelCount not as expected";
320 ASSERT_EQ(exSampleRate, trackSampleRate) << "SampleRate not as expected";
321 } else {
322 int32_t exWidth, exHeight;
323 int32_t trackWidth, trackHeight;
324 ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_WIDTH, &exWidth));
325 ASSERT_TRUE(AMediaFormat_getInt32(extractorFormat, AMEDIAFORMAT_KEY_HEIGHT, &exHeight));
326 ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_WIDTH, &trackWidth));
327 ASSERT_TRUE(AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_HEIGHT, &trackHeight));
328 ASSERT_EQ(exWidth, trackWidth) << "Width not as expected";
329 ASSERT_EQ(exHeight, trackHeight) << "Height not as expected";
330 }
331 status = cTrack->stop(track);
332 ASSERT_EQ(OK, status) << "Failed to stop the track";
333 delete bufferGroup;
334 delete track;
335 }
336 AMediaFormat_delete(trackFormat);
337 AMediaFormat_delete(extractorFormat);
338 }
339
TEST_P(ExtractorUnitTest,MultipleStartStopTest)340 TEST_P(ExtractorUnitTest, MultipleStartStopTest) {
341 if (mDisableTest) return;
342
343 ALOGV("Test %s extractor for multiple start and stop calls", GetParam().first.c_str());
344 string inputFileName = gEnv->getRes() + GetParam().second;
345
346 int32_t status = setDataSource(inputFileName);
347 ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
348
349 status = createExtractor();
350 ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
351
352 int32_t numTracks = mExtractor->countTracks();
353 ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
354
355 // start/stop the tracks multiple times
356 for (int32_t count = 0; count < kMaxCount; count++) {
357 for (int32_t idx = 0; idx < numTracks; idx++) {
358 MediaTrackHelper *track = mExtractor->getTrack(idx);
359 ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
360
361 CMediaTrack *cTrack = wrap(track);
362 ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
363
364 MediaBufferGroup *bufferGroup = new MediaBufferGroup();
365 status = cTrack->start(track, bufferGroup->wrap());
366 ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
367 MediaBufferHelper *buffer = nullptr;
368 status = track->read(&buffer);
369 if (buffer) {
370 ALOGV("buffer->data %p buffer->size() %zu buffer->range_length() %zu",
371 buffer->data(), buffer->size(), buffer->range_length());
372 buffer->release();
373 }
374 status = cTrack->stop(track);
375 ASSERT_EQ(OK, status) << "Failed to stop the track";
376 delete bufferGroup;
377 delete track;
378 }
379 }
380 }
381
TEST_P(ExtractorUnitTest,SeekTest)382 TEST_P(ExtractorUnitTest, SeekTest) {
383 // Both Flac and Wav extractor can give samples from any pts and mark the given sample as
384 // sync frame. So, this seek test is not applicable to FLAC and WAV extractors
385 if (mDisableTest || mExtractorName == FLAC || mExtractorName == WAV) return;
386
387 ALOGV("Validates %s Extractor behaviour for different seek modes", GetParam().first.c_str());
388 string inputFileName = gEnv->getRes() + GetParam().second;
389
390 int32_t status = setDataSource(inputFileName);
391 ASSERT_EQ(status, 0) << "SetDataSource failed for" << GetParam().first << "extractor";
392
393 status = createExtractor();
394 ASSERT_EQ(status, 0) << "Extractor creation failed for" << GetParam().first << "extractor";
395
396 int32_t numTracks = mExtractor->countTracks();
397 ASSERT_GT(numTracks, 0) << "Extractor didn't find any track for the given clip";
398
399 uint32_t seekFlag = mExtractor->flags();
400 if (!(seekFlag & MediaExtractorPluginHelper::CAN_SEEK)) {
401 cout << "[ WARN ] Test Skipped. " << GetParam().first
402 << " Extractor doesn't support seek\n";
403 return;
404 }
405
406 vector<int64_t> seekablePoints;
407 for (int32_t idx = 0; idx < numTracks; idx++) {
408 MediaTrackHelper *track = mExtractor->getTrack(idx);
409 ASSERT_NE(track, nullptr) << "Failed to get track for index " << idx;
410
411 CMediaTrack *cTrack = wrap(track);
412 ASSERT_NE(cTrack, nullptr) << "Failed to get track wrapper for index " << idx;
413
414 // Get all the seekable points of a given input
415 MediaBufferGroup *bufferGroup = new MediaBufferGroup();
416 status = cTrack->start(track, bufferGroup->wrap());
417 ASSERT_EQ(OK, (media_status_t)status) << "Failed to start the track";
418 getSeekablePoints(seekablePoints, track);
419 ASSERT_GT(seekablePoints.size(), 0)
420 << "Failed to get seekable points for " << GetParam().first << " extractor";
421
422 AMediaFormat *trackFormat = AMediaFormat_new();
423 ASSERT_NE(trackFormat, nullptr) << "AMediaFormat_new returned null format";
424 status = track->getFormat(trackFormat);
425 ASSERT_EQ(OK, (media_status_t)status) << "Failed to get track meta data";
426
427 bool isOpus = false;
428 const char *mime;
429 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
430 if (!strcmp(mime, "audio/opus")) isOpus = true;
431 AMediaFormat_delete(trackFormat);
432
433 int32_t seekIdx = 0;
434 size_t seekablePointsSize = seekablePoints.size();
435 for (int32_t mode = CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC;
436 mode <= CMediaTrackReadOptions::SEEK_CLOSEST; mode++) {
437 for (int32_t seekCount = 0; seekCount < kMaxCount; seekCount++) {
438 seekIdx = rand() % seekablePointsSize + 1;
439 if (seekIdx >= seekablePointsSize) seekIdx = seekablePointsSize - 1;
440
441 int64_t seekToTimeStamp = seekablePoints[seekIdx];
442 if (seekablePointsSize > 1) {
443 int64_t prevTimeStamp = seekablePoints[seekIdx - 1];
444 seekToTimeStamp = seekToTimeStamp - ((seekToTimeStamp - prevTimeStamp) >> 3);
445 }
446
447 // Opus has a seekPreRollUs. TimeStamp returned by the
448 // extractor is calculated based on (seekPts - seekPreRollUs).
449 // So we add the preRoll value to the timeStamp we want to seek to.
450 if (isOpus) {
451 seekToTimeStamp += kOpusSeekPreRollUs;
452 }
453
454 MediaTrackHelper::ReadOptions *options = new MediaTrackHelper::ReadOptions(
455 mode | CMediaTrackReadOptions::SEEK, seekToTimeStamp);
456 ASSERT_NE(options, nullptr) << "Cannot create read option";
457
458 MediaBufferHelper *buffer = nullptr;
459 status = track->read(&buffer, options);
460 if (status == AMEDIA_ERROR_END_OF_STREAM) {
461 delete options;
462 continue;
463 }
464 if (buffer) {
465 AMediaFormat *metaData = buffer->meta_data();
466 int64_t timeStamp;
467 AMediaFormat_getInt64(metaData, AMEDIAFORMAT_KEY_TIME_US, &timeStamp);
468 buffer->release();
469
470 // CMediaTrackReadOptions::SEEK is 8. Using mask 0111b to get true modes
471 switch (mode & 0x7) {
472 case CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC:
473 if (seekablePointsSize == 1) {
474 EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
475 } else {
476 EXPECT_EQ(timeStamp, seekablePoints[seekIdx - 1]);
477 }
478 break;
479 case CMediaTrackReadOptions::SEEK_NEXT_SYNC:
480 case CMediaTrackReadOptions::SEEK_CLOSEST_SYNC:
481 case CMediaTrackReadOptions::SEEK_CLOSEST:
482 EXPECT_EQ(timeStamp, seekablePoints[seekIdx]);
483 break;
484 default:
485 break;
486 }
487 }
488 delete options;
489 }
490 }
491 status = cTrack->stop(track);
492 ASSERT_EQ(OK, status) << "Failed to stop the track";
493 delete bufferGroup;
494 delete track;
495 }
496 seekablePoints.clear();
497 }
498
499 // TODO: (b/145332185)
500 // Add MIDI inputs
501 INSTANTIATE_TEST_SUITE_P(ExtractorUnitTestAll, ExtractorUnitTest,
502 ::testing::Values(make_pair("aac", "loudsoftaac.aac"),
503 make_pair("amr", "testamr.amr"),
504 make_pair("amr", "amrwb.wav"),
505 make_pair("ogg", "john_cage.ogg"),
506 make_pair("wav", "monotestgsm.wav"),
507 make_pair("mpeg2ts", "segment000001.ts"),
508 make_pair("flac", "sinesweepflac.flac"),
509 make_pair("ogg", "testopus.opus"),
510 make_pair("mkv", "sinesweepvorbis.mkv"),
511 make_pair("mpeg4", "sinesweepoggmp4.mp4"),
512 make_pair("mp3", "sinesweepmp3lame.mp3"),
513 make_pair("mkv", "swirl_144x136_vp9.webm"),
514 make_pair("mkv", "swirl_144x136_vp8.webm"),
515 make_pair("mpeg2ps", "swirl_144x136_mpeg2.mpg"),
516 make_pair("mpeg4", "swirl_132x130_mpeg4.mp4")));
517
main(int argc,char ** argv)518 int main(int argc, char **argv) {
519 gEnv = new ExtractorUnitTestEnvironment();
520 ::testing::AddGlobalTestEnvironment(gEnv);
521 ::testing::InitGoogleTest(&argc, argv);
522 int status = gEnv->initFromOptions(argc, argv);
523 if (status == 0) {
524 status = RUN_ALL_TESTS();
525 ALOGV("Test result = %d\n", status);
526 }
527 return status;
528 }
529