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 "NativeExtractorTest"
19 #include <log/log.h>
20
21 #include <NdkMediaExtractor.h>
22 #include <jni.h>
23 #include <sys/stat.h>
24
25 #include <cstdlib>
26 #include <random>
27
28 #include "NativeMediaCommon.h"
29
isExtractorOKonEOS(AMediaExtractor * extractor)30 static bool isExtractorOKonEOS(AMediaExtractor* extractor) {
31 return AMediaExtractor_getSampleTrackIndex(extractor) < 0 &&
32 AMediaExtractor_getSampleSize(extractor) < 0 &&
33 (int)AMediaExtractor_getSampleFlags(extractor) < 0 &&
34 AMediaExtractor_getSampleTime(extractor) < 0;
35 }
36
isSampleInfoIdentical(AMediaCodecBufferInfo * refSample,AMediaCodecBufferInfo * testSample)37 static bool isSampleInfoIdentical(AMediaCodecBufferInfo* refSample,
38 AMediaCodecBufferInfo* testSample) {
39 return refSample->flags == testSample->flags && refSample->size == testSample->size &&
40 refSample->presentationTimeUs == testSample->presentationTimeUs;
41 }
42
isSampleInfoValidAndIdentical(AMediaCodecBufferInfo * refSample,AMediaCodecBufferInfo * testSample)43 static bool isSampleInfoValidAndIdentical(AMediaCodecBufferInfo* refSample,
44 AMediaCodecBufferInfo* testSample) {
45 return refSample->flags == testSample->flags && refSample->size == testSample->size &&
46 abs(refSample->presentationTimeUs - testSample->presentationTimeUs) <= 1 &&
47 (int)refSample->flags >= 0 && refSample->size >= 0 && refSample->presentationTimeUs >= 0;
48 }
49
isFormatSimilar(AMediaFormat * refFormat,AMediaFormat * testFormat)50 static bool isFormatSimilar(AMediaFormat* refFormat, AMediaFormat* testFormat) {
51 const char *refMime = nullptr, *testMime = nullptr;
52 bool hasRefMime = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
53 bool hasTestMime = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
54
55 if (!hasRefMime || !hasTestMime || strcmp(refMime, testMime) != 0) return false;
56 if (!isCSDIdentical(refFormat, testFormat)) return false;
57 if (!strncmp(refMime, "audio/", strlen("audio/"))) {
58 int32_t refSampleRate, testSampleRate, refNumChannels, testNumChannels;
59 bool hasRefSampleRate =
60 AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate);
61 bool hasTestSampleRate =
62 AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &testSampleRate);
63 bool hasRefNumChannels =
64 AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels);
65 bool hasTestNumChannels =
66 AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &testNumChannels);
67 return hasRefSampleRate && hasTestSampleRate && hasRefNumChannels && hasTestNumChannels &&
68 refNumChannels == testNumChannels && refSampleRate == testSampleRate;
69 } else if (!strncmp(refMime, "video/", strlen("video/"))) {
70 int32_t refWidth, testWidth, refHeight, testHeight;
71 bool hasRefWidth = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_WIDTH, &refWidth);
72 bool hasTestWidth = AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_WIDTH, &testWidth);
73 bool hasRefHeight = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_HEIGHT, &refHeight);
74 bool hasTestHeight =
75 AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_HEIGHT, &testHeight);
76 return hasRefWidth && hasTestWidth && hasRefHeight && hasTestHeight &&
77 refWidth == testWidth && refHeight == testHeight;
78 }
79 return true;
80 }
81
setSampleInfo(AMediaExtractor * extractor,AMediaCodecBufferInfo * info)82 static void inline setSampleInfo(AMediaExtractor* extractor, AMediaCodecBufferInfo* info) {
83 info->flags = AMediaExtractor_getSampleFlags(extractor);
84 info->offset = 0;
85 info->size = AMediaExtractor_getSampleSize(extractor);
86 info->presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
87 }
88
isMediaSimilar(AMediaExtractor * refExtractor,AMediaExtractor * testExtractor,const char * mime,int sampleLimit=INT32_MAX)89 static bool isMediaSimilar(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor,
90 const char* mime, int sampleLimit = INT32_MAX) {
91 const int maxSampleSize = (4 * 1024 * 1024);
92 auto refBuffer = new uint8_t[maxSampleSize];
93 auto testBuffer = new uint8_t[maxSampleSize];
94 int noOfTracksMatched = 0;
95 for (size_t refTrackID = 0; refTrackID < AMediaExtractor_getTrackCount(refExtractor);
96 refTrackID++) {
97 AMediaFormat* refFormat = AMediaExtractor_getTrackFormat(refExtractor, refTrackID);
98 const char* refMime = nullptr;
99 bool hasKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
100 if (!hasKey || (mime != nullptr && strcmp(refMime, mime) != 0)) {
101 AMediaFormat_delete(refFormat);
102 continue;
103 }
104 for (size_t testTrackID = 0; testTrackID < AMediaExtractor_getTrackCount(testExtractor);
105 testTrackID++) {
106 AMediaFormat* testFormat = AMediaExtractor_getTrackFormat(testExtractor, testTrackID);
107 if (!isFormatSimilar(refFormat, testFormat)) {
108 AMediaFormat_delete(testFormat);
109 continue;
110 }
111 AMediaExtractor_selectTrack(refExtractor, refTrackID);
112 AMediaExtractor_selectTrack(testExtractor, testTrackID);
113
114 AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
115 bool areTracksIdentical = true;
116 for (int frameCount = 0;; frameCount++) {
117 setSampleInfo(refExtractor, &refSampleInfo);
118 setSampleInfo(testExtractor, &testSampleInfo);
119 if (!isSampleInfoValidAndIdentical(&refSampleInfo, &testSampleInfo)) {
120 ALOGD(" Mime: %s mismatch for sample: %d", refMime, frameCount);
121 ALOGD(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
122 ALOGD(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
123 ALOGD(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
124 (int)testSampleInfo.presentationTimeUs);
125 areTracksIdentical = false;
126 break;
127 }
128 ssize_t refSz =
129 AMediaExtractor_readSampleData(refExtractor, refBuffer, maxSampleSize);
130 if (refSz != refSampleInfo.size) {
131 ALOGD("Mime: %s Size exp/got: %d / %zd ", refMime, refSampleInfo.size, refSz);
132 areTracksIdentical = false;
133 break;
134 }
135 ssize_t testSz =
136 AMediaExtractor_readSampleData(testExtractor, testBuffer, maxSampleSize);
137 if (testSz != testSampleInfo.size) {
138 ALOGD("Mime: %s Size exp/got: %d / %zd ", refMime, testSampleInfo.size,
139 testSz);
140 areTracksIdentical = false;
141 break;
142 }
143 int trackIndex = AMediaExtractor_getSampleTrackIndex(refExtractor);
144 if (trackIndex != refTrackID) {
145 ALOGD("Mime: %s TrackID exp/got: %zu / %d", refMime, refTrackID, trackIndex);
146 areTracksIdentical = false;
147 break;
148 }
149 trackIndex = AMediaExtractor_getSampleTrackIndex(testExtractor);
150 if (trackIndex != testTrackID) {
151 ALOGD("Mime: %s TrackID exp/got %zd / %d : ", refMime, testTrackID,
152 trackIndex);
153 areTracksIdentical = false;
154 break;
155 }
156 if (memcmp(refBuffer, testBuffer, refSz)) {
157 ALOGD("Mime: %s Mismatch in sample data", refMime);
158 areTracksIdentical = false;
159 break;
160 }
161 bool haveRefSamples = AMediaExtractor_advance(refExtractor);
162 bool haveTestSamples = AMediaExtractor_advance(testExtractor);
163 if (haveRefSamples != haveTestSamples) {
164 ALOGD("Mime: %s Mismatch in sampleCount", refMime);
165 areTracksIdentical = false;
166 break;
167 }
168
169 if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
170 ALOGD("Mime: %s calls post advance() are not OK", refMime);
171 areTracksIdentical = false;
172 break;
173 }
174 if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
175 ALOGD("Mime: %s calls post advance() are not OK", refMime);
176 areTracksIdentical = false;
177 break;
178 }
179 ALOGV("Mime: %s Sample: %d flags: %d size: %d ts: %d", refMime, frameCount,
180 refSampleInfo.flags, refSampleInfo.size,
181 (int)refSampleInfo.presentationTimeUs);
182 if (!haveRefSamples || frameCount >= sampleLimit) {
183 break;
184 }
185 }
186 AMediaExtractor_unselectTrack(testExtractor, testTrackID);
187 AMediaExtractor_unselectTrack(refExtractor, refTrackID);
188 AMediaFormat_delete(testFormat);
189 if (areTracksIdentical) {
190 noOfTracksMatched++;
191 break;
192 }
193 }
194 AMediaFormat_delete(refFormat);
195 if (mime != nullptr && noOfTracksMatched > 0) break;
196 }
197 delete[] refBuffer;
198 delete[] testBuffer;
199 if (mime == nullptr) {
200 return noOfTracksMatched == AMediaExtractor_getTrackCount(refExtractor);
201 } else {
202 return noOfTracksMatched > 0;
203 }
204 }
205
validateCachedDuration(AMediaExtractor * extractor,bool isNetworkSource)206 static bool validateCachedDuration(AMediaExtractor* extractor, bool isNetworkSource) {
207 if (isNetworkSource) {
208 AMediaExtractor_selectTrack(extractor, 0);
209 for (unsigned cnt = 0;; cnt++) {
210 if ((cnt & (cnt - 1)) == 0) {
211 if (AMediaExtractor_getCachedDuration(extractor) < 0) {
212 ALOGE("getCachedDuration is less than zero for network source");
213 return false;
214 }
215 }
216 if (!AMediaExtractor_advance(extractor)) break;
217 }
218 AMediaExtractor_unselectTrack(extractor, 0);
219 } else {
220 if (AMediaExtractor_getCachedDuration(extractor) != -1) {
221 ALOGE("getCachedDuration != -1 for non-network source");
222 return false;
223 }
224 }
225 return true;
226 }
227
createExtractorFromFD(FILE * fp)228 static AMediaExtractor* createExtractorFromFD(FILE* fp) {
229 AMediaExtractor* extractor = nullptr;
230 struct stat buf {};
231 if (fp && !fstat(fileno(fp), &buf)) {
232 extractor = AMediaExtractor_new();
233 media_status_t res = AMediaExtractor_setDataSourceFd(extractor, fileno(fp), 0, buf.st_size);
234 if (res != AMEDIA_OK) {
235 AMediaExtractor_delete(extractor);
236 extractor = nullptr;
237 }
238 }
239 return extractor;
240 }
241
242 // content necessary for testing seek are grouped in this class
243 class SeekTestParams {
244 public:
SeekTestParams(AMediaCodecBufferInfo expected,int64_t timeStamp,SeekMode mode)245 SeekTestParams(AMediaCodecBufferInfo expected, int64_t timeStamp, SeekMode mode)
246 : mExpected{expected}, mTimeStamp{timeStamp}, mMode{mode} {}
247
248 AMediaCodecBufferInfo mExpected;
249 int64_t mTimeStamp;
250 SeekMode mMode;
251 };
252
getSeekablePoints(const char * srcFile,const char * mime)253 static std::vector<AMediaCodecBufferInfo*> getSeekablePoints(const char* srcFile,
254 const char* mime) {
255 std::vector<AMediaCodecBufferInfo*> bookmarks;
256 if (mime == nullptr) return bookmarks;
257 FILE* srcFp = fopen(srcFile, "rbe");
258 if (!srcFp) {
259 ALOGE("fopen failed for srcFile %s", srcFile);
260 return bookmarks;
261 }
262 AMediaExtractor* extractor = createExtractorFromFD(srcFp);
263 if (!extractor) {
264 if (srcFp) fclose(srcFp);
265 ALOGE("createExtractorFromFD failed");
266 return bookmarks;
267 }
268
269 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
270 AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
271 const char* currMime = nullptr;
272 bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
273 if (!hasKey || strcmp(currMime, mime) != 0) {
274 AMediaFormat_delete(format);
275 continue;
276 }
277 AMediaExtractor_selectTrack(extractor, trackID);
278 do {
279 uint32_t sampleFlags = AMediaExtractor_getSampleFlags(extractor);
280 if ((sampleFlags & AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC) != 0) {
281 auto sampleInfo = new AMediaCodecBufferInfo;
282 setSampleInfo(extractor, sampleInfo);
283 bookmarks.push_back(sampleInfo);
284 }
285 } while (AMediaExtractor_advance(extractor));
286 AMediaExtractor_unselectTrack(extractor, trackID);
287 AMediaFormat_delete(format);
288 break;
289 }
290 AMediaExtractor_delete(extractor);
291 if (srcFp) fclose(srcFp);
292 return bookmarks;
293 }
294
295 static constexpr unsigned kSeed = 0x7ab7;
296
generateSeekTestArgs(const char * srcFile,const char * mime,bool isRandom)297 static std::vector<SeekTestParams*> generateSeekTestArgs(const char* srcFile, const char* mime,
298 bool isRandom) {
299 std::vector<SeekTestParams*> testArgs;
300 if (mime == nullptr) return testArgs;
301 const int MAX_SEEK_POINTS = 7;
302 std::srand(kSeed);
303 if (isRandom) {
304 FILE* srcFp = fopen(srcFile, "rbe");
305 if (!srcFp) {
306 ALOGE("fopen failed for srcFile %s", srcFile);
307 return testArgs;
308 }
309 AMediaExtractor* extractor = createExtractorFromFD(srcFp);
310 if (!extractor) {
311 if (srcFp) fclose(srcFp);
312 ALOGE("createExtractorFromFD failed");
313 return testArgs;
314 }
315
316 const int64_t maxEstDuration = 4000000;
317 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
318 AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
319 const char* currMime = nullptr;
320 bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
321 if (!hasKey || strcmp(currMime, mime) != 0) {
322 AMediaFormat_delete(format);
323 continue;
324 }
325 AMediaExtractor_selectTrack(extractor, trackID);
326 for (int i = 0; i < MAX_SEEK_POINTS; i++) {
327 double r = ((double)rand() / (RAND_MAX));
328 long pts = (long)(r * maxEstDuration);
329
330 for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
331 mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
332 AMediaExtractor_seekTo(extractor, pts, (SeekMode)mode);
333 AMediaCodecBufferInfo currInfo;
334 setSampleInfo(extractor, &currInfo);
335 testArgs.push_back((new SeekTestParams(currInfo, pts, (SeekMode)mode)));
336 }
337 }
338 AMediaExtractor_unselectTrack(extractor, trackID);
339 AMediaFormat_delete(format);
340 break;
341 }
342 AMediaExtractor_delete(extractor);
343 if (srcFp) fclose(srcFp);
344 } else {
345 std::vector<AMediaCodecBufferInfo*> bookmarks = getSeekablePoints(srcFile, mime);
346 if (bookmarks.empty()) return testArgs;
347 int size = bookmarks.size();
348 int* indices;
349 int indexSize = 0;
350 if (size > MAX_SEEK_POINTS) {
351 indices = new int[MAX_SEEK_POINTS];
352 indexSize = MAX_SEEK_POINTS;
353 indices[0] = 0;
354 indices[MAX_SEEK_POINTS - 1] = size - 1;
355 for (int i = 1; i < MAX_SEEK_POINTS - 1; i++) {
356 double r = ((double)rand() / (RAND_MAX));
357 indices[i] = (int)(r * (MAX_SEEK_POINTS - 1) + 1);
358 }
359 } else {
360 indices = new int[size];
361 indexSize = size;
362 for (int i = 0; i < size; i++) indices[i] = i;
363 }
364 for (int i = 0; i < indexSize; i++) {
365 AMediaCodecBufferInfo currInfo = *bookmarks[i];
366 int64_t pts = currInfo.presentationTimeUs;
367 testArgs.push_back(
368 (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
369 testArgs.push_back((new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
370 testArgs.push_back(
371 (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
372 if (i > 0) {
373 AMediaCodecBufferInfo prevInfo = *bookmarks[i - 1];
374 int64_t ptsMinus = prevInfo.presentationTimeUs;
375 ptsMinus = pts - ((pts - ptsMinus) >> 3);
376 testArgs.push_back((
377 new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
378 testArgs.push_back(
379 (new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
380 testArgs.push_back((new SeekTestParams(prevInfo, ptsMinus,
381 AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
382 }
383 if (i < size - 1) {
384 AMediaCodecBufferInfo nextInfo = *bookmarks[i + 1];
385 int64_t ptsPlus = nextInfo.presentationTimeUs;
386 ptsPlus = pts + ((ptsPlus - pts) >> 3);
387 testArgs.push_back(
388 (new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
389 testArgs.push_back(
390 (new SeekTestParams(nextInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
391 testArgs.push_back((
392 new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
393 }
394 }
395 for (auto bookmark : bookmarks) {
396 delete bookmark;
397 }
398 bookmarks.clear();
399 delete[] indices;
400 }
401 return testArgs;
402 }
403
checkSeekPoints(const char * srcFile,const char * mime,const std::vector<SeekTestParams * > & seekTestArgs)404 static int checkSeekPoints(const char* srcFile, const char* mime,
405 const std::vector<SeekTestParams*>& seekTestArgs) {
406 int errCnt = 0;
407 FILE* srcFp = fopen(srcFile, "rbe");
408 AMediaExtractor* extractor = createExtractorFromFD(srcFp);
409 if (!extractor) {
410 if (srcFp) fclose(srcFp);
411 ALOGE("createExtractorFromFD failed");
412 return -1;
413 }
414 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
415 AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
416 const char* currMime = nullptr;
417 bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
418 if (!hasKey || strcmp(currMime, mime) != 0) {
419 continue;
420 }
421 AMediaExtractor_selectTrack(extractor, trackID);
422 AMediaCodecBufferInfo received;
423 for (auto arg : seekTestArgs) {
424 AMediaExtractor_seekTo(extractor, arg->mTimeStamp, arg->mMode);
425 setSampleInfo(extractor, &received);
426 if (!isSampleInfoIdentical(&arg->mExpected, &received)) {
427 ALOGE(" flags exp/got: %d / %d", arg->mExpected.flags, received.flags);
428 ALOGE(" size exp/got: %d / %d ", arg->mExpected.size, received.size);
429 ALOGE(" ts exp/got: %d / %d ", (int)arg->mExpected.presentationTimeUs,
430 (int)received.presentationTimeUs);
431 errCnt++;
432 }
433 }
434 AMediaExtractor_unselectTrack(extractor, trackID);
435 AMediaFormat_delete(format);
436 break;
437 }
438 AMediaExtractor_delete(extractor);
439 if (srcFp) fclose(srcFp);
440 return errCnt;
441 }
442
isFileFormatIdentical(AMediaExtractor * refExtractor,AMediaExtractor * testExtractor)443 static bool isFileFormatIdentical(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
444 bool result = false;
445 if (refExtractor && testExtractor) {
446 AMediaFormat* refFormat = AMediaExtractor_getFileFormat(refExtractor);
447 AMediaFormat* testFormat = AMediaExtractor_getFileFormat(testExtractor);
448 if (refFormat && testFormat) {
449 const char *refMime = nullptr, *testMime = nullptr;
450 bool hasRefKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
451 bool hasTestKey = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
452 /* TODO: Not Sure if we need to verify any other parameter of file format */
453 if (hasRefKey && hasTestKey && strcmp(refMime, testMime) == 0) {
454 result = true;
455 } else {
456 ALOGE("file format exp/got : %s/%s", refMime, testMime);
457 }
458 }
459 if (refFormat) AMediaFormat_delete(refFormat);
460 if (testFormat) AMediaFormat_delete(testFormat);
461 }
462 return result;
463 }
464
isSeekOk(AMediaExtractor * refExtractor,AMediaExtractor * testExtractor)465 static bool isSeekOk(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
466 const long maxEstDuration = 14000000;
467 const int MAX_SEEK_POINTS = 7;
468 std::srand(kSeed);
469 AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
470 bool result = true;
471 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(refExtractor); trackID++) {
472 AMediaExtractor_selectTrack(refExtractor, trackID);
473 AMediaExtractor_selectTrack(testExtractor, trackID);
474 for (int i = 0; i < MAX_SEEK_POINTS && result; i++) {
475 double r = ((double)rand() / (RAND_MAX));
476 long pts = (long)(r * maxEstDuration);
477 for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
478 mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
479 AMediaExtractor_seekTo(refExtractor, pts, (SeekMode)mode);
480 AMediaExtractor_seekTo(testExtractor, pts, (SeekMode)mode);
481 setSampleInfo(refExtractor, &refSampleInfo);
482 setSampleInfo(testExtractor, &testSampleInfo);
483 result = isSampleInfoIdentical(&refSampleInfo, &testSampleInfo);
484 if (!result) {
485 ALOGE(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
486 ALOGE(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
487 ALOGE(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
488 (int)testSampleInfo.presentationTimeUs);
489 }
490 int refTrackIdx = AMediaExtractor_getSampleTrackIndex(refExtractor);
491 int testTrackIdx = AMediaExtractor_getSampleTrackIndex(testExtractor);
492 if (refTrackIdx != testTrackIdx) {
493 ALOGE("trackIdx exp/got: %d/%d ", refTrackIdx, testTrackIdx);
494 result = false;
495 }
496 }
497 }
498 AMediaExtractor_unselectTrack(refExtractor, trackID);
499 AMediaExtractor_unselectTrack(testExtractor, trackID);
500 }
501 return result;
502 }
503
nativeTestExtract(JNIEnv * env,jobject,jstring jsrcPath,jstring jrefPath,jstring jmime)504 static jboolean nativeTestExtract(JNIEnv* env, jobject, jstring jsrcPath, jstring jrefPath,
505 jstring jmime) {
506 bool isPass = false;
507 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
508 const char* ctestPath = env->GetStringUTFChars(jrefPath, nullptr);
509 const char* cmime = env->GetStringUTFChars(jmime, nullptr);
510 FILE* srcFp = fopen(csrcPath, "rbe");
511 AMediaExtractor* srcExtractor = createExtractorFromFD(srcFp);
512 FILE* testFp = fopen(ctestPath, "rbe");
513 AMediaExtractor* testExtractor = createExtractorFromFD(testFp);
514 if (srcExtractor && testExtractor) {
515 isPass = isMediaSimilar(srcExtractor, testExtractor, cmime);
516 if (!isPass) {
517 ALOGE(" Src and test are different from extractor perspective");
518 }
519 AMediaExtractor_delete(srcExtractor);
520 AMediaExtractor_delete(testExtractor);
521 }
522 if (srcFp) fclose(srcFp);
523 if (testFp) fclose(testFp);
524 env->ReleaseStringUTFChars(jmime, cmime);
525 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
526 env->ReleaseStringUTFChars(jrefPath, ctestPath);
527 return static_cast<jboolean>(isPass);
528 }
529
nativeTestSeek(JNIEnv * env,jobject,jstring jsrcPath,jstring jmime)530 static jboolean nativeTestSeek(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
531 bool isPass = false;
532 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
533 const char* cmime = env->GetStringUTFChars(jmime, nullptr);
534 std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, false);
535 if (!seekTestArgs.empty()) {
536 std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
537 int seekAccErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
538 if (seekAccErrCnt != 0) {
539 ALOGE("For %s seek chose inaccurate Sync point in: %d / %d", csrcPath, seekAccErrCnt,
540 (int)seekTestArgs.size());
541 isPass = false;
542 } else {
543 isPass = true;
544 }
545 for (auto seekTestArg : seekTestArgs) {
546 delete seekTestArg;
547 }
548 seekTestArgs.clear();
549 } else {
550 ALOGE("No sync samples found.");
551 }
552 env->ReleaseStringUTFChars(jmime, cmime);
553 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
554 return static_cast<jboolean>(isPass);
555 }
556
nativeTestSeekFlakiness(JNIEnv * env,jobject,jstring jsrcPath,jstring jmime)557 static jboolean nativeTestSeekFlakiness(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
558 bool isPass = false;
559 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
560 const char* cmime = env->GetStringUTFChars(jmime, nullptr);
561 std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, true);
562 if (!seekTestArgs.empty()) {
563 std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
564 int flakyErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
565 if (flakyErrCnt != 0) {
566 ALOGE("No. of Samples where seek showed flakiness is: %d", flakyErrCnt);
567 isPass = false;
568 } else {
569 isPass = true;
570 }
571 for (auto seekTestArg : seekTestArgs) {
572 delete seekTestArg;
573 }
574 seekTestArgs.clear();
575 } else {
576 ALOGE("No sync samples found.");
577 }
578 env->ReleaseStringUTFChars(jmime, cmime);
579 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
580 return static_cast<jboolean>(isPass);
581 }
582
nativeTestSeekToZero(JNIEnv * env,jobject,jstring jsrcPath,jstring jmime)583 static jboolean nativeTestSeekToZero(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
584 bool isPass = false;
585 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
586 const char* cmime = env->GetStringUTFChars(jmime, nullptr);
587 FILE* srcFp = fopen(csrcPath, "rbe");
588 AMediaExtractor* extractor = createExtractorFromFD(srcFp);
589 if (extractor) {
590 AMediaCodecBufferInfo sampleInfoAtZero;
591 AMediaCodecBufferInfo currInfo;
592 static long randomPts = 1 << 20;
593 for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
594 AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
595 if (format) {
596 const char* currMime = nullptr;
597 bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
598 if (!hasKey || strcmp(currMime, cmime) != 0) {
599 AMediaFormat_delete(format);
600 continue;
601 }
602 AMediaExtractor_selectTrack(extractor, trackID);
603 setSampleInfo(extractor, &sampleInfoAtZero);
604 AMediaExtractor_seekTo(extractor, randomPts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC);
605 AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
606 setSampleInfo(extractor, &currInfo);
607 isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
608 if (!isPass) {
609 ALOGE("seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
610 ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
611 ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
612 ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
613 (int)currInfo.presentationTimeUs);
614 AMediaFormat_delete(format);
615 break;
616 }
617 AMediaExtractor_seekTo(extractor, -1L, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
618 setSampleInfo(extractor, &currInfo);
619 isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
620 if (!isPass) {
621 ALOGE("seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
622 ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
623 ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
624 ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
625 (int)currInfo.presentationTimeUs);
626 AMediaFormat_delete(format);
627 break;
628 }
629 AMediaExtractor_unselectTrack(extractor, trackID);
630 AMediaFormat_delete(format);
631 }
632 }
633 AMediaExtractor_delete(extractor);
634 }
635 if (srcFp) fclose(srcFp);
636 env->ReleaseStringUTFChars(jmime, cmime);
637 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
638 return static_cast<jboolean>(isPass);
639 }
640
nativeTestFileFormat(JNIEnv * env,jobject,jstring jsrcPath)641 static jboolean nativeTestFileFormat(JNIEnv* env, jobject, jstring jsrcPath) {
642 bool isPass = false;
643 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
644 FILE* srcFp = fopen(csrcPath, "rbe");
645 AMediaExtractor* extractor = createExtractorFromFD(srcFp);
646 if (extractor) {
647 AMediaFormat* format = AMediaExtractor_getFileFormat(extractor);
648 const char* mime = nullptr;
649 bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
650 /* TODO: Not Sure if we need to verify any other parameter of file format */
651 if (hasKey && mime && strlen(mime) > 0) {
652 isPass = true;
653 }
654 AMediaFormat_delete(format);
655 AMediaExtractor_delete(extractor);
656 }
657 if (srcFp) fclose(srcFp);
658 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
659 return static_cast<jboolean>(isPass);
660 }
661
nativeTestDataSource(JNIEnv * env,jobject,jstring jsrcPath,jstring jsrcUrl)662 static jboolean nativeTestDataSource(JNIEnv* env, jobject, jstring jsrcPath, jstring jsrcUrl) {
663 bool isPass = true;
664 const char* csrcUrl = env->GetStringUTFChars(jsrcUrl, nullptr);
665 AMediaExtractor* refExtractor = AMediaExtractor_new();
666 media_status_t status = AMediaExtractor_setDataSource(refExtractor, csrcUrl);
667 if (status == AMEDIA_OK) {
668 isPass &= validateCachedDuration(refExtractor, true);
669 const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
670 AMediaDataSource* dataSource = AMediaDataSource_newUri(csrcUrl, 0, nullptr);
671 AMediaExtractor* testExtractor = AMediaExtractor_new();
672 status = AMediaExtractor_setDataSourceCustom(testExtractor, dataSource);
673 if (status != AMEDIA_OK) {
674 ALOGE("setDataSourceCustom failed");
675 isPass = false;
676 } else {
677 isPass &= validateCachedDuration(testExtractor, true);
678 if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
679 isFileFormatIdentical(refExtractor, testExtractor) &&
680 isSeekOk(refExtractor, testExtractor))) {
681 isPass = false;
682 }
683 }
684 if (testExtractor) AMediaExtractor_delete(testExtractor);
685 if (dataSource) AMediaDataSource_delete(dataSource);
686
687 FILE* testFp = fopen(csrcPath, "rbe");
688 testExtractor = createExtractorFromFD(testFp);
689 if (testExtractor == nullptr) {
690 ALOGE("createExtractorFromFD failed for test extractor");
691 isPass = false;
692 } else {
693 isPass &= validateCachedDuration(testExtractor, false);
694 if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
695 isFileFormatIdentical(refExtractor, testExtractor) &&
696 isSeekOk(refExtractor, testExtractor))) {
697 isPass = false;
698 }
699 }
700 if (testExtractor) AMediaExtractor_delete(testExtractor);
701 if (testFp) fclose(testFp);
702 env->ReleaseStringUTFChars(jsrcPath, csrcPath);
703 } else {
704 ALOGE("setDataSource failed");
705 isPass = false;
706 }
707 if (refExtractor) AMediaExtractor_delete(refExtractor);
708 env->ReleaseStringUTFChars(jsrcUrl, csrcUrl);
709 return static_cast<jboolean>(isPass);
710 }
711
registerAndroidMediaV2CtsExtractorTestSetDS(JNIEnv * env)712 int registerAndroidMediaV2CtsExtractorTestSetDS(JNIEnv* env) {
713 const JNINativeMethod methodTable[] = {
714 {"nativeTestDataSource", "(Ljava/lang/String;Ljava/lang/String;)Z",
715 (void*)nativeTestDataSource},
716 };
717 jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$SetDataSourceTest");
718 return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
719 }
720
registerAndroidMediaV2CtsExtractorTestFunc(JNIEnv * env)721 int registerAndroidMediaV2CtsExtractorTestFunc(JNIEnv* env) {
722 const JNINativeMethod methodTable[] = {
723 {"nativeTestExtract", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
724 (void*)nativeTestExtract},
725 {"nativeTestSeek", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeek},
726 {"nativeTestSeekFlakiness", "(Ljava/lang/String;Ljava/lang/String;)Z",
727 (void*)nativeTestSeekFlakiness},
728 {"nativeTestSeekToZero", "(Ljava/lang/String;Ljava/lang/String;)Z",
729 (void*)nativeTestSeekToZero},
730 {"nativeTestFileFormat", "(Ljava/lang/String;)Z", (void*)nativeTestFileFormat},
731 };
732 jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$FunctionalityTest");
733 return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
734 }
735
736 extern int registerAndroidMediaV2CtsExtractorUnitTestApi(JNIEnv* env);
737
JNI_OnLoad(JavaVM * vm,void *)738 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
739 JNIEnv* env;
740 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
741 if (registerAndroidMediaV2CtsExtractorTestSetDS(env) != JNI_OK) return JNI_ERR;
742 if (registerAndroidMediaV2CtsExtractorTestFunc(env) != JNI_OK) return JNI_ERR;
743 if (registerAndroidMediaV2CtsExtractorUnitTestApi(env) != JNI_OK) return JNI_ERR;
744 return JNI_VERSION_1_6;
745 }