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 }