You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
893 lines
40 KiB
893 lines
40 KiB
/*
|
|
* 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 <log/log.h>
|
|
|
|
#include <NdkMediaExtractor.h>
|
|
#include <jni.h>
|
|
#include <sys/stat.h>
|
|
#include <zlib.h>
|
|
|
|
#include <cstdlib>
|
|
#include <random>
|
|
|
|
#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<AMediaCodecBufferInfo*> getSeekablePoints(const char* srcFile,
|
|
const char* mime) {
|
|
std::vector<AMediaCodecBufferInfo*> 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<SeekTestParams*> generateSeekTestArgs(const char* srcFile, const char* mime,
|
|
bool isRandom) {
|
|
std::vector<SeekTestParams*> 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<AMediaCodecBufferInfo*> 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<SeekTestParams*>& 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<jlong>(-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<int32_t>(buffer, &bufferSize, 0);
|
|
CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate),
|
|
format, isPass);
|
|
flattenField<int32_t>(buffer, &bufferSize, refSampleRate);
|
|
CHECK_KEY(
|
|
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels),
|
|
format, isPass);
|
|
flattenField<int32_t>(buffer, &bufferSize, refNumChannels);
|
|
} else if (strncmp(refMime, "video/", strlen("video/")) == 0) {
|
|
int32_t refWidth, refHeight;
|
|
flattenField<int32_t>(buffer, &bufferSize, 1);
|
|
CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &refWidth), format,
|
|
isPass);
|
|
flattenField<int32_t>(buffer, &bufferSize, refWidth);
|
|
CHECK_KEY(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &refHeight), format,
|
|
isPass);
|
|
flattenField<int32_t>(buffer, &bufferSize, refHeight);
|
|
} else {
|
|
flattenField<int32_t>(buffer, &bufferSize, 2);
|
|
}
|
|
int64_t keyDuration = 0;
|
|
CHECK_KEY(AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &keyDuration), format,
|
|
isPass);
|
|
flattenField<int64_t>(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<uint8_t*>(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<int32_t>(buffer, &bufferSize, sampleInfo.size);
|
|
flattenField<int32_t>(buffer, &bufferSize, sampleInfo.flags);
|
|
flattenField<int64_t>(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<jlong>(-3);
|
|
}
|
|
return static_cast<jlong>(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<jboolean>(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<SeekTestParams*> 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<jboolean>(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<SeekTestParams*> 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<jboolean>(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<jboolean>(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<jboolean>(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<jboolean>(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<void**>(&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;
|
|
}
|