blob: e7c24708e47712a76d3df2e8ff54e30ead8244fc [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 : public CodecTestBase {
private:
uint8_t* mInputData;
size_t mInputLength;
int mInputBufferReadOffset;
int mNumBytesSubmitted;
int mLoopBackFrameLimit;
bool mIsLoopBack;
int64_t mInputOffsetPts;
std::vector<AMediaFormat*> mFormats;
int mNumSyncFramesReceived;
std::vector<int> mSyncFramesPos;
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 deleteParams();
bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
bool isEncoder) override;
void resetContext(bool isAsync, bool signalEOSWithLastFrame) override;
bool enqueueInput(size_t bufferIndex) override;
bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
bool doWork(int frameLimit) override;
bool isTestStateValid() override;
bool 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, const char* cfgParams, const char* cfgReconfigParams,
const char* separator);
~CodecEncoderTest();
bool testSimpleEncode(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, const char* cfgParams,
const char* cfgReconfigParams, const char* separator)
: CodecTestBase(mime) {
mFormats.push_back(deSerializeMediaFormat(cfgParams, separator));
if (cfgReconfigParams) {
mFormats.push_back(deSerializeMediaFormat(cfgReconfigParams, separator));
}
if (mIsVideo && mFormats[0] != nullptr) {
AMediaFormat_getInt32(mFormats[0], AMEDIAFORMAT_KEY_COLOR_FORMAT, &mColorFormat);
}
mInputData = nullptr;
mInputLength = 0;
mInputBufferReadOffset = 0;
mNumBytesSubmitted = 0;
mLoopBackFrameLimit = 0;
mIsLoopBack = false;
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::deleteParams() {
for (auto format : mFormats) AMediaFormat_delete(format);
mFormats.clear();
}
bool CodecEncoderTest::configureCodec(AMediaFormat* format, bool isAsync,
bool signalEOSWithLastFrame, bool isEncoder) {
if (!initFormat(format)) return false;
return CodecTestBase::configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
}
void CodecEncoderTest::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
CodecTestBase::resetContext(isAsync, signalEOSWithLastFrame);
mInputBufferReadOffset = 0;
mNumBytesSubmitted = 0;
mInputOffsetPts = 0;
mNumSyncFramesReceived = 0;
mSyncFramesPos.clear();
}
void CodecEncoderTest::fillByteBuffer(uint8_t* inputBuffer) {
int width, height, tileWidth, tileHeight;
int offset = 0, frmOffset = mInputBufferReadOffset;
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 (mInputBufferReadOffset >= mInputLength) {
if (!mIsLoopBack) return enqueueEOS(bufferIndex);
mInputBufferReadOffset = 0; // loop back to beginning
}
{
int size = 0;
int flags = 0;
int64_t pts = mInputOffsetPts;
size_t buffSize;
uint8_t* inputBuffer = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &buffSize);
RETURN_IF_NULL(inputBuffer, std::string{"AMediaCodec_getInputBuffer returned nullptr"})
if (mIsAudio) {
pts += mNumBytesSubmitted * 1000000LL / (2 * mChannels * mSampleRate);
size = std::min(buffSize, mInputLength - mInputBufferReadOffset);
memcpy(inputBuffer, mInputData + mInputBufferReadOffset, size);
if (mSignalEOSWithLastFrame) {
if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit)
: (mInputBufferReadOffset + size >= mInputLength)) {
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
mSawInputEOS = true;
}
}
mInputBufferReadOffset += size;
} else {
pts += mInputCount * 1000000LL / mDefFrameRate;
size = mWidth * mHeight * 3 / 2;
int frmSize = kInpFrmWidth * kInpFrmHeight * 3 / 2;
RETURN_IF_TRUE(mInputBufferReadOffset + frmSize > mInputLength,
std::string{"received partial frame to encode"})
RETURN_IF_TRUE(size > buffSize,
StringFormat("frame size exceeds buffer capacity of input buffer %d %zu",
size, buffSize))
if (mWidth == kInpFrmWidth && mHeight == kInpFrmHeight) {
memcpy(inputBuffer, mInputData + mInputBufferReadOffset, size);
} else {
fillByteBuffer(inputBuffer);
}
if (mSignalEOSWithLastFrame) {
if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit)
: (mInputBufferReadOffset + frmSize >= mInputLength)) {
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
mSawInputEOS = true;
}
}
mInputBufferReadOffset += frmSize;
}
mNumBytesSubmitted += size;
RETURN_IF_FAIL(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);
RETURN_IF_NULL(buf, std::string{"AMediaCodec_getOutputBuffer returned nullptr"})
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);
RETURN_IF_FAIL(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false),
"AMediaCodec_releaseOutputBuffer failed")
return !hasSeenError();
}
bool CodecEncoderTest::doWork(int frameLimit) {
mLoopBackFrameLimit = frameLimit;
return CodecTestBase::doWork(frameLimit);
}
bool CodecEncoderTest::isTestStateValid() {
if (!CodecTestBase::isTestStateValid()) return false;
RETURN_IF_TRUE((mIsAudio || (mIsVideo && mMaxBFrames == 0)) &&
!mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts),
std::string{"Output timestamps are not strictly increasing \n"}.append(
mOutputBuff->getErrorMsg()))
RETURN_IF_TRUE(mIsVideo && !mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0),
std::string{"Input pts list and Output pts list are not identical \n"}.append(
mOutputBuff->getErrorMsg()))
return true;
}
bool CodecEncoderTest::initFormat(AMediaFormat* format) {
if (mIsAudio) {
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_SAMPLE_RATE))
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &mChannels),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_CHANNEL_COUNT))
} else {
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_WIDTH))
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_HEIGHT))
RETURN_IF_FALSE(AMediaFormat_getInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES,
&mMaxBFrames),
StringFormat("format does not have key %s",
TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES))
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, &mColorFormat),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_COLOR_FORMAT))
}
return true;
}
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s \n", encoder))
setUpSource(file);
RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", file))
if (!configureCodec(format, false, true, true)) 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();
}
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) {
setUpSource(srcPath);
RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
/* TODO(b/149027258) */
if (true) mSaveToMem = false;
else mSaveToMem = true;
auto ref = mRefBuff;
auto test = mTestBuff;
const bool boolStates[]{true, false};
for (auto format : mFormats) {
RETURN_IF_NULL(format,
std::string{"encountered error during deserialization of media format"})
int loopCounter = 0;
for (auto eosType : boolStates) {
for (auto isAsync : boolStates) {
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", encoder))
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, encoder) != 0;
AMediaCodec_releaseName(mCodec, name);
RETURN_IF_TRUE(res,
StringFormat("Codec name mismatch act/got: %s/%s", encoder, name))
if (!configureCodec(format, isAsync, eosType, true)) return false;
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{"Encoder output is not consistent across runs \n"}
.append(test->getErrorMsg()))
loopCounter++;
}
}
}
return true;
}
bool CodecEncoderTest::testReconfigure(const char* encoder, const char* srcPath) {
setUpSource(srcPath);
RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
auto configRef = mReconfBuff;
if (mFormats.size() > 1) {
auto format = mFormats[1];
RETURN_IF_NULL(format,
std::string{"encountered error during deserialization of media format"})
RETURN_IF_FALSE(encodeToMemory(srcPath, encoder, INT32_MAX, format, configRef),
StringFormat("encodeToMemory failed for file: %s codec: %s \n format: %s",
srcPath, encoder, AMediaFormat_toString(format)))
}
auto format = mFormats[0];
RETURN_IF_NULL(format, std::string{"encountered error during deserialization of media format"})
auto ref = mRefBuff;
RETURN_IF_FALSE(encodeToMemory(srcPath, encoder, INT32_MAX, format, ref),
StringFormat("encodeToMemory failed for file: %s codec: %s \n format: %s",
srcPath, encoder, AMediaFormat_toString(format)))
auto test = mTestBuff;
mOutputBuff = test;
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
/* 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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec %s", encoder))
if (!configureCodec(format, isAsync, true, true)) return false;
/* test reconfigure in init state */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
/* test reconfigure in running state before queuing input */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
RETURN_IF_FAIL(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;
RETURN_IF_FAIL(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;
RETURN_IF_FAIL(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed")
RETURN_IF_TRUE(!ref->equals(test),
std::string{"Encoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
/* test reconfigure codec at eos state */
if (!reConfigureCodec(format, !isAsync, false, true)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
test->reset();
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_TRUE(!ref->equals(test),
std::string{"Encoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
/* test reconfigure codec for new format */
if (mFormats.size() > 1) {
if (!reConfigureCodec(mFormats[1], isAsync, false, true)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
test->reset();
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_TRUE(!configRef->equals(test),
std::string{"Encoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
}
mSaveToMem = false;
RETURN_IF_FAIL(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed")
mCodec = nullptr;
}
return true;
}
bool CodecEncoderTest::testOnlyEos(const char* encoder) {
/* 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];
RETURN_IF_NULL(format,
std::string{"encountered error during deserialization of media format"})
int loopCounter = 0;
for (auto isAsync : boolStates) {
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s", encoder))
if (!configureCodec(format, isAsync, false, true)) 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{"Encoder output is not consistent across runs \n"}.append(
test->getErrorMsg()))
loopCounter++;
}
return true;
}
bool CodecEncoderTest::testSetForceSyncFrame(const char* encoder, const char* srcPath) {
setUpSource(srcPath);
RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
AMediaFormat* format = mFormats[0];
RETURN_IF_NULL(format,
std::string{"encountered error during deserialization of media format"})
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
// 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) {
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name%s", encoder))
if (!configureCodec(format, isAsync, false, true)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
for (int i = 0; i < kNumKeyFrameRequests; i++) {
if (!doWork(kKeyFramePos)) return false;
RETURN_IF_TRUE(mSawInputEOS,
StringFormat("Unable to encode %d frames as the input resource contains "
"only %d frames \n",
kKeyFramePos, mInputCount))
forceSyncFrame(params);
mInputBufferReadOffset = 0;
}
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((mNumSyncFramesReceived < kNumKeyFrameRequests),
StringFormat("Received only %d key frames for %d key frame requests \n",
mNumSyncFramesReceived, kNumKeyFrameRequests))
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 true;
}
bool CodecEncoderTest::testAdaptiveBitRate(const char* encoder, const char* srcPath) {
setUpSource(srcPath);
RETURN_IF_NULL(mInputData, StringFormat("unable to open input file %s", srcPath))
AMediaFormat* format = mFormats[0];
RETURN_IF_NULL(format,
std::string{"encountered error during deserialization of media format"})
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mDefFrameRate),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_FRAME_RATE))
int kAdaptiveBitrateInterval = 3; // change bitrate every 3 seconds.
int kAdaptiveBitrateDurationFrame = mDefFrameRate * kAdaptiveBitrateInterval;
int kBitrateChangeRequests = 7;
// TODO(b/251265293) Reduce the allowed deviation after improving the test conditions
float kMaxBitrateDeviation = 60.0; // allowed bitrate deviation in %
AMediaFormat* params = AMediaFormat_new();
mFormats.push_back(params);
mOutputBuff = mTestBuff;
mSaveToMem = true;
const bool boolStates[]{true, false};
for (auto isAsync : boolStates) {
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);
RETURN_IF_NULL(mCodec, StringFormat("unable to create codec by name %s", encoder))
if (!configureCodec(format, isAsync, false, true)) return false;
RETURN_IF_FAIL(AMediaCodec_start(mCodec), "AMediaCodec_start failed")
int expOutSize = 0;
int bitrate;
RETURN_IF_FALSE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate),
StringFormat("format does not have key %s", AMEDIAFORMAT_KEY_BIT_RATE))
for (int i = 0; i < kBitrateChangeRequests; i++) {
if (!doWork(kAdaptiveBitrateDurationFrame)) return false;
RETURN_IF_TRUE(mSawInputEOS,
StringFormat("Unable to encode %d frames as the input resource contains "
"only %d frames \n",
kAdaptiveBitrateDurationFrame, mInputCount))
expOutSize += kAdaptiveBitrateInterval * bitrate;
if ((i & 1) == 1) bitrate *= 2;
else bitrate /= 2;
updateBitrate(params, bitrate);
mInputBufferReadOffset = 0;
}
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;
/* 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;
RETURN_IF_TRUE(brDev > kMaxBitrateDeviation,
StringFormat("Relative Bitrate error is too large : %f %%\n", brDev))
}
return true;
}
static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
jstring jMime, jstring jCfgParams, jstring jSeparator,
jobject jRetMsg) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cCfgParams, nullptr, cSeparator);
bool isPass = codecEncoderTest->testSimpleEncode(cEncoder, csrcPath);
std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
delete codecEncoderTest;
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(jCfgParams, cCfgParams);
env->ReleaseStringUTFChars(jSeparator, cSeparator);
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, jstring jCfgParams, jstring jReconfigCfgParams,
jstring jSeparator, jobject jRetMsg) {
bool isPass;
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
const char* cReconfigCfgParams = jReconfigCfgParams != nullptr
? env->GetStringUTFChars(jReconfigCfgParams, nullptr)
: nullptr;
const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cCfgParams, cReconfigCfgParams, cSeparator);
isPass = codecEncoderTest->testReconfigure(cEncoder, csrcPath);
std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
delete codecEncoderTest;
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(jCfgParams, cCfgParams);
if (cReconfigCfgParams != nullptr) {
env->ReleaseStringUTFChars(jReconfigCfgParams, cReconfigCfgParams);
}
env->ReleaseStringUTFChars(jSeparator, cSeparator);
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, jstring jCfgParams,
jstring jSeparator, jobject jRetMsg) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cCfgParams, nullptr, cSeparator);
bool isPass = codecEncoderTest->testSetForceSyncFrame(cEncoder, csrcPath);
std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
delete codecEncoderTest;
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(jCfgParams, cCfgParams);
env->ReleaseStringUTFChars(jSeparator, cSeparator);
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, jstring jCfgParams, jstring jSeparator,
jobject jRetMsg) {
const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cCfgParams, nullptr, cSeparator);
bool isPass = codecEncoderTest->testAdaptiveBitRate(cEncoder, csrcPath);
std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
delete codecEncoderTest;
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(jCfgParams, cCfgParams);
env->ReleaseStringUTFChars(jSeparator, cSeparator);
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,
jstring jCfgParams, jstring jSeparator, jobject jRetMsg) {
const char* cmime = env->GetStringUTFChars(jMime, nullptr);
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
const char* cCfgParams = env->GetStringUTFChars(jCfgParams, nullptr);
const char* cSeparator = env->GetStringUTFChars(jSeparator, nullptr);
auto codecEncoderTest = new CodecEncoderTest(cmime, cCfgParams, nullptr, cSeparator);
bool isPass = codecEncoderTest->testOnlyEos(cEncoder);
std::string msg = isPass ? std::string{} : codecEncoderTest->getErrorMsg();
delete codecEncoderTest;
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(jCfgParams, cCfgParams);
env->ReleaseStringUTFChars(jSeparator, cSeparator);
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;Ljava/lang/String;Ljava/lang/"
"String;Ljava/lang/StringBuilder;)Z",
(void*)nativeTestSimpleEncode},
{"nativeTestReconfigure",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;Ljava/lang/String;Ljava/lang/StringBuilder;)Z",
(void*)nativeTestReconfigure},
{"nativeTestSetForceSyncFrame",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;Ljava/lang/StringBuilder;)Z",
(void*)nativeTestSetForceSyncFrame},
{"nativeTestAdaptiveBitRate",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;Ljava/lang/StringBuilder;)Z",
(void*)nativeTestAdaptiveBitRate},
{"nativeTestOnlyEos",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
"StringBuilder;)Z",
(void*)nativeTestOnlyEos},
};
jclass c = env->FindClass("android/mediav2/cts/CodecEncoderTest");
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
}
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 (registerAndroidMediaV2CtsEncoderTest(env) != JNI_OK) return JNI_ERR;
return JNI_VERSION_1_6;
}