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