blob: de0fae8a4430f3fbd14cf5cc7bd7de483b976c60 [file] [log] [blame]
/*
* 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;
}