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