blob: 8d1669983d0d841c5f5e00ab2d04b2074d8c50f2 [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 "NativeCodecEncoderTest"
#include <log/log.h>
#include <jni.h>
#include <sys/stat.h>
#include "NativeCodecTestBase.h"
#include "NativeMediaCommon.h"
class CodecEncoderTest final : CodecTestBase {
private:
uint8_t* mInputData;
size_t mInputLength;
int mNumBytesSubmitted;
int64_t mInputOffsetPts;
std::vector<AMediaFormat*> mFormats;
int mNumSyncFramesReceived;
std::vector<int> mSyncFramesPos;
int32_t* mBitRates;
int mLen0;
int32_t* mEncParamList1;
int mLen1;
int32_t* mEncParamList2;
int mLen2;
int mWidth, mHeight;
int mChannels;
int mSampleRate;
int mColorFormat;
int mMaxBFrames;
int mDefFrameRate;
const int kInpFrmWidth = 352;
const int kInpFrmHeight = 288;
void convertyuv420ptoyuv420sp();
void setUpSource(const char* srcPath);
void deleteSource();
void setUpParams(int limit);
void deleteParams();
bool flushCodec() override;
void resetContext(bool isAsync, bool signalEOSWithLastFrame) override;
bool enqueueInput(size_t bufferIndex) override;
bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
void initFormat(AMediaFormat* format);
bool encodeToMemory(const char* file, const char* encoder, int frameLimit, AMediaFormat* format,
OutputManager* ref);
void fillByteBuffer(uint8_t* inputBuffer);
void forceSyncFrame(AMediaFormat* format);
void updateBitrate(AMediaFormat* format, int bitrate);
public:
CodecEncoderTest(const char* mime, int32_t* list0, int len0, int32_t* list1, int len1,
int32_t* list2, int len2, int colorFormat);
~CodecEncoderTest();
bool testSimpleEncode(const char* encoder, const char* srcPath);
bool testFlush(const char* encoder, const char* srcPath);
bool testReconfigure(const char* encoder, const char* srcPath);
bool testSetForceSyncFrame(const char* encoder, const char* srcPath);
bool testAdaptiveBitRate(const char* encoder, const char* srcPath);
bool testOnlyEos(const char* encoder);
};
CodecEncoderTest::CodecEncoderTest(const char* mime, int32_t* list0, int len0, int32_t* list1,
int len1, int32_t* list2, int len2, int colorFormat)
: CodecTestBase(mime),
mBitRates{list0},
mLen0{len0},
mEncParamList1{list1},
mLen1{len1},
mEncParamList2{list2},
mLen2{len2},
mColorFormat{colorFormat} {
mDefFrameRate = 30;
if (!strcmp(mime, AMEDIA_MIMETYPE_VIDEO_H263)) mDefFrameRate = 12;
else if (!strcmp(mime, AMEDIA_MIMETYPE_VIDEO_MPEG4)) mDefFrameRate = 12;
mMaxBFrames = 0;
mInputData = nullptr;
mInputLength = 0;
mNumBytesSubmitted = 0;
mInputOffsetPts = 0;
}
CodecEncoderTest::~CodecEncoderTest() {
deleteSource();
deleteParams();
}
void CodecEncoderTest::convertyuv420ptoyuv420sp() {
int ySize = kInpFrmWidth * kInpFrmHeight;
int uSize = kInpFrmWidth * kInpFrmHeight / 4;
int frameSize = ySize + uSize * 2;
int totalFrames = mInputLength / frameSize;
uint8_t* u = new uint8_t[uSize];
uint8_t* v = new uint8_t[uSize];
uint8_t* frameBase = mInputData;
for (int i = 0; i < totalFrames; i++) {
uint8_t* uvBase = frameBase + ySize;
memcpy(u, uvBase, uSize);
memcpy(v, uvBase + uSize, uSize);
for (int j = 0, idx = 0; j < uSize; j++, idx += 2) {
uvBase[idx] = u[j];
uvBase[idx + 1] = v[j];
}
frameBase += frameSize;
}
delete[] u;
delete[] v;
}
void CodecEncoderTest::setUpSource(const char* srcPath) {
FILE* fp = fopen(srcPath, "rbe");
struct stat buf {};
if (fp && !fstat(fileno(fp), &buf)) {
deleteSource();
mInputLength = buf.st_size;
mInputData = new uint8_t[mInputLength];
fread(mInputData, sizeof(uint8_t), mInputLength, fp);
if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
convertyuv420ptoyuv420sp();
}
} else {
ALOGE("unable to open input file %s", srcPath);
}
if (fp) fclose(fp);
}
void CodecEncoderTest::deleteSource() {
if (mInputData) {
delete[] mInputData;
mInputData = nullptr;
}
mInputLength = 0;
}
void CodecEncoderTest::setUpParams(int limit) {
int count = 0;
for (int k = 0; k < mLen0; k++) {
int bitrate = mBitRates[k];
if (mIsAudio) {
for (int j = 0; j < mLen1; j++) {
int rate = mEncParamList1[j];
for (int i = 0; i < mLen2; i++) {
int channels = mEncParamList2[i];
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mMime);
if (!strcmp(mMime, AMEDIA_MIMETYPE_AUDIO_FLAC)) {
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL,
bitrate);
} else {
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
}
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, rate);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, channels);
mFormats.push_back(format);
count++;
if (count >= limit) break;
}
}
} else {
for (int j = 0; j < mLen1; j++) {
int width = mEncParamList1[j];
int height = mEncParamList2[j];
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mMime);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, mDefFrameRate);
AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES,
mMaxBFrames);
AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1.0F);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, mColorFormat);
mFormats.push_back(format);
count++;
if (count >= limit) break;
}
}
}
}
void CodecEncoderTest::deleteParams() {
for (auto format : mFormats) AMediaFormat_delete(format);
mFormats.clear();
}
void CodecEncoderTest::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
CodecTestBase::resetContext(isAsync, signalEOSWithLastFrame);
mNumBytesSubmitted = 0;
mInputOffsetPts = 0;
mNumSyncFramesReceived = 0;
mSyncFramesPos.clear();
}
bool CodecEncoderTest::flushCodec() {
bool isOk = CodecTestBase::flushCodec();
if (mIsAudio) {
mInputOffsetPts = (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate);
} else {
mInputOffsetPts = (mInputCount + 5) * 1000000L / mDefFrameRate;
}
mPrevOutputPts = mInputOffsetPts - 1;
mNumBytesSubmitted = 0;
mNumSyncFramesReceived = 0;
mSyncFramesPos.clear();
return isOk;
}
void CodecEncoderTest::fillByteBuffer(uint8_t* inputBuffer) {
int width, height, tileWidth, tileHeight;
int offset = 0, frmOffset = mNumBytesSubmitted;
int numOfPlanes;
if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
numOfPlanes = 2;
} else {
numOfPlanes = 3;
}
for (int plane = 0; plane < numOfPlanes; plane++) {
if (plane == 0) {
width = mWidth;
height = mHeight;
tileWidth = kInpFrmWidth;
tileHeight = kInpFrmHeight;
} else {
if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
width = mWidth;
tileWidth = kInpFrmWidth;
} else {
width = mWidth / 2;
tileWidth = kInpFrmWidth / 2;
}
height = mHeight / 2;
tileHeight = kInpFrmHeight / 2;
}
for (int k = 0; k < height; k += tileHeight) {
int rowsToCopy = std::min(height - k, tileHeight);
for (int j = 0; j < rowsToCopy; j++) {
for (int i = 0; i < width; i += tileWidth) {
int colsToCopy = std::min(width - i, tileWidth);
memcpy(inputBuffer + (offset + (k + j) * width + i),
mInputData + (frmOffset + j * tileWidth), colsToCopy);
}
}
}
offset += width * height;
frmOffset += tileWidth * tileHeight;
}
}
bool CodecEncoderTest::enqueueInput(size_t bufferIndex) {
if (mNumBytesSubmitted >= mInputLength) {
return enqueueEOS(bufferIndex);
} else {
int size = 0;
int flags = 0;
int64_t pts = mInputOffsetPts;
size_t buffSize;
uint8_t* inputBuffer = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &buffSize);
if (mIsAudio) {
pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate);
size = std::min(buffSize, mInputLength - mNumBytesSubmitted);
memcpy(inputBuffer, mInputData + mNumBytesSubmitted, size);
if (mNumBytesSubmitted + size >= mInputLength && mSignalEOSWithLastFrame) {
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
mSawInputEOS = true;
}
mNumBytesSubmitted += size;
} else {
pts += mInputCount * 1000000L / mDefFrameRate;
size = mWidth * mHeight * 3 / 2;
int frmSize = kInpFrmWidth * kInpFrmHeight * 3 / 2;
if (mNumBytesSubmitted + frmSize > mInputLength) {
ALOGE("received partial frame to encode");
return false;
} else if (size > buffSize) {
ALOGE("frame size exceeds buffer capacity of input buffer %d %zu", size, buffSize);
return false;
} else {
if (mWidth == kInpFrmWidth && mHeight == kInpFrmHeight) {
memcpy(inputBuffer, mInputData + mNumBytesSubmitted, size);
} else {
fillByteBuffer(inputBuffer);
}
}
if (mNumBytesSubmitted + frmSize >= mInputLength && mSignalEOSWithLastFrame) {
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
mSawInputEOS = true;
}
mNumBytesSubmitted += frmSize;
}
CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
"AMediaCodec_queueInputBuffer failed");
ALOGV("input: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, size, pts,
flags);
mOutputBuff->saveInPTS(pts);
mInputCount++;
}
return !hasSeenError();
}
bool CodecEncoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
mSawOutputEOS = true;
}
if (info->size > 0) {
if (mSaveToMem) {
size_t buffSize;
uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
mOutputBuff->saveToMemory(buf, info);
}
if ((info->flags & TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0) {
mNumSyncFramesReceived += 1;
mSyncFramesPos.push_back(mOutputCount);
}
if ((info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
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, false),
"AMediaCodec_releaseOutputBuffer failed");
return !hasSeenError();
}
void CodecEncoderTest::initFormat(AMediaFormat* format) {
if (mIsAudio) {
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &mChannels);
} else {
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
}
}
bool CodecEncoderTest::encodeToMemory(const char* file, const char* encoder, int32_t frameLimit,
AMediaFormat* format, OutputManager* ref) {
/* TODO(b/149027258) */
if (true) mSaveToMem = false;
else mSaveToMem = true;
mOutputBuff = ref;
mCodec = AMediaCodec_createCodecByName(encoder);
if (!mCodec) {
ALOGE("unable to create codec %s", encoder);
return false;
}
setUpSource(file);
if (!mInputData) return false;
if (!configureCodec(format, false, true, true)) return false;
initFormat(format);
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();
}
void CodecEncoderTest::forceSyncFrame(AMediaFormat* format) {
AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
ALOGV("requesting key frame");
AMediaCodec_setParameters(mCodec, format);
}
void CodecEncoderTest::updateBitrate(AMediaFormat* format, int bitrate) {
AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate);
ALOGV("requesting bitrate to be changed to %d", bitrate);
AMediaCodec_setParameters(mCodec, format);
}
bool CodecEncoderTest::testSimpleEncode(const char* encoder, const char* srcPath) {
bool isPass = true;
setUpSource(srcPath);
if (!mInputData) return false;
setUpParams(INT32_MAX);
/* TODO(b/149027258) */
if (true) mSaveToMem = false;
else mSaveToMem = true;
auto ref = &mRefBuff;
auto test = &mTestBuff;
const bool boolStates[]{true, false};
for (int i = 0; i < mFormats.size() && isPass; i++) {
AMediaFormat* format = mFormats[i];
initFormat(format);
int loopCounter = 0;
for (auto eosType : boolStates) {
if (!isPass) break;
for (auto isAsync : boolStates) {
if (!isPass) break;
char log[1000];
snprintf(log, sizeof(log),
"format: %s \n codec: %s, file: %s, mode: %s, eos type: %s:: ",
AMediaFormat_toString(format), encoder, srcPath,
(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(encoder);
if (!mCodec) {
ALOGE("%s unable to create media codec by name %s", log, encoder);
isPass = false;
continue;
}
char* name = nullptr;
if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) {
if (!name || strcmp(name, encoder) != 0) {
ALOGE("%s error codec-name act/got: %s/%s", log, name, encoder);
if (name) AMediaCodec_releaseName(mCodec, name);
return false;
}
} else {
ALOGE("AMediaCodec_getName failed unexpectedly");
return false;
}
if (name) AMediaCodec_releaseName(mCodec, name);
if (!configureCodec(format, isAsync, eosType, true)) return false;
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((!mIsAudio && mInputCount != mOutputCount), log,
"input cnt != output cnt", 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(mMaxBFrames != 0)),
log, "input pts list and output pts list are not identical", isPass);
loopCounter++;
}
}
}
return isPass;
}
bool CodecEncoderTest::testFlush(const char* encoder, const char* srcPath) {
bool isPass = true;
setUpSource(srcPath);
if (!mInputData) return false;
setUpParams(1);
mOutputBuff = &mTestBuff;
AMediaFormat* format = mFormats[0];
initFormat(format);
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
if (!isPass) break;
char log[1000];
snprintf(log, sizeof(log),
"format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
encoder, srcPath, (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(encoder);
if (!mCodec) {
ALOGE("unable to create media codec by name %s", encoder);
isPass = false;
continue;
}
if (!configureCodec(format, isAsync, true, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
/* test flush in running state before queuing input */
if (!flushCodec()) return false;
mOutputBuff->reset();
if (mIsCodecInAsyncMode) {
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
}
if (!doWork(23)) return false;
CHECK_ERR((!mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
"pts is not strictly increasing", isPass);
if (!isPass) continue;
/* test flush in running state */
if (!flushCodec()) return false;
mOutputBuff->reset();
if (mIsCodecInAsyncMode) {
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
}
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((mIsAudio && !mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
"pts is not strictly increasing", isPass);
CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!mIsAudio && !mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
log, "input pts list and output pts list are not identical", isPass);
if (!isPass) continue;
/* test flush in eos state */
if (!flushCodec()) return false;
mOutputBuff->reset();
if (mIsCodecInAsyncMode) {
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
}
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((mIsAudio && !mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
"pts is not strictly increasing", isPass);
CHECK_ERR(!mIsAudio && (mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR(!mIsAudio && (!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
log, "input pts list and output pts list are not identical", isPass);
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
mCodec = nullptr;
}
return isPass;
}
bool CodecEncoderTest::testReconfigure(const char* encoder, const char* srcPath) {
bool isPass = true;
setUpSource(srcPath);
if (!mInputData) return false;
setUpParams(2);
auto configRef = &mReconfBuff;
if (mFormats.size() > 1) {
auto format = mFormats[1];
if (!encodeToMemory(srcPath, encoder, INT32_MAX, format, configRef)) {
ALOGE("encodeToMemory failed for file: %s codec: %s \n format: %s", srcPath, encoder,
AMediaFormat_toString(format));
return false;
}
CHECK_ERR(mIsAudio && (!configRef->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
"pts is not strictly increasing", isPass);
CHECK_ERR(!mIsAudio && (!configRef->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
"", "input pts list and output pts list are not identical", isPass);
if (!isPass) return false;
}
auto format = mFormats[0];
auto ref = &mRefBuff;
if (!encodeToMemory(srcPath, encoder, INT32_MAX, format, ref)) {
ALOGE("encodeToMemory failed for file: %s codec: %s \n format: %s", srcPath, encoder,
AMediaFormat_toString(format));
return false;
}
CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
"pts is not strictly increasing", isPass);
CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), "",
"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),
"format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
encoder, srcPath, (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(encoder);
if (!mCodec) {
ALOGE("%s unable to create media codec by name %s", log, encoder);
isPass = false;
continue;
}
if (!configureCodec(format, isAsync, true, true)) return false;
/* test reconfigure in init state */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
/* test reconfigure in running state before queuing input */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
if (!doWork(23)) return false;
/* test reconfigure codec in running state */
if (!reConfigureCodec(format, isAsync, true, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
/* TODO(b/149027258) */
if (true) mSaveToMem = false;
else mSaveToMem = true;
test->reset();
if (!doWork(INT32_MAX)) return false;
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
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((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
if (!isPass) continue;
/* test reconfigure codec at eos state */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
test->reset();
if (!doWork(INT32_MAX)) return false;
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
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((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
/* test reconfigure codec for new format */
if (mFormats.size() > 1) {
if (!reConfigureCodec(mFormats[1], isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
test->reset();
if (!doWork(INT32_MAX)) return false;
if (!queueEOS()) return false;
if (!waitForAllOutputs()) return false;
CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
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((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!configRef->equals(test)), log, "output is flaky", isPass);
}
mSaveToMem = false;
CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
mCodec = nullptr;
}
return isPass;
}
bool CodecEncoderTest::testOnlyEos(const char* encoder) {
bool isPass = true;
setUpParams(1);
/* TODO(b/149027258) */
if (true) mSaveToMem = false;
else mSaveToMem = true;
auto ref = &mRefBuff;
auto test = &mTestBuff;
const bool boolStates[]{true, false};
AMediaFormat* format = mFormats[0];
int loopCounter = 0;
for (int k = 0; (k < (sizeof(boolStates) / sizeof(boolStates[0]))) && isPass; k++) {
bool isAsync = boolStates[k];
char log[1000];
snprintf(log, sizeof(log),
"format: %s \n codec: %s, mode: %s:: ", AMediaFormat_toString(format), encoder,
(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(encoder);
if (!mCodec) {
ALOGE("unable to create media codec by name %s", encoder);
isPass = false;
continue;
}
if (!configureCodec(format, isAsync, false, true)) 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(mMaxBFrames != 0)),
log, "input pts list and output pts list are not identical", isPass);
loopCounter++;
}
return isPass;
}
bool CodecEncoderTest::testSetForceSyncFrame(const char* encoder, const char* srcPath) {
bool isPass = true;
setUpSource(srcPath);
if (!mInputData) return false;
setUpParams(1);
AMediaFormat* format = mFormats[0];
AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 500.f);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
// Maximum allowed key frame interval variation from the target value.
int kMaxKeyFrameIntervalVariation = 3;
int kKeyFrameInterval = 2; // force key frame every 2 seconds.
int kKeyFramePos = mDefFrameRate * kKeyFrameInterval;
int kNumKeyFrameRequests = 7;
AMediaFormat* params = AMediaFormat_new();
mFormats.push_back(params);
mOutputBuff = &mTestBuff;
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
if (!isPass) break;
char log[1000];
snprintf(log, sizeof(log),
"format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
encoder, srcPath, (isAsync ? "async" : "sync"));
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(encoder);
if (!mCodec) {
ALOGE("%s unable to create media codec by name %s", log, encoder);
isPass = false;
continue;
}
if (!configureCodec(format, isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
for (int i = 0; i < kNumKeyFrameRequests; i++) {
if (!doWork(kKeyFramePos)) return false;
assert(!mSawInputEOS);
forceSyncFrame(params);
mNumBytesSubmitted = 0;
}
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((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), log,
"input pts list and output pts list are not identical", isPass);
CHECK_ERR((mNumSyncFramesReceived < kNumKeyFrameRequests), log,
"Num Sync Frames Received != Num Key Frame Requested", isPass);
ALOGD("mNumSyncFramesReceived %d", mNumSyncFramesReceived);
for (int i = 0, expPos = 0, index = 0; i < kNumKeyFrameRequests; i++) {
int j = index;
for (; j < mSyncFramesPos.size(); j++) {
// Check key frame intervals:
// key frame position should not be greater than target value + 3
// key frame position should not be less than target value - 3
if (abs(expPos - mSyncFramesPos.at(j)) <= kMaxKeyFrameIntervalVariation) {
index = j;
break;
}
}
if (j == mSyncFramesPos.size()) {
ALOGW("requested key frame at frame index %d none found near by", expPos);
}
expPos += kKeyFramePos;
}
}
return isPass;
}
bool CodecEncoderTest::testAdaptiveBitRate(const char* encoder, const char* srcPath) {
bool isPass = true;
setUpSource(srcPath);
if (!mInputData) return false;
setUpParams(1);
AMediaFormat* format = mFormats[0];
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
int kAdaptiveBitrateInterval = 3; // change bitrate every 3 seconds.
int kAdaptiveBitrateDurationFrame = mDefFrameRate * kAdaptiveBitrateInterval;
int kBitrateChangeRequests = 7;
AMediaFormat* params = AMediaFormat_new();
mFormats.push_back(params);
// Setting in CBR Mode
AMediaFormat_setInt32(format, TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE, kBitrateModeConstant);
mOutputBuff = &mTestBuff;
mSaveToMem = true;
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
if (!isPass) break;
char log[1000];
snprintf(log, sizeof(log),
"format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
encoder, srcPath, (isAsync ? "async" : "sync"));
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(encoder);
if (!mCodec) {
ALOGE("%s unable to create media codec by name %s", log, encoder);
isPass = false;
continue;
}
if (!configureCodec(format, isAsync, false, true)) return false;
CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
int expOutSize = 0;
int bitrate;
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate);
for (int i = 0; i < kBitrateChangeRequests; i++) {
if (!doWork(kAdaptiveBitrateDurationFrame)) return false;
assert(!mSawInputEOS);
expOutSize += kAdaptiveBitrateInterval * bitrate;
if ((i & 1) == 1) bitrate *= 2;
else bitrate /= 2;
updateBitrate(params, bitrate);
mNumBytesSubmitted = 0;
}
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((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
isPass);
CHECK_ERR((!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), log,
"input pts list and output pts list are not identical", isPass);
/* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
int outSize = mOutputBuff->getOutStreamSize() * 8;
float brDev = abs(expOutSize - outSize) * 100.0f / expOutSize;
ALOGD("%s relative bitrate error is %f %%", log, brDev);
if (brDev > 50) {
ALOGE("%s relative bitrate error is is too large %f %%", log, brDev);
return false;
}
}
return isPass;
}
static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
jstring jMime, jintArray jList0, jintArray jList1,
jintArray jList2, jint colorFormat) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
bool isPass = codecEncoderTest->testSimpleEncode(cEncoder, csrcPath);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
env->ReleaseStringUTFChars(jsrcPath, csrcPath);
return static_cast<jboolean>(isPass);
}
static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
jstring jMime, jintArray jList0, jintArray jList1, jintArray jList2,
jint colorFormat) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
bool isPass = codecEncoderTest->testFlush(cEncoder, csrcPath);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
env->ReleaseStringUTFChars(jsrcPath, csrcPath);
return static_cast<jboolean>(isPass);
}
static jboolean nativeTestReconfigure(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
jstring jMime, jintArray jList0, jintArray jList1,
jintArray jList2, jint colorFormat) {
bool isPass;
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
isPass = codecEncoderTest->testReconfigure(cEncoder, csrcPath);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
env->ReleaseStringUTFChars(jsrcPath, csrcPath);
return static_cast<jboolean>(isPass);
}
static jboolean nativeTestSetForceSyncFrame(JNIEnv* env, jobject, jstring jEncoder,
jstring jsrcPath, jstring jMime, jintArray jList0,
jintArray jList1, jintArray jList2, jint colorFormat) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
bool isPass = codecEncoderTest->testSetForceSyncFrame(cEncoder, csrcPath);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
env->ReleaseStringUTFChars(jsrcPath, csrcPath);
return static_cast<jboolean>(isPass);
}
static jboolean nativeTestAdaptiveBitRate(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
jstring jMime, jintArray jList0, jintArray jList1,
jintArray jList2, jint colorFormat) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
bool isPass = codecEncoderTest->testAdaptiveBitRate(cEncoder, csrcPath);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
env->ReleaseStringUTFChars(jsrcPath, csrcPath);
return static_cast<jboolean>(isPass);
}
static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jEncoder, jstring jMime,
jintArray jList0, jintArray jList1, jintArray jList2,
jint colorFormat) {
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
jsize cLen0 = env->GetArrayLength(jList0);
jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
jsize cLen1 = env->GetArrayLength(jList1);
jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
jsize cLen2 = env->GetArrayLength(jList2);
jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
(int)colorFormat);
bool isPass = codecEncoderTest->testOnlyEos(cEncoder);
delete codecEncoderTest;
env->ReleaseIntArrayElements(jList0, cList0, 0);
env->ReleaseIntArrayElements(jList1, cList1, 0);
env->ReleaseIntArrayElements(jList2, cList2, 0);
env->ReleaseStringUTFChars(jEncoder, cEncoder);
env->ReleaseStringUTFChars(jMime, cmime);
return static_cast<jboolean>(isPass);
}
int registerAndroidMediaV2CtsEncoderTest(JNIEnv* env) {
const JNINativeMethod methodTable[] = {
{"nativeTestSimpleEncode",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestSimpleEncode},
{"nativeTestFlush", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestFlush},
{"nativeTestReconfigure",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestReconfigure},
{"nativeTestSetForceSyncFrame",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestSetForceSyncFrame},
{"nativeTestAdaptiveBitRate",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestAdaptiveBitRate},
{"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
(void*)nativeTestOnlyEos},
};
jclass c = env->FindClass("android/mediav2/cts/CodecEncoderTest");
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
}
extern int registerAndroidMediaV2CtsCodecUnitTest(JNIEnv* env);
extern int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env);
extern int registerAndroidMediaV2CtsDecoderSurfaceTest(JNIEnv* env);
extern int registerAndroidMediaV2CtsEncoderSurfaceTest(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 (registerAndroidMediaV2CtsCodecUnitTest(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsEncoderTest(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsDecoderTest(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsDecoderSurfaceTest(env) != JNI_OK) return JNI_ERR;
if (registerAndroidMediaV2CtsEncoderSurfaceTest(env) != JNI_OK) return JNI_ERR;
return JNI_VERSION_1_6;
}