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.
768 lines
34 KiB
768 lines
34 KiB
/*
|
|
* Copyright (C) 2020 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 "NativeCodecDecoderTest"
|
|
#include <log/log.h>
|
|
#include <android/native_window_jni.h>
|
|
#include <NdkMediaExtractor.h>
|
|
#include <jni.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <array>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
#include "NativeCodecTestBase.h"
|
|
#include "NativeMediaCommon.h"
|
|
|
|
class CodecDecoderTest final : public CodecTestBase {
|
|
private:
|
|
bool mIsInterlaced;
|
|
uint8_t* mRefData;
|
|
size_t mRefLength;
|
|
AMediaExtractor* mExtractor;
|
|
AMediaFormat* mInpDecFormat;
|
|
AMediaFormat* mInpDecDupFormat;
|
|
std::vector<std::pair<void*, size_t>> mCsdBuffers;
|
|
int mCurrCsdIdx;
|
|
ANativeWindow* mWindow;
|
|
|
|
void setUpAudioReference(const char* refFile);
|
|
void deleteReference();
|
|
bool setUpExtractor(const char* srcFile);
|
|
void deleteExtractor();
|
|
bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
|
|
bool isEncoder) override;
|
|
bool enqueueInput(size_t bufferIndex) override;
|
|
bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
|
|
bool queueCodecConfig();
|
|
bool enqueueCodecConfig(int32_t bufferIndex);
|
|
bool decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
|
|
OutputManager* ref, int64_t pts, SeekMode mode);
|
|
|
|
public:
|
|
explicit CodecDecoderTest(const char* mime, ANativeWindow* window);
|
|
~CodecDecoderTest();
|
|
|
|
bool testSimpleDecode(const char* decoder, const char* testFile, const char* refFile,
|
|
float rmsError, uLong checksum);
|
|
bool testFlush(const char* decoder, const char* testFile);
|
|
bool testOnlyEos(const char* decoder, const char* testFile);
|
|
bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile);
|
|
};
|
|
|
|
CodecDecoderTest::CodecDecoderTest(const char* mime, ANativeWindow* window)
|
|
: CodecTestBase(mime),
|
|
mRefData(nullptr),
|
|
mRefLength(0),
|
|
mExtractor(nullptr),
|
|
mInpDecFormat(nullptr),
|
|
mInpDecDupFormat(nullptr),
|
|
mCurrCsdIdx(0),
|
|
mWindow{window} {}
|
|
|
|
CodecDecoderTest::~CodecDecoderTest() {
|
|
deleteReference();
|
|
deleteExtractor();
|
|
}
|
|
|
|
void CodecDecoderTest::setUpAudioReference(const char* refFile) {
|
|
FILE* fp = fopen(refFile, "rbe");
|
|
struct stat buf {};
|
|
if (fp && !fstat(fileno(fp), &buf)) {
|
|
deleteReference();
|
|
mRefLength = buf.st_size;
|
|
mRefData = new uint8_t[mRefLength];
|
|
fread(mRefData, sizeof(uint8_t), mRefLength, fp);
|
|
} else {
|
|
ALOGE("unable to open input file %s", refFile);
|
|
}
|
|
if (fp) fclose(fp);
|
|
}
|
|
|
|
void CodecDecoderTest::deleteReference() {
|
|
if (mRefData) {
|
|
delete[] mRefData;
|
|
mRefData = nullptr;
|
|
}
|
|
mRefLength = 0;
|
|
}
|
|
|
|
bool CodecDecoderTest::setUpExtractor(const char* srcFile) {
|
|
FILE* fp = fopen(srcFile, "rbe");
|
|
struct stat buf {};
|
|
if (fp && !fstat(fileno(fp), &buf)) {
|
|
deleteExtractor();
|
|
mExtractor = AMediaExtractor_new();
|
|
media_status_t res =
|
|
AMediaExtractor_setDataSourceFd(mExtractor, fileno(fp), 0, buf.st_size);
|
|
if (res != AMEDIA_OK) {
|
|
deleteExtractor();
|
|
} else {
|
|
for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor);
|
|
trackID++) {
|
|
AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID);
|
|
const char* mime = nullptr;
|
|
AMediaFormat_getString(currFormat, AMEDIAFORMAT_KEY_MIME, &mime);
|
|
if (mime && strcmp(mMime, mime) == 0) {
|
|
AMediaExtractor_selectTrack(mExtractor, trackID);
|
|
if (!mIsAudio) {
|
|
AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
|
|
COLOR_FormatYUV420Flexible);
|
|
}
|
|
mInpDecFormat = currFormat;
|
|
// TODO: determine this from the extractor format when it becomes exposed.
|
|
mIsInterlaced = strstr(srcFile, "_interlaced_") != nullptr;
|
|
break;
|
|
}
|
|
AMediaFormat_delete(currFormat);
|
|
}
|
|
}
|
|
}
|
|
if (fp) fclose(fp);
|
|
return mInpDecFormat != nullptr;
|
|
}
|
|
|
|
void CodecDecoderTest::deleteExtractor() {
|
|
if (mExtractor) {
|
|
AMediaExtractor_delete(mExtractor);
|
|
mExtractor = nullptr;
|
|
}
|
|
if (mInpDecFormat) {
|
|
AMediaFormat_delete(mInpDecFormat);
|
|
mInpDecFormat = nullptr;
|
|
}
|
|
if (mInpDecDupFormat) {
|
|
AMediaFormat_delete(mInpDecDupFormat);
|
|
mInpDecDupFormat = nullptr;
|
|
}
|
|
}
|
|
|
|
bool CodecDecoderTest::configureCodec(AMediaFormat* format, bool isAsync,
|
|
bool signalEOSWithLastFrame, bool isEncoder) {
|
|
resetContext(isAsync, signalEOSWithLastFrame);
|
|
CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
|
|
"AMediaCodec_setAsyncNotifyCallback failed");
|
|
CHECK_STATUS(AMediaCodec_configure(mCodec, format, mWindow, nullptr,
|
|
isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0),
|
|
"AMediaCodec_configure failed");
|
|
return true;
|
|
}
|
|
|
|
bool CodecDecoderTest::enqueueCodecConfig(int32_t bufferIndex) {
|
|
size_t bufSize;
|
|
uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
|
|
if (buf == nullptr) {
|
|
ALOGE("AMediaCodec_getInputBuffer failed");
|
|
return false;
|
|
}
|
|
void* csdBuffer = mCsdBuffers[mCurrCsdIdx].first;
|
|
size_t csdSize = mCsdBuffers[mCurrCsdIdx].second;
|
|
if (bufSize < csdSize) {
|
|
ALOGE("csd exceeds input buffer size, csdSize: %zu bufSize: %zu", csdSize, bufSize);
|
|
return false;
|
|
}
|
|
memcpy((void*)buf, csdBuffer, csdSize);
|
|
uint32_t flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
|
|
CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, csdSize, 0, flags),
|
|
"AMediaCodec_queueInputBuffer failed");
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecDecoderTest::enqueueInput(size_t bufferIndex) {
|
|
if (AMediaExtractor_getSampleSize(mExtractor) < 0) {
|
|
return enqueueEOS(bufferIndex);
|
|
} else {
|
|
uint32_t flags = 0;
|
|
size_t bufSize;
|
|
uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
|
|
if (buf == nullptr) {
|
|
ALOGE("AMediaCodec_getInputBuffer failed");
|
|
return false;
|
|
}
|
|
ssize_t size = AMediaExtractor_getSampleSize(mExtractor);
|
|
int64_t pts = AMediaExtractor_getSampleTime(mExtractor);
|
|
if (size > bufSize) {
|
|
ALOGE("extractor sample size exceeds codec input buffer size %zu %zu", size, bufSize);
|
|
return false;
|
|
}
|
|
if (size != AMediaExtractor_readSampleData(mExtractor, buf, bufSize)) {
|
|
ALOGE("AMediaExtractor_readSampleData failed");
|
|
return false;
|
|
}
|
|
if (!AMediaExtractor_advance(mExtractor) && mSignalEOSWithLastFrame) {
|
|
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
|
|
mSawInputEOS = true;
|
|
}
|
|
CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
|
|
"AMediaCodec_queueInputBuffer failed");
|
|
ALOGV("input: id: %zu size: %zu pts: %" PRId64 " flags: %d", bufferIndex, size, pts,
|
|
flags);
|
|
if (size > 0) {
|
|
mOutputBuff->saveInPTS(pts);
|
|
mInputCount++;
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecDecoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
|
|
if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
mSawOutputEOS = true;
|
|
}
|
|
if (info->size > 0 && (info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
|
if (mSaveToMem) {
|
|
size_t buffSize;
|
|
uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
|
|
if (mIsAudio) {
|
|
mOutputBuff->saveToMemory(buf, info);
|
|
mOutputBuff->updateChecksum(buf, info);
|
|
} else {
|
|
AMediaFormat* format =
|
|
mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat;
|
|
int32_t width, height, stride;
|
|
AMediaFormat_getInt32(format, "width", &width);
|
|
AMediaFormat_getInt32(format, "height", &height);
|
|
AMediaFormat_getInt32(format, "stride", &stride);
|
|
mOutputBuff->updateChecksum(buf, info, width, height, stride);
|
|
}
|
|
}
|
|
mOutputBuff->saveOutPTS(info->presentationTimeUs);
|
|
mOutputCount++;
|
|
}
|
|
ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size,
|
|
info->presentationTimeUs, info->flags);
|
|
CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, mWindow != nullptr),
|
|
"AMediaCodec_releaseOutputBuffer failed");
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecDecoderTest::queueCodecConfig() {
|
|
bool isOk = true;
|
|
if (mIsCodecInAsyncMode) {
|
|
for (mCurrCsdIdx = 0; !hasSeenError() && isOk && mCurrCsdIdx < mCsdBuffers.size();
|
|
mCurrCsdIdx++) {
|
|
callbackObject element = mAsyncHandle.getInput();
|
|
if (element.bufferIndex >= 0) {
|
|
isOk = enqueueCodecConfig(element.bufferIndex);
|
|
}
|
|
}
|
|
} else {
|
|
int bufferIndex;
|
|
for (mCurrCsdIdx = 0; isOk && mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
|
|
bufferIndex = AMediaCodec_dequeueInputBuffer(mCodec, -1);
|
|
if (bufferIndex >= 0) {
|
|
isOk = enqueueCodecConfig(bufferIndex);
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueInputBuffer: %d", bufferIndex);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return !hasSeenError() && isOk;
|
|
}
|
|
|
|
bool CodecDecoderTest::decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
|
|
OutputManager* ref, int64_t pts, SeekMode mode) {
|
|
mSaveToMem = (mWindow == nullptr);
|
|
mOutputBuff = ref;
|
|
AMediaExtractor_seekTo(mExtractor, pts, mode);
|
|
mCodec = AMediaCodec_createCodecByName(decoder);
|
|
if (!mCodec) {
|
|
ALOGE("unable to create codec %s", decoder);
|
|
return false;
|
|
}
|
|
if (!configureCodec(format, false, true, false)) return false;
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
if (!doWork(frameLimit)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
|
|
mCodec = nullptr;
|
|
mSaveToMem = false;
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile,
|
|
const char* refFile, float rmsError, uLong checksum) {
|
|
bool isPass = true;
|
|
if (!setUpExtractor(testFile)) return false;
|
|
mSaveToMem = (mWindow == nullptr);
|
|
auto ref = &mRefBuff;
|
|
auto test = &mTestBuff;
|
|
const bool boolStates[]{true, false};
|
|
int loopCounter = 0;
|
|
for (auto eosType : boolStates) {
|
|
if (!isPass) break;
|
|
for (auto isAsync : boolStates) {
|
|
if (!isPass) break;
|
|
bool validateFormat = true;
|
|
char log[1000];
|
|
snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
|
|
decoder, testFile, (isAsync ? "async" : "sync"),
|
|
(eosType ? "eos with last frame" : "eos separate"));
|
|
mOutputBuff = loopCounter == 0 ? ref : test;
|
|
mOutputBuff->reset();
|
|
AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
|
|
/* TODO(b/147348711) */
|
|
/* Instead of create and delete codec at every iteration, we would like to create
|
|
* once and use it for all iterations and delete before exiting */
|
|
mCodec = AMediaCodec_createCodecByName(decoder);
|
|
if (!mCodec) {
|
|
ALOGE("unable to create codec %s", decoder);
|
|
return false;
|
|
}
|
|
char* name = nullptr;
|
|
if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) {
|
|
if (!name || strcmp(name, decoder) != 0) {
|
|
ALOGE("%s error codec-name act/got: %s/%s", log, name, decoder);
|
|
if (name) AMediaCodec_releaseName(mCodec, name);
|
|
return false;
|
|
}
|
|
} else {
|
|
ALOGE("AMediaCodec_getName failed unexpectedly");
|
|
return false;
|
|
}
|
|
if (name) AMediaCodec_releaseName(mCodec, name);
|
|
if (!configureCodec(mInpDecFormat, isAsync, eosType, false)) return false;
|
|
AMediaFormat* decFormat = AMediaCodec_getOutputFormat(mCodec);
|
|
if (isFormatSimilar(mInpDecFormat, decFormat)) {
|
|
ALOGD("Input format is same as default for format for %s", decoder);
|
|
validateFormat = false;
|
|
}
|
|
AMediaFormat_delete(decFormat);
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
if (!doWork(INT32_MAX)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
|
|
mCodec = nullptr;
|
|
CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
|
|
CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
|
|
CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
|
|
CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
|
|
CHECK_ERR(
|
|
loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
|
|
log, "pts is not strictly increasing", isPass);
|
|
// TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
|
|
// produce multiple progressive frames?) For now, do not verify timestamps.
|
|
if (!mIsInterlaced) {
|
|
CHECK_ERR(loopCounter == 0 && !mIsAudio &&
|
|
(!ref->isOutPtsListIdenticalToInpPtsList(false)),
|
|
log, "input pts list and output pts list are not identical", isPass);
|
|
}
|
|
if (validateFormat) {
|
|
if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
|
|
: !mSignalledOutFormatChanged) {
|
|
ALOGE("%s%s", log, "not received format change");
|
|
isPass = false;
|
|
} else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
|
|
? mAsyncHandle.getOutputFormat()
|
|
: mOutFormat)) {
|
|
ALOGE("%s%s", log, "configured format and output format are not similar");
|
|
isPass = false;
|
|
}
|
|
}
|
|
if (checksum != ref->getChecksum()) {
|
|
ALOGE("%s%s", log, "sdk output and ndk output differ");
|
|
isPass = false;
|
|
}
|
|
loopCounter++;
|
|
}
|
|
}
|
|
if (mSaveToMem && refFile && rmsError >= 0) {
|
|
setUpAudioReference(refFile);
|
|
float currError = ref->getRmsError(mRefData, mRefLength);
|
|
float errMargin = rmsError * kRmsErrorTolerance;
|
|
if (currError > errMargin) {
|
|
isPass = false;
|
|
ALOGE("rms error too high for file %s, ref/exp/got: %f/%f/%f", testFile, rmsError,
|
|
errMargin, currError);
|
|
}
|
|
}
|
|
return isPass;
|
|
}
|
|
|
|
bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile) {
|
|
bool isPass = true;
|
|
if (!setUpExtractor(testFile)) return false;
|
|
mCsdBuffers.clear();
|
|
for (int i = 0;; i++) {
|
|
char csdName[16];
|
|
void* csdBuffer;
|
|
size_t csdSize;
|
|
snprintf(csdName, sizeof(csdName), "csd-%d", i);
|
|
if (AMediaFormat_getBuffer(mInpDecFormat, csdName, &csdBuffer, &csdSize)) {
|
|
mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
|
|
} else break;
|
|
}
|
|
const int64_t pts = 500000;
|
|
const SeekMode mode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC;
|
|
auto ref = &mRefBuff;
|
|
if (!decodeToMemory(decoder, mInpDecFormat, INT32_MAX, ref, pts, mode)) {
|
|
ALOGE("decodeToMemory failed for file: %s codec: %s", testFile, decoder);
|
|
return false;
|
|
}
|
|
CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
|
|
"pts is not strictly increasing", isPass);
|
|
// TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
|
|
// produce multiple progressive frames?) For now, do not verify timestamps.
|
|
if (!mIsInterlaced) {
|
|
CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)), "",
|
|
"input pts list and output pts list are not identical", isPass);
|
|
}
|
|
if (!isPass) return false;
|
|
|
|
auto test = &mTestBuff;
|
|
mOutputBuff = test;
|
|
const bool boolStates[]{true, false};
|
|
for (auto isAsync : boolStates) {
|
|
if (!isPass) break;
|
|
char log[1000];
|
|
snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
|
|
(isAsync ? "async" : "sync"));
|
|
/* TODO(b/147348711) */
|
|
/* Instead of create and delete codec at every iteration, we would like to create
|
|
* once and use it for all iterations and delete before exiting */
|
|
mCodec = AMediaCodec_createCodecByName(decoder);
|
|
if (!mCodec) {
|
|
ALOGE("unable to create codec %s", decoder);
|
|
return false;
|
|
}
|
|
AMediaExtractor_seekTo(mExtractor, 0, mode);
|
|
if (!configureCodec(mInpDecFormat, isAsync, true, false)) return false;
|
|
AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
|
|
bool validateFormat = true;
|
|
if (isFormatSimilar(mInpDecFormat, defFormat)) {
|
|
ALOGD("Input format is same as default for format for %s", decoder);
|
|
validateFormat = false;
|
|
}
|
|
AMediaFormat_delete(defFormat);
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
|
|
/* test flush in running state before queuing input */
|
|
if (!flushCodec()) return false;
|
|
if (mIsCodecInAsyncMode) {
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
}
|
|
if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
|
|
if (!doWork(1)) return false;
|
|
|
|
if (!flushCodec()) return false;
|
|
if (mIsCodecInAsyncMode) {
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
}
|
|
if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
|
|
AMediaExtractor_seekTo(mExtractor, 0, mode);
|
|
test->reset();
|
|
if (!doWork(23)) return false;
|
|
CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "",
|
|
"pts is not strictly increasing", isPass);
|
|
|
|
/* test flush in running state */
|
|
if (!flushCodec()) return false;
|
|
if (mIsCodecInAsyncMode) {
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
}
|
|
mSaveToMem = (mWindow == nullptr);
|
|
test->reset();
|
|
AMediaExtractor_seekTo(mExtractor, pts, mode);
|
|
if (!doWork(INT32_MAX)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
|
|
CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
|
|
CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
|
|
CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
|
|
if (!isPass) continue;
|
|
|
|
/* test flush in eos state */
|
|
if (!flushCodec()) return false;
|
|
if (mIsCodecInAsyncMode) {
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
}
|
|
test->reset();
|
|
AMediaExtractor_seekTo(mExtractor, pts, mode);
|
|
if (!doWork(INT32_MAX)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
|
|
mCodec = nullptr;
|
|
CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
|
|
CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
|
|
CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
|
|
CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
|
|
if (validateFormat) {
|
|
if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
|
|
: !mSignalledOutFormatChanged) {
|
|
ALOGE("%s%s", log, "not received format change");
|
|
isPass = false;
|
|
} else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
|
|
? mAsyncHandle.getOutputFormat()
|
|
: mOutFormat)) {
|
|
ALOGE("%s%s", log, "configured format and output format are not similar");
|
|
isPass = false;
|
|
}
|
|
}
|
|
mSaveToMem = false;
|
|
}
|
|
return isPass;
|
|
}
|
|
|
|
bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile) {
|
|
bool isPass = true;
|
|
if (!setUpExtractor(testFile)) return false;
|
|
mSaveToMem = (mWindow == nullptr);
|
|
auto ref = &mRefBuff;
|
|
auto test = &mTestBuff;
|
|
const bool boolStates[]{true, false};
|
|
int loopCounter = 0;
|
|
for (auto isAsync : boolStates) {
|
|
if (!isPass) break;
|
|
char log[1000];
|
|
snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
|
|
(isAsync ? "async" : "sync"));
|
|
mOutputBuff = loopCounter == 0 ? ref : test;
|
|
mOutputBuff->reset();
|
|
/* TODO(b/147348711) */
|
|
/* Instead of create and delete codec at every iteration, we would like to create
|
|
* once and use it for all iterations and delete before exiting */
|
|
mCodec = AMediaCodec_createCodecByName(decoder);
|
|
if (!mCodec) {
|
|
ALOGE("unable to create codec %s", decoder);
|
|
return false;
|
|
}
|
|
if (!configureCodec(mInpDecFormat, isAsync, false, false)) return false;
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
|
|
mCodec = nullptr;
|
|
CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
|
|
CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
|
|
CHECK_ERR(loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
|
|
log, "pts is not strictly increasing", isPass);
|
|
CHECK_ERR(loopCounter == 0 && !mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)),
|
|
log, "input pts list and output pts list are not identical", isPass);
|
|
loopCounter++;
|
|
}
|
|
return isPass;
|
|
}
|
|
|
|
bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile) {
|
|
bool isPass = true;
|
|
if (!setUpExtractor(testFile)) return false;
|
|
std::vector<AMediaFormat*> formats;
|
|
formats.push_back(mInpDecFormat);
|
|
mInpDecDupFormat = AMediaFormat_new();
|
|
AMediaFormat_copy(mInpDecDupFormat, mInpDecFormat);
|
|
formats.push_back(mInpDecDupFormat);
|
|
mCsdBuffers.clear();
|
|
for (int i = 0;; i++) {
|
|
char csdName[16];
|
|
void* csdBuffer;
|
|
size_t csdSize;
|
|
snprintf(csdName, sizeof(csdName), "csd-%d", i);
|
|
if (AMediaFormat_getBuffer(mInpDecDupFormat, csdName, &csdBuffer, &csdSize)) {
|
|
mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
|
|
AMediaFormat_setBuffer(mInpDecFormat, csdName, nullptr, 0);
|
|
} else break;
|
|
}
|
|
|
|
const bool boolStates[]{true, false};
|
|
mSaveToMem = true;
|
|
auto ref = &mRefBuff;
|
|
auto test = &mTestBuff;
|
|
int loopCounter = 0;
|
|
for (int i = 0; i < formats.size() && isPass; i++) {
|
|
auto fmt = formats[i];
|
|
for (auto eosType : boolStates) {
|
|
if (!isPass) break;
|
|
for (auto isAsync : boolStates) {
|
|
if (!isPass) break;
|
|
bool validateFormat = true;
|
|
char log[1000];
|
|
snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
|
|
decoder, testFile, (isAsync ? "async" : "sync"),
|
|
(eosType ? "eos with last frame" : "eos separate"));
|
|
mOutputBuff = loopCounter == 0 ? ref : test;
|
|
mOutputBuff->reset();
|
|
/* TODO(b/147348711) */
|
|
/* Instead of create and delete codec at every iteration, we would like to create
|
|
* once and use it for all iterations and delete before exiting */
|
|
mCodec = AMediaCodec_createCodecByName(decoder);
|
|
if (!mCodec) {
|
|
ALOGE("unable to create codec");
|
|
return false;
|
|
}
|
|
AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
|
|
if (!configureCodec(fmt, isAsync, eosType, false)) return false;
|
|
AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
|
|
if (isFormatSimilar(defFormat, mInpDecFormat)) {
|
|
ALOGD("Input format is same as default for format for %s", decoder);
|
|
validateFormat = false;
|
|
}
|
|
AMediaFormat_delete(defFormat);
|
|
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
|
|
/* formats[0] doesn't contain csd-data, so queuing csd separately, formats[1]
|
|
* contain csd-data */
|
|
if (i == 0 && !queueCodecConfig()) return false;
|
|
if (!doWork(INT32_MAX)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllOutputs()) return false;
|
|
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
|
|
mCodec = nullptr;
|
|
CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
|
|
CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
|
|
CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
|
|
CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
|
|
CHECK_ERR(loopCounter == 0 && mIsAudio &&
|
|
(!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
|
|
log, "pts is not strictly increasing", isPass);
|
|
CHECK_ERR(loopCounter == 0 && !mIsAudio &&
|
|
(!ref->isOutPtsListIdenticalToInpPtsList(false)),
|
|
log, "input pts list and output pts list are not identical", isPass);
|
|
if (validateFormat) {
|
|
if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
|
|
: !mSignalledOutFormatChanged) {
|
|
ALOGE("%s%s", log, "not received format change");
|
|
isPass = false;
|
|
} else if (!isFormatSimilar(mInpDecFormat,
|
|
mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat()
|
|
: mOutFormat)) {
|
|
ALOGE("%s%s", log, "configured format and output format are not similar");
|
|
isPass = false;
|
|
}
|
|
}
|
|
loopCounter++;
|
|
}
|
|
}
|
|
}
|
|
mSaveToMem = false;
|
|
return isPass;
|
|
}
|
|
|
|
static jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
|
|
jstring jMime, jstring jtestFile, jstring jrefFile,
|
|
jfloat jrmsError, jlong jChecksum) {
|
|
const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
|
|
const char* cMime = env->GetStringUTFChars(jMime, nullptr);
|
|
const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
|
|
const char* cRefFile = env->GetStringUTFChars(jrefFile, nullptr);
|
|
float cRmsError = jrmsError;
|
|
uLong cChecksum = jChecksum;
|
|
ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
|
|
auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
|
|
bool isPass =
|
|
codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, cRmsError, cChecksum);
|
|
delete codecDecoderTest;
|
|
if (window) {
|
|
ANativeWindow_release(window);
|
|
window = nullptr;
|
|
}
|
|
env->ReleaseStringUTFChars(jDecoder, cDecoder);
|
|
env->ReleaseStringUTFChars(jMime, cMime);
|
|
env->ReleaseStringUTFChars(jtestFile, cTestFile);
|
|
env->ReleaseStringUTFChars(jrefFile, cRefFile);
|
|
return static_cast<jboolean>(isPass);
|
|
}
|
|
|
|
static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
|
|
jstring jtestFile) {
|
|
const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
|
|
const char* cMime = env->GetStringUTFChars(jMime, nullptr);
|
|
const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
|
|
auto* codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
|
|
bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile);
|
|
delete codecDecoderTest;
|
|
env->ReleaseStringUTFChars(jDecoder, cDecoder);
|
|
env->ReleaseStringUTFChars(jMime, cMime);
|
|
env->ReleaseStringUTFChars(jtestFile, cTestFile);
|
|
return static_cast<jboolean>(isPass);
|
|
}
|
|
|
|
static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
|
|
jstring jMime, jstring jtestFile) {
|
|
const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
|
|
const char* cMime = env->GetStringUTFChars(jMime, nullptr);
|
|
const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
|
|
ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
|
|
auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
|
|
bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile);
|
|
delete codecDecoderTest;
|
|
if (window) {
|
|
ANativeWindow_release(window);
|
|
window = nullptr;
|
|
}
|
|
env->ReleaseStringUTFChars(jDecoder, cDecoder);
|
|
env->ReleaseStringUTFChars(jMime, cMime);
|
|
env->ReleaseStringUTFChars(jtestFile, cTestFile);
|
|
return static_cast<jboolean>(isPass);
|
|
}
|
|
|
|
static jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder,
|
|
jstring jMime, jstring jtestFile) {
|
|
const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
|
|
const char* cMime = env->GetStringUTFChars(jMime, nullptr);
|
|
const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
|
|
auto codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
|
|
bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile);
|
|
delete codecDecoderTest;
|
|
env->ReleaseStringUTFChars(jDecoder, cDecoder);
|
|
env->ReleaseStringUTFChars(jMime, cMime);
|
|
env->ReleaseStringUTFChars(jtestFile, cTestFile);
|
|
return static_cast<jboolean>(isPass);
|
|
}
|
|
|
|
int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env) {
|
|
const JNINativeMethod methodTable[] = {
|
|
{"nativeTestSimpleDecode",
|
|
"(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
|
|
"lang/String;FJ)Z",
|
|
(void*)nativeTestSimpleDecode},
|
|
{"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
|
|
(void*)nativeTestOnlyEos},
|
|
{"nativeTestFlush",
|
|
"(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
|
|
(void*)nativeTestFlush},
|
|
{"nativeTestSimpleDecodeQueueCSD",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
|
|
(void*)nativeTestSimpleDecodeQueueCSD},
|
|
};
|
|
jclass c = env->FindClass("android/mediav2/cts/CodecDecoderTest");
|
|
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
|
|
}
|
|
|
|
int registerAndroidMediaV2CtsDecoderSurfaceTest(JNIEnv* env) {
|
|
const JNINativeMethod methodTable[] = {
|
|
{"nativeTestSimpleDecode",
|
|
"(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
|
|
"lang/String;FJ)Z",
|
|
(void*)nativeTestSimpleDecode},
|
|
{"nativeTestFlush",
|
|
"(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
|
|
(void*)nativeTestFlush},
|
|
};
|
|
jclass c = env->FindClass("android/mediav2/cts/CodecDecoderSurfaceTest");
|
|
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
|
|
}
|