blob: 907b9f303c60439de90531a8b2bfc8d356b2ed35 [file] [log] [blame]
/*
* 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 "NativeCodecDecoderTestCommon"
#include <log/log.h>
#include <android/native_window_jni.h>
#include <jni.h>
#include <media/NdkMediaExtractor.h>
#include <sys/stat.h>
#include <array>
#include <fstream>
#include <string>
#include "NativeCodecDecoderTestCommon.h"
#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, int colorFormat);
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 isTestStateValid() override;
bool isOutputFormatOk(AMediaFormat* configFormat);
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,
int colorFormat, float rmsError, uLong checksum);
bool testFlush(const char* decoder, const char* testFile, int colorFormat);
bool testOnlyEos(const char* decoder, const char* testFile, int colorFormat);
bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile, int colorFormat);
};
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, int colorFormat) {
FILE* fp = fopen(srcFile, "rbe");
RETURN_IF_NULL(fp, StringFormat("Unable to open file %s", srcFile))
struct stat buf {};
if (!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();
RETURN_IF_TRUE(true,
StringFormat("AMediaExtractor_setDataSourceFd failed with error %d",
res))
} else {
mBytesPerSample = (colorFormat == COLOR_FormatYUVP010) ? 2 : 1;
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,
colorFormat);
}
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_IF_NULL(mInpDecFormat,
StringFormat("No track with media type %s found in file: %s", mMime, srcFile))
return true;
}
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);
mTestEnv = "################### Test Environment #####################\n";
{
char* name = nullptr;
media_status_t val = AMediaCodec_getName(mCodec, &name);
if (AMEDIA_OK != val) {
mErrorLogs = StringFormat("%s with error %d \n", "AMediaCodec_getName failed", val);
return false;
}
if (!name) {
mErrorLogs = std::string{"AMediaCodec_getName returned null"};
return false;
}
mTestEnv.append(StringFormat("Component name %s \n", name));
AMediaCodec_releaseName(mCodec, name);
}
mTestEnv.append(StringFormat("Format under test :- %s \n", AMediaFormat_toString(format)));
mTestEnv.append(StringFormat("Component operating in :- %s mode \n",
(isAsync ? "asynchronous" : "synchronous")));
mTestEnv.append(
StringFormat("Component received input eos :- %s \n",
(signalEOSWithLastFrame ? "with full buffer" : "with empty buffer")));
RETURN_IF_FAIL(mAsyncHandle.setCallBack(mCodec, isAsync),
"AMediaCodec_setAsyncNotifyCallback failed")
RETURN_IF_FAIL(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);
RETURN_IF_NULL(buf, std::string{"AMediaCodec_getInputBuffer returned nullptr"})
void* csdBuffer = mCsdBuffers[mCurrCsdIdx].first;
size_t csdSize = mCsdBuffers[mCurrCsdIdx].second;
RETURN_IF_TRUE(bufSize < csdSize,
StringFormat("csd exceeds input buffer size, csdSize: %zu bufSize: %zu", csdSize,
bufSize))
memcpy((void*)buf, csdBuffer, csdSize);
uint32_t flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
RETURN_IF_FAIL(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);
RETURN_IF_NULL(buf, std::string{"AMediaCodec_getInputBuffer returned nullptr"})
ssize_t size = AMediaExtractor_getSampleSize(mExtractor);
int64_t pts = AMediaExtractor_getSampleTime(mExtractor);
RETURN_IF_TRUE(size > bufSize,
StringFormat("extractor sample size exceeds codec input buffer size %zu %zu",
size, bufSize))
RETURN_IF_TRUE(size != AMediaExtractor_readSampleData(mExtractor, buf, bufSize),
std::string{"AMediaExtractor_readSampleData failed"})
if (!AMediaExtractor_advance(mExtractor) && mSignalEOSWithLastFrame) {
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
mSawInputEOS = true;
}
RETURN_IF_FAIL(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);
RETURN_IF_NULL(buf, std::string{"AMediaCodec_getOutputBuffer returned nullptr"})
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, mBytesPerSample);
}
}
mOutputBuff->saveOutPTS(info->presentationTimeUs);
mOutputCount++;
}
ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size,
info->presentationTimeUs, info->flags);
RETURN_IF_FAIL(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, mWindow != nullptr),
"AMediaCodec_releaseOutputBuffer failed")
return !hasSeenError();
}
bool CodecDecoderTest::isTestStateValid() {
if (!CodecTestBase::isTestStateValid()) return false;
RETURN_IF_FALSE(mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts),
std::string{"Output timestamps are not strictly increasing \n"}.append(
mOutputBuff->getErrorMsg()))
RETURN_IF_TRUE(mIsVideo && !mIsInterlaced &&
!mOutputBuff->isOutPtsListIdenticalToInpPtsList(false),
std::string{"Input pts list and Output pts list are not identical \n"}.append(
mOutputBuff->getErrorMsg()))
return true;
}
bool CodecDecoderTest::isOutputFormatOk(AMediaFormat* configFormat) {
RETURN_IF_TRUE(mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
: !mSignalledOutFormatChanged,
std::string{"Input test file format is not same as default format of component, "
"but test did not receive INFO_OUTPUT_FORMAT_CHANGED signal.\n"})
RETURN_IF_TRUE(!isFormatSimilar(configFormat,
mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat()
: mOutFormat),
StringFormat("Configured input format and received output format are not "
"similar. \nConfigured Input format is :- %s \nReceived Output "
"format is :- %s \n",
AMediaFormat_toString(configFormat),
mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat))
return true;
}
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 {
auto msg = StringFormat("unexpected return value from "
"AMediaCodec_dequeueInputBuffer: %d \n",
bufferIndex);
mErrorLogs.append(msg);
ALOGE("%s", msg.c_str());
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", decoder))
if (!configureCodec(format, false, true, false)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
if (!doWork(frameLimit)) return false;
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
mSaveToMem = false;
return !hasSeenError();
}
bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile,
const char* refFile, int colorFormat, float rmsError,
uLong checksum) {
if (!setUpExtractor(testFile, colorFormat)) return false;
mSaveToMem = (mWindow == nullptr);
auto ref = mRefBuff;
auto test = mTestBuff;
const bool boolStates[]{true, false};
int loopCounter = 0;
for (auto eosType : boolStates) {
for (auto isAsync : boolStates) {
bool validateFormat = true;
mOutputBuff = loopCounter == 0 ? ref : test;
mOutputBuff->reset();
AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
/* TODO(b/149981033) */
/* 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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", decoder))
char* name = nullptr;
RETURN_IF_FAIL(AMediaCodec_getName(mCodec, &name), "AMediaCodec_getName failed")
RETURN_IF_NULL(name, std::string{"AMediaCodec_getName returned null"})
auto res = strcmp(name, decoder) != 0;
AMediaCodec_releaseName(mCodec, name);
RETURN_IF_TRUE(res, StringFormat("Codec name mismatch act/got: %s/%s", decoder, 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);
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
if (!doWork(INT32_MAX)) return false;
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
RETURN_IF_TRUE((loopCounter != 0 && !ref->equals(test)),
std::string{"Decoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
if (validateFormat && !isOutputFormatOk(mInpDecFormat)) {
return false;
}
RETURN_IF_TRUE(checksum != ref->getChecksum(),
StringFormat("sdk output and ndk output for same configuration is not "
"identical. \n sdk buffer output checksum is %lu. \n ndk "
"buffer output checksum is %lu. \n",
checksum, ref->getChecksum()))
loopCounter++;
}
}
if (mSaveToMem && refFile && rmsError >= 0) {
setUpAudioReference(refFile);
float currError = ref->getRmsError(mRefData, mRefLength);
float errMargin = rmsError * kRmsErrorTolerance;
RETURN_IF_TRUE(currError > errMargin,
StringFormat("rms error too high for file %s, ref/exp/got: %f/%f/%f",
testFile, rmsError, errMargin, currError))
}
return true;
}
bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile, int colorFormat) {
if (!setUpExtractor(testFile, colorFormat)) 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.emplace_back(std::make_pair(csdBuffer, csdSize));
} else break;
}
const int64_t pts = 500000;
const SeekMode mode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC;
auto ref = mRefBuff;
RETURN_IF_FALSE(decodeToMemory(decoder, mInpDecFormat, INT32_MAX, ref, pts, mode),
StringFormat("decodeToMemory failed for file: %s codec: %s", testFile, decoder))
auto test = mTestBuff;
mOutputBuff = test;
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
if (isAsync) continue; // TODO(b/147576107)
/* TODO(b/149981033) */
/* 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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", decoder))
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);
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
/* test flush in running state before queuing input */
if (!flushCodec()) return false;
if (mIsCodecInAsyncMode) {
RETURN_IF_FAIL(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) {
RETURN_IF_FAIL(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;
RETURN_IF_TRUE(!mIsInterlaced && !test->isPtsStrictlyIncreasing(mPrevOutputPts),
std::string{"Output timestamps are not strictly increasing \n"}.append(
test->getErrorMsg()))
/* test flush in running state */
if (!flushCodec()) return false;
if (mIsCodecInAsyncMode) {
RETURN_IF_FAIL(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;
RETURN_IF_TRUE(isMediaTypeOutputUnAffectedBySeek(mMime) && !ref->equals(test),
std::string{"Decoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
/* test flush in eos state */
if (!flushCodec()) return false;
if (mIsCodecInAsyncMode) {
RETURN_IF_FAIL(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;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
RETURN_IF_TRUE(isMediaTypeOutputUnAffectedBySeek(mMime) && !ref->equals(test),
std::string{"Decoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
if (validateFormat && !isOutputFormatOk(mInpDecFormat)) {
return false;
}
mSaveToMem = false;
}
return true;
}
bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile, int colorFormat) {
if (!setUpExtractor(testFile, colorFormat)) return false;
mSaveToMem = (mWindow == nullptr);
auto ref = mRefBuff;
auto test = mTestBuff;
const bool boolStates[]{true, false};
int loopCounter = 0;
for (auto isAsync : boolStates) {
mOutputBuff = loopCounter == 0 ? ref : test;
mOutputBuff->reset();
/* TODO(b/149981033) */
/* 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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", decoder))
if (!configureCodec(mInpDecFormat, isAsync, false, false)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
RETURN_IF_TRUE((loopCounter != 0 && !ref->equals(test)),
std::string{"Decoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
loopCounter++;
}
return true;
}
bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile,
int colorFormat) {
if (!setUpExtractor(testFile, colorFormat)) 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.emplace_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(); i++) {
auto fmt = formats[i];
for (auto eosType : boolStates) {
for (auto isAsync : boolStates) {
bool validateFormat = true;
mOutputBuff = loopCounter == 0 ? ref : test;
mOutputBuff->reset();
/* TODO(b/149981033) */
/* 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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", decoder))
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);
RETURN_IF_FAIL(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;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
RETURN_IF_TRUE((loopCounter != 0 && !ref->equals(test)),
std::string{"Decoder output is not consistent across runs \n"}
.append(test->getErrorMsg()))
if (validateFormat && !isOutputFormatOk(mInpDecFormat)) {
return false;
}
loopCounter++;
}
}
}
mSaveToMem = false;
return true;
}
jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
jstring jMime, jstring jtestFile, jstring jrefFile,
jint jColorFormat, jfloat jrmsError, jlong jChecksum,
jobject jRetMsg) {
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, jColorFormat,
cRmsError, cChecksum);
std::string msg = isPass ? std::string{} : codecDecoderTest->getErrorMsg();
delete codecDecoderTest;
jclass clazz = env->GetObjectClass(jRetMsg);
jmethodID mId =
env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
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);
}
jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime, jstring jtestFile,
jint jColorFormat, jobject jRetMsg) {
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, jColorFormat);
std::string msg = isPass ? std::string{} : codecDecoderTest->getErrorMsg();
delete codecDecoderTest;
jclass clazz = env->GetObjectClass(jRetMsg);
jmethodID mId =
env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
env->ReleaseStringUTFChars(jDecoder, cDecoder);
env->ReleaseStringUTFChars(jMime, cMime);
env->ReleaseStringUTFChars(jtestFile, cTestFile);
return static_cast<jboolean>(isPass);
}
jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jobject surface, jstring jMime,
jstring jtestFile, jint jColorFormat, jobject jRetMsg) {
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, jColorFormat);
std::string msg = isPass ? std::string{} : codecDecoderTest->getErrorMsg();
delete codecDecoderTest;
jclass clazz = env->GetObjectClass(jRetMsg);
jmethodID mId =
env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
if (window) {
ANativeWindow_release(window);
window = nullptr;
}
env->ReleaseStringUTFChars(jDecoder, cDecoder);
env->ReleaseStringUTFChars(jMime, cMime);
env->ReleaseStringUTFChars(jtestFile, cTestFile);
return static_cast<jboolean>(isPass);
}
jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
jstring jtestFile, jint jColorFormat, jobject jRetMsg) {
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, jColorFormat);
std::string msg = isPass ? std::string{} : codecDecoderTest->getErrorMsg();
delete codecDecoderTest;
jclass clazz = env->GetObjectClass(jRetMsg);
jmethodID mId =
env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
env->ReleaseStringUTFChars(jDecoder, cDecoder);
env->ReleaseStringUTFChars(jMime, cMime);
env->ReleaseStringUTFChars(jtestFile, cTestFile);
return static_cast<jboolean>(isPass);
}