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