/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "NativeExtractorTest" #include #include #include #include #include #include #include #include "NativeMediaCommon.h" #define CHECK_KEY(hasKey, format, isPass) \ if (!(hasKey)) { \ AMediaFormat_delete((format)); \ (isPass) = false; \ break; \ } static bool isExtractorOKonEOS(AMediaExtractor* extractor) { return AMediaExtractor_getSampleTrackIndex(extractor) < 0 && AMediaExtractor_getSampleSize(extractor) < 0 && (int)AMediaExtractor_getSampleFlags(extractor) < 0 && AMediaExtractor_getSampleTime(extractor) < 0; } static bool isSampleInfoIdentical(AMediaCodecBufferInfo* refSample, AMediaCodecBufferInfo* testSample) { return refSample->flags == testSample->flags && refSample->size == testSample->size && refSample->presentationTimeUs == testSample->presentationTimeUs; } static bool isSampleInfoValidAndIdentical(AMediaCodecBufferInfo* refSample, AMediaCodecBufferInfo* testSample) { return refSample->flags == testSample->flags && refSample->size == testSample->size && abs(refSample->presentationTimeUs - testSample->presentationTimeUs) <= 1 && (int)refSample->flags >= 0 && refSample->size >= 0 && refSample->presentationTimeUs >= 0; } static void inline setSampleInfo(AMediaExtractor* extractor, AMediaCodecBufferInfo* info) { info->flags = AMediaExtractor_getSampleFlags(extractor); info->offset = 0; info->size = AMediaExtractor_getSampleSize(extractor); info->presentationTimeUs = AMediaExtractor_getSampleTime(extractor); } static bool isMediaSimilar(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor, const char* mime, int sampleLimit = INT32_MAX) { const int maxSampleSize = (4 * 1024 * 1024); auto refBuffer = new uint8_t[maxSampleSize]; auto testBuffer = new uint8_t[maxSampleSize]; int noOfTracksMatched = 0; for (size_t refTrackID = 0; refTrackID < AMediaExtractor_getTrackCount(refExtractor); refTrackID++) { AMediaFormat* refFormat = AMediaExtractor_getTrackFormat(refExtractor, refTrackID); const char* refMime = nullptr; bool hasKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime); if (!hasKey || (mime != nullptr && strcmp(refMime, mime) != 0)) { AMediaFormat_delete(refFormat); continue; } for (size_t testTrackID = 0; testTrackID < AMediaExtractor_getTrackCount(testExtractor); testTrackID++) { AMediaFormat* testFormat = AMediaExtractor_getTrackFormat(testExtractor, testTrackID); if (!isFormatSimilar(refFormat, testFormat)) { AMediaFormat_delete(testFormat); continue; } AMediaExtractor_selectTrack(refExtractor, refTrackID); AMediaExtractor_selectTrack(testExtractor, testTrackID); AMediaCodecBufferInfo refSampleInfo, testSampleInfo; bool areTracksIdentical = true; for (int frameCount = 0;; frameCount++) { setSampleInfo(refExtractor, &refSampleInfo); setSampleInfo(testExtractor, &testSampleInfo); if (!isSampleInfoValidAndIdentical(&refSampleInfo, &testSampleInfo)) { ALOGD(" Mime: %s mismatch for sample: %d", mime, frameCount); ALOGD(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags); ALOGD(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size); ALOGD(" ts exp/got: %" PRId64 " / %" PRId64 "", refSampleInfo.presentationTimeUs, testSampleInfo.presentationTimeUs); areTracksIdentical = false; break; } ssize_t refSz = AMediaExtractor_readSampleData(refExtractor, refBuffer, maxSampleSize); if (refSz != refSampleInfo.size) { ALOGD("Mime: %s Size exp/got: %d / %zd ", mime, refSampleInfo.size, refSz); areTracksIdentical = false; break; } ssize_t testSz = AMediaExtractor_readSampleData(testExtractor, testBuffer, maxSampleSize); if (testSz != testSampleInfo.size) { ALOGD("Mime: %s Size exp/got: %d / %zd ", mime, testSampleInfo.size, testSz); areTracksIdentical = false; break; } int trackIndex = AMediaExtractor_getSampleTrackIndex(refExtractor); if (trackIndex != refTrackID) { ALOGD("Mime: %s TrackID exp/got: %zu / %d", mime, refTrackID, trackIndex); areTracksIdentical = false; break; } trackIndex = AMediaExtractor_getSampleTrackIndex(testExtractor); if (trackIndex != testTrackID) { ALOGD("Mime: %s TrackID exp/got %zd / %d : ", mime, testTrackID, trackIndex); areTracksIdentical = false; break; } if (memcmp(refBuffer, testBuffer, refSz)) { ALOGD("Mime: %s Mismatch in sample data", mime); areTracksIdentical = false; break; } bool haveRefSamples = AMediaExtractor_advance(refExtractor); bool haveTestSamples = AMediaExtractor_advance(testExtractor); if (haveRefSamples != haveTestSamples) { ALOGD("Mime: %s Mismatch in sampleCount", mime); areTracksIdentical = false; break; } if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) { ALOGD("Mime: %s calls post advance() are not OK", mime); areTracksIdentical = false; break; } if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) { ALOGD("Mime: %s calls post advance() are not OK", mime); areTracksIdentical = false; break; } ALOGV("Mime: %s Sample: %d flags: %d size: %d ts: % " PRId64 "", mime, frameCount, refSampleInfo.flags, refSampleInfo.size, refSampleInfo.presentationTimeUs); if (!haveRefSamples || frameCount >= sampleLimit) { break; } } AMediaExtractor_unselectTrack(testExtractor, testTrackID); AMediaExtractor_unselectTrack(refExtractor, refTrackID); AMediaFormat_delete(testFormat); if (areTracksIdentical) { noOfTracksMatched++; break; } } AMediaFormat_delete(refFormat); if (mime != nullptr && noOfTracksMatched > 0) break; } delete[] refBuffer; delete[] testBuffer; if (mime == nullptr) { return noOfTracksMatched == AMediaExtractor_getTrackCount(refExtractor); } else { return noOfTracksMatched > 0; } } static bool validateCachedDuration(AMediaExtractor* extractor, bool isNetworkSource) { if (isNetworkSource) { AMediaExtractor_selectTrack(extractor, 0); for (unsigned cnt = 0;; cnt++) { if ((cnt & (cnt - 1)) == 0) { if (AMediaExtractor_getCachedDuration(extractor) < 0) { ALOGE("getCachedDuration is less than zero for network source"); return false; } } if (!AMediaExtractor_advance(extractor)) break; } AMediaExtractor_unselectTrack(extractor, 0); } else { if (AMediaExtractor_getCachedDuration(extractor) != -1) { ALOGE("getCachedDuration != -1 for non-network source"); return false; } } return true; } static AMediaExtractor* createExtractorFromFD(FILE* fp) { AMediaExtractor* extractor = nullptr; struct stat buf {}; if (fp && !fstat(fileno(fp), &buf)) { extractor = AMediaExtractor_new(); media_status_t res = AMediaExtractor_setDataSourceFd(extractor, fileno(fp), 0, buf.st_size); if (res != AMEDIA_OK) { AMediaExtractor_delete(extractor); extractor = nullptr; } } return extractor; } static bool createExtractorFromUrl(JNIEnv* env, jobjectArray jkeys, jobjectArray jvalues, AMediaExtractor** ex, AMediaDataSource** ds, const char* url) { int numkeys = jkeys ? env->GetArrayLength(jkeys) : 0; int numvalues = jvalues ? env->GetArrayLength(jvalues) : 0; if (numkeys != numvalues) { ALOGE("Unequal number of keys and values"); return false; } const char** keyvalues = numkeys ? new const char*[numkeys * 2] : nullptr; for (int i = 0; i < numkeys; i++) { auto jkey = (jstring)(env->GetObjectArrayElement(jkeys, i)); auto jvalue = (jstring)(env->GetObjectArrayElement(jvalues, i)); const char* key = env->GetStringUTFChars(jkey, nullptr); const char* value = env->GetStringUTFChars(jvalue, nullptr); keyvalues[i * 2] = key; keyvalues[i * 2 + 1] = value; } *ex = AMediaExtractor_new(); *ds = AMediaDataSource_newUri(url, numkeys, keyvalues); bool isPass = *ds ? (AMEDIA_OK == AMediaExtractor_setDataSourceCustom(*ex, *ds)) : false; if (!isPass) ALOGE("setDataSourceCustom failed"); for (int i = 0; i < numkeys; i++) { auto jkey = (jstring)(env->GetObjectArrayElement(jkeys, i)); auto jvalue = (jstring)(env->GetObjectArrayElement(jvalues, i)); env->ReleaseStringUTFChars(jkey, keyvalues[i * 2]); env->ReleaseStringUTFChars(jvalue, keyvalues[i * 2 + 1]); } delete[] keyvalues; return isPass; } // content necessary for testing seek are grouped in this class class SeekTestParams { public: SeekTestParams(AMediaCodecBufferInfo expected, int64_t timeStamp, SeekMode mode) : mExpected{expected}, mTimeStamp{timeStamp}, mMode{mode} {} AMediaCodecBufferInfo mExpected; int64_t mTimeStamp; SeekMode mMode; }; static std::vector getSeekablePoints(const char* srcFile, const char* mime) { std::vector bookmarks; if (mime == nullptr) return bookmarks; FILE* srcFp = fopen(srcFile, "rbe"); if (!srcFp) { ALOGE("fopen failed for srcFile %s", srcFile); return bookmarks; } AMediaExtractor* extractor = createExtractorFromFD(srcFp); if (!extractor) { if (srcFp) fclose(srcFp); ALOGE("createExtractorFromFD failed"); return bookmarks; } for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) { AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); const char* currMime = nullptr; bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime); if (!hasKey || strcmp(currMime, mime) != 0) { AMediaFormat_delete(format); continue; } AMediaExtractor_selectTrack(extractor, trackID); do { uint32_t sampleFlags = AMediaExtractor_getSampleFlags(extractor); if ((sampleFlags & AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC) != 0) { auto sampleInfo = new AMediaCodecBufferInfo; setSampleInfo(extractor, sampleInfo); bookmarks.push_back(sampleInfo); } } while (AMediaExtractor_advance(extractor)); AMediaExtractor_unselectTrack(extractor, trackID); AMediaFormat_delete(format); break; } AMediaExtractor_delete(extractor); if (srcFp) fclose(srcFp); return bookmarks; } static constexpr unsigned kSeed = 0x7ab7; static std::vector generateSeekTestArgs(const char* srcFile, const char* mime, bool isRandom) { std::vector testArgs; if (mime == nullptr) return testArgs; const int MAX_SEEK_POINTS = 7; std::srand(kSeed); if (isRandom) { FILE* srcFp = fopen(srcFile, "rbe"); if (!srcFp) { ALOGE("fopen failed for srcFile %s", srcFile); return testArgs; } AMediaExtractor* extractor = createExtractorFromFD(srcFp); if (!extractor) { if (srcFp) fclose(srcFp); ALOGE("createExtractorFromFD failed"); return testArgs; } const int64_t maxEstDuration = 4000000; for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) { AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); const char* currMime = nullptr; bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime); if (!hasKey || strcmp(currMime, mime) != 0) { AMediaFormat_delete(format); continue; } AMediaExtractor_selectTrack(extractor, trackID); for (int i = 0; i < MAX_SEEK_POINTS; i++) { double r = ((double)rand() / (RAND_MAX)); long pts = (long)(r * maxEstDuration); for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC; mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) { AMediaExtractor_seekTo(extractor, pts, (SeekMode)mode); AMediaCodecBufferInfo currInfo; setSampleInfo(extractor, &currInfo); testArgs.push_back((new SeekTestParams(currInfo, pts, (SeekMode)mode))); } } AMediaExtractor_unselectTrack(extractor, trackID); AMediaFormat_delete(format); break; } AMediaExtractor_delete(extractor); if (srcFp) fclose(srcFp); } else { std::vector bookmarks = getSeekablePoints(srcFile, mime); if (bookmarks.empty()) return testArgs; int size = bookmarks.size(); int* indices; int indexSize = 0; if (size > MAX_SEEK_POINTS) { indices = new int[MAX_SEEK_POINTS]; indexSize = MAX_SEEK_POINTS; indices[0] = 0; indices[MAX_SEEK_POINTS - 1] = size - 1; for (int i = 1; i < MAX_SEEK_POINTS - 1; i++) { double r = ((double)rand() / (RAND_MAX)); indices[i] = (int)(r * (MAX_SEEK_POINTS - 1) + 1); } } else { indices = new int[size]; indexSize = size; for (int i = 0; i < size; i++) indices[i] = i; } for (int i = 0; i < indexSize; i++) { AMediaCodecBufferInfo currInfo = *bookmarks[i]; int64_t pts = currInfo.presentationTimeUs; testArgs.push_back( (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC))); testArgs.push_back((new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC))); testArgs.push_back( (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC))); if (i > 0) { AMediaCodecBufferInfo prevInfo = *bookmarks[i - 1]; int64_t ptsMinus = prevInfo.presentationTimeUs; ptsMinus = pts - ((pts - ptsMinus) >> 3); testArgs.push_back(( new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC))); testArgs.push_back( (new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC))); testArgs.push_back((new SeekTestParams(prevInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC))); } if (i < size - 1) { AMediaCodecBufferInfo nextInfo = *bookmarks[i + 1]; int64_t ptsPlus = nextInfo.presentationTimeUs; ptsPlus = pts + ((ptsPlus - pts) >> 3); testArgs.push_back( (new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC))); testArgs.push_back( (new SeekTestParams(nextInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC))); testArgs.push_back(( new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC))); } } for (auto bookmark : bookmarks) { delete bookmark; } bookmarks.clear(); delete[] indices; } return testArgs; } static int checkSeekPoints(const char* srcFile, const char* mime, const std::vector& seekTestArgs) { int errCnt = 0; FILE* srcFp = fopen(srcFile, "rbe"); AMediaExtractor* extractor = createExtractorFromFD(srcFp); if (!extractor) { if (srcFp) fclose(srcFp); ALOGE("createExtractorFromFD failed"); return -1; } for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) { AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); const char* currMime = nullptr; bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime); if (!hasKey || strcmp(currMime, mime) != 0) { AMediaFormat_delete(format); continue; } AMediaExtractor_selectTrack(extractor, trackID); AMediaCodecBufferInfo received; for (auto arg : seekTestArgs) { AMediaExtractor_seekTo(extractor, arg->mTimeStamp, arg->mMode); setSampleInfo(extractor, &received); if (!isSampleInfoIdentical(&arg->mExpected, &received)) { ALOGE(" flags exp/got: %d / %d", arg->mExpected.flags, received.flags); ALOGE(" size exp/got: %d / %d ", arg->mExpected.size, received.size); ALOGE(" ts exp/got: %" PRId64 " / %" PRId64 "", arg->mExpected.presentationTimeUs, received.presentationTimeUs); errCnt++; } } AMediaExtractor_unselectTrack(extractor, trackID); AMediaFormat_delete(format); break; } AMediaExtractor_delete(extractor); if (srcFp) fclose(srcFp); return errCnt; } static bool isFileFormatIdentical(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) { bool result = false; if (refExtractor && testExtractor) { AMediaFormat* refFormat = AMediaExtractor_getFileFormat(refExtractor); AMediaFormat* testFormat = AMediaExtractor_getFileFormat(testExtractor); if (refFormat && testFormat) { const char *refMime = nullptr, *testMime = nullptr; bool hasRefKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime); bool hasTestKey = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime); /* TODO: Not Sure if we need to verify any other parameter of file format */ if (hasRefKey && hasTestKey && strcmp(refMime, testMime) == 0) { result = true; } else { ALOGE("file format exp/got : %s/%s", refMime, testMime); } } if (refFormat) AMediaFormat_delete(refFormat); if (testFormat) AMediaFormat_delete(testFormat); } return result; } static bool isSeekOk(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) { const long maxEstDuration = 14000000; const int MAX_SEEK_POINTS = 7; std::srand(kSeed); AMediaCodecBufferInfo refSampleInfo, testSampleInfo; bool result = true; for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(refExtractor); trackID++) { AMediaExtractor_selectTrack(refExtractor, trackID); AMediaExtractor_selectTrack(testExtractor, trackID); for (int i = 0; i < MAX_SEEK_POINTS && result; i++) { double r = ((double)rand() / (RAND_MAX)); long pts = (long)(r * maxEstDuration); for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC; mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) { AMediaExtractor_seekTo(refExtractor, pts, (SeekMode)mode); AMediaExtractor_seekTo(testExtractor, pts, (SeekMode)mode); setSampleInfo(refExtractor, &refSampleInfo); setSampleInfo(testExtractor, &testSampleInfo); result = isSampleInfoIdentical(&refSampleInfo, &testSampleInfo); if (!result) { ALOGE(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags); ALOGE(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size); ALOGE(" ts exp/got: %" PRId64 " / %" PRId64 "", refSampleInfo.presentationTimeUs, testSampleInfo.presentationTimeUs); } int refTrackIdx = AMediaExtractor_getSampleTrackIndex(refExtractor); int testTrackIdx = AMediaExtractor_getSampleTrackIndex(testExtractor); if (refTrackIdx != testTrackIdx) { ALOGE("trackIdx exp/got: %d/%d ", refTrackIdx, testTrackIdx); result = false; } } } AMediaExtractor_unselectTrack(refExtractor, trackID); AMediaExtractor_unselectTrack(testExtractor, trackID); } return result; } static jlong nativeReadAllData(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime, jint sampleLimit, jobjectArray jkeys, jobjectArray jvalues, jboolean isSrcUrl) { const int maxSampleSize = (4 * 1024 * 1024); bool isPass = true; uLong crc32value = 0U; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); const char* cmime = env->GetStringUTFChars(jmime, nullptr); AMediaExtractor* extractor = nullptr; AMediaDataSource* dataSource = nullptr; FILE* srcFp = nullptr; if (isSrcUrl) { isPass = createExtractorFromUrl(env, jkeys, jvalues, &extractor, &dataSource, csrcPath); } else { srcFp = fopen(csrcPath, "rbe"); extractor = createExtractorFromFD(srcFp); if (extractor == nullptr) { if (srcFp) fclose(srcFp); isPass = false; } } if (!isPass) { env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); ALOGE("Error while creating extractor"); if (dataSource) AMediaDataSource_delete(dataSource); if (extractor) AMediaExtractor_delete(extractor); return static_cast(-2); } auto buffer = new uint8_t[maxSampleSize]; int bufferSize = 0; int tracksSelected = 0; for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor) && isPass; trackID++) { AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); const char* refMime = nullptr; CHECK_KEY(AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &refMime), format, isPass); if (strlen(cmime) != 0 && strcmp(refMime, cmime) != 0) { AMediaFormat_delete(format); continue; } AMediaExtractor_selectTrack(extractor, trackID); tracksSelected++; if (strncmp(refMime, "audio/", strlen("audio/")) == 0) { int32_t refSampleRate, refNumChannels; flattenField(buffer, &bufferSize, 0); CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate), format, isPass); flattenField(buffer, &bufferSize, refSampleRate); CHECK_KEY( AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels), format, isPass); flattenField(buffer, &bufferSize, refNumChannels); } else if (strncmp(refMime, "video/", strlen("video/")) == 0) { int32_t refWidth, refHeight; flattenField(buffer, &bufferSize, 1); CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &refWidth), format, isPass); flattenField(buffer, &bufferSize, refWidth); CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &refHeight), format, isPass); flattenField(buffer, &bufferSize, refHeight); } else { flattenField(buffer, &bufferSize, 2); } int64_t keyDuration = 0; CHECK_KEY(AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &keyDuration), format, isPass); flattenField(buffer, &bufferSize, keyDuration); // csd keys for (int i = 0;; i++) { char csdName[16]; void* csdBuffer; size_t csdSize; snprintf(csdName, sizeof(csdName), "csd-%d", i); if (AMediaFormat_getBuffer(format, csdName, &csdBuffer, &csdSize)) { crc32value = crc32(crc32value, static_cast(csdBuffer), csdSize); } else break; } AMediaFormat_delete(format); } if (tracksSelected < 1) { isPass = false; ALOGE("No track selected"); } crc32value = crc32(crc32value, buffer, bufferSize); AMediaCodecBufferInfo sampleInfo; for (int sampleCount = 0; sampleCount < sampleLimit && isPass; sampleCount++) { setSampleInfo(extractor, &sampleInfo); ssize_t refSz = AMediaExtractor_readSampleData(extractor, buffer, maxSampleSize); crc32value = crc32(crc32value, buffer, refSz); if (sampleInfo.size != refSz) { isPass = false; ALOGE("Buffer read size %zd mismatches extractor sample size %d", refSz, sampleInfo.size); } if (sampleInfo.flags == -1) { isPass = false; ALOGE("No extractor flags"); } if (sampleInfo.presentationTimeUs == -1) { isPass = false; ALOGE("Presentation times are negative"); } bufferSize = 0; flattenField(buffer, &bufferSize, sampleInfo.size); flattenField(buffer, &bufferSize, sampleInfo.flags); flattenField(buffer, &bufferSize, sampleInfo.presentationTimeUs); crc32value = crc32(crc32value, buffer, bufferSize); sampleCount++; if (!AMediaExtractor_advance(extractor)) { if (!isExtractorOKonEOS(extractor)) { isPass = false; ALOGE("Mime: %s calls post advance() are not OK", cmime); } break; } } delete[] buffer; if (extractor) AMediaExtractor_delete(extractor); if (dataSource) AMediaDataSource_delete(dataSource); if (srcFp) fclose(srcFp); env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); if (!isPass) { ALOGE(" Error while extracting"); return static_cast(-3); } return static_cast(crc32value); } static jboolean nativeTestExtract(JNIEnv* env, jobject, jstring jsrcPath, jstring jrefPath, jstring jmime) { bool isPass = false; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); const char* ctestPath = env->GetStringUTFChars(jrefPath, nullptr); const char* cmime = env->GetStringUTFChars(jmime, nullptr); FILE* srcFp = fopen(csrcPath, "rbe"); AMediaExtractor* srcExtractor = createExtractorFromFD(srcFp); FILE* testFp = fopen(ctestPath, "rbe"); AMediaExtractor* testExtractor = createExtractorFromFD(testFp); if (srcExtractor && testExtractor) { isPass = isMediaSimilar(srcExtractor, testExtractor, cmime); if (!isPass) { ALOGE(" Src and test are different from extractor perspective"); } AMediaExtractor_delete(srcExtractor); AMediaExtractor_delete(testExtractor); } if (srcFp) fclose(srcFp); if (testFp) fclose(testFp); env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); env->ReleaseStringUTFChars(jrefPath, ctestPath); return static_cast(isPass); } static jboolean nativeTestSeek(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) { bool isPass = false; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); const char* cmime = env->GetStringUTFChars(jmime, nullptr); std::vector seekTestArgs = generateSeekTestArgs(csrcPath, cmime, false); if (!seekTestArgs.empty()) { std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed)); int seekAccErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs); if (seekAccErrCnt != 0) { ALOGE("For %s seek chose inaccurate Sync point in: %d / %zu", csrcPath, seekAccErrCnt, seekTestArgs.size()); isPass = false; } else { isPass = true; } for (auto seekTestArg : seekTestArgs) { delete seekTestArg; } seekTestArgs.clear(); } else { ALOGE("No sync samples found."); } env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); return static_cast(isPass); } static jboolean nativeTestSeekFlakiness(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) { bool isPass = false; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); const char* cmime = env->GetStringUTFChars(jmime, nullptr); std::vector seekTestArgs = generateSeekTestArgs(csrcPath, cmime, true); if (!seekTestArgs.empty()) { std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed)); int flakyErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs); if (flakyErrCnt != 0) { ALOGE("No. of Samples where seek showed flakiness is: %d", flakyErrCnt); isPass = false; } else { isPass = true; } for (auto seekTestArg : seekTestArgs) { delete seekTestArg; } seekTestArgs.clear(); } else { ALOGE("No sync samples found."); } env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); return static_cast(isPass); } static jboolean nativeTestSeekToZero(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) { bool isPass = false; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); const char* cmime = env->GetStringUTFChars(jmime, nullptr); FILE* srcFp = fopen(csrcPath, "rbe"); AMediaExtractor* extractor = createExtractorFromFD(srcFp); if (extractor) { AMediaCodecBufferInfo sampleInfoAtZero; AMediaCodecBufferInfo currInfo; static long randomPts = 1 << 20; for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) { AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); if (format) { const char* currMime = nullptr; bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime); if (!hasKey || strcmp(currMime, cmime) != 0) { AMediaFormat_delete(format); continue; } AMediaExtractor_selectTrack(extractor, trackID); setSampleInfo(extractor, &sampleInfoAtZero); AMediaExtractor_seekTo(extractor, randomPts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC); AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC); setSampleInfo(extractor, &currInfo); isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo); if (!isPass) { ALOGE("seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)"); ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags); ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size); ALOGE(" ts exp/got: %" PRId64 " / %" PRId64 " ", sampleInfoAtZero.presentationTimeUs, currInfo.presentationTimeUs); AMediaFormat_delete(format); break; } AMediaExtractor_seekTo(extractor, -1L, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC); setSampleInfo(extractor, &currInfo); isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo); if (!isPass) { ALOGE("seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)"); ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags); ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size); ALOGE(" ts exp/got: %" PRId64 " / %" PRId64 "", sampleInfoAtZero.presentationTimeUs, currInfo.presentationTimeUs); AMediaFormat_delete(format); break; } AMediaExtractor_unselectTrack(extractor, trackID); AMediaFormat_delete(format); } } AMediaExtractor_delete(extractor); } if (srcFp) fclose(srcFp); env->ReleaseStringUTFChars(jmime, cmime); env->ReleaseStringUTFChars(jsrcPath, csrcPath); return static_cast(isPass); } static jboolean nativeTestFileFormat(JNIEnv* env, jobject, jstring jsrcPath) { bool isPass = false; const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); FILE* srcFp = fopen(csrcPath, "rbe"); AMediaExtractor* extractor = createExtractorFromFD(srcFp); if (extractor) { AMediaFormat* format = AMediaExtractor_getFileFormat(extractor); const char* mime = nullptr; bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime); /* TODO: Not Sure if we need to verify any other parameter of file format */ if (hasKey && mime && strlen(mime) > 0) { isPass = true; } AMediaFormat_delete(format); AMediaExtractor_delete(extractor); } if (srcFp) fclose(srcFp); env->ReleaseStringUTFChars(jsrcPath, csrcPath); return static_cast(isPass); } static jboolean nativeTestDataSource(JNIEnv* env, jobject, jstring jsrcPath, jstring jsrcUrl) { bool isPass = true; const char* csrcUrl = env->GetStringUTFChars(jsrcUrl, nullptr); AMediaExtractor* refExtractor = AMediaExtractor_new(); media_status_t status = AMediaExtractor_setDataSource(refExtractor, csrcUrl); if (status == AMEDIA_OK) { isPass &= validateCachedDuration(refExtractor, true); const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); AMediaDataSource* dataSource = nullptr; AMediaExtractor* testExtractor = nullptr; if (createExtractorFromUrl(env, nullptr, nullptr, &testExtractor, &dataSource, csrcUrl)) { isPass &= validateCachedDuration(testExtractor, true); if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) && isFileFormatIdentical(refExtractor, testExtractor) && isSeekOk(refExtractor, testExtractor))) { isPass = false; } } if (testExtractor) AMediaExtractor_delete(testExtractor); if (dataSource) AMediaDataSource_delete(dataSource); FILE* testFp = fopen(csrcPath, "rbe"); testExtractor = createExtractorFromFD(testFp); if (testExtractor == nullptr) { ALOGE("createExtractorFromFD failed for test extractor"); isPass = false; } else { isPass &= validateCachedDuration(testExtractor, false); if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) && isFileFormatIdentical(refExtractor, testExtractor) && isSeekOk(refExtractor, testExtractor))) { isPass = false; } } if (testExtractor) AMediaExtractor_delete(testExtractor); if (testFp) fclose(testFp); env->ReleaseStringUTFChars(jsrcPath, csrcPath); } else { ALOGE("setDataSource failed"); isPass = false; } if (refExtractor) AMediaExtractor_delete(refExtractor); env->ReleaseStringUTFChars(jsrcUrl, csrcUrl); return static_cast(isPass); } int registerAndroidMediaV2CtsExtractorTestSetDS(JNIEnv* env) { const JNINativeMethod methodTable[] = { {"nativeTestDataSource", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestDataSource}, }; jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$SetDataSourceTest"); return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); } int registerAndroidMediaV2CtsExtractorTestFunc(JNIEnv* env) { const JNINativeMethod methodTable[] = { {"nativeTestExtract", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestExtract}, {"nativeTestSeek", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeek}, {"nativeTestSeekFlakiness", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeekFlakiness}, {"nativeTestSeekToZero", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeekToZero}, {"nativeTestFileFormat", "(Ljava/lang/String;)Z", (void*)nativeTestFileFormat}, }; jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$FunctionalityTest"); return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); } int registerAndroidMediaV2CtsExtractorTest(JNIEnv* env) { const JNINativeMethod methodTable[] = { {"nativeReadAllData", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;[Ljava/lang/String;Z)J", (void*)nativeReadAllData}, }; jclass c = env->FindClass("android/mediav2/cts/ExtractorTest"); return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); } extern int registerAndroidMediaV2CtsExtractorUnitTestApi(JNIEnv* env); extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; if (registerAndroidMediaV2CtsExtractorTest(env) != JNI_OK) return JNI_ERR; if (registerAndroidMediaV2CtsExtractorTestSetDS(env) != JNI_OK) return JNI_ERR; if (registerAndroidMediaV2CtsExtractorTestFunc(env) != JNI_OK) return JNI_ERR; if (registerAndroidMediaV2CtsExtractorUnitTestApi(env) != JNI_OK) return JNI_ERR; return JNI_VERSION_1_6; }