You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
630 lines
26 KiB
630 lines
26 KiB
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "NativeCodecEncoderSurfaceTest"
|
|
#include <log/log.h>
|
|
#include <android/native_window_jni.h>
|
|
#include <NdkMediaExtractor.h>
|
|
#include <NdkMediaMuxer.h>
|
|
#include <jni.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "NativeCodecTestBase.h"
|
|
#include "NativeMediaCommon.h"
|
|
|
|
class CodecEncoderSurfaceTest {
|
|
private:
|
|
const char* mMime;
|
|
ANativeWindow* mWindow;
|
|
AMediaExtractor* mExtractor;
|
|
AMediaFormat* mDecFormat;
|
|
AMediaFormat* mEncFormat;
|
|
AMediaMuxer* mMuxer;
|
|
AMediaCodec* mDecoder;
|
|
AMediaCodec* mEncoder;
|
|
CodecAsyncHandler mAsyncHandleDecoder;
|
|
CodecAsyncHandler mAsyncHandleEncoder;
|
|
bool mIsCodecInAsyncMode;
|
|
bool mSawDecInputEOS;
|
|
bool mSawDecOutputEOS;
|
|
bool mSawEncOutputEOS;
|
|
bool mSignalEOSWithLastFrame;
|
|
int mDecInputCount;
|
|
int mDecOutputCount;
|
|
int mEncOutputCount;
|
|
int mEncBitrate;
|
|
int mEncFramerate;
|
|
int mMaxBFrames;
|
|
int mLatency;
|
|
bool mReviseLatency;
|
|
int mMuxTrackID;
|
|
|
|
OutputManager* mOutputBuff;
|
|
OutputManager mRefBuff;
|
|
OutputManager mTestBuff;
|
|
bool mSaveToMem;
|
|
|
|
bool setUpExtractor(const char* srcPath);
|
|
void deleteExtractor();
|
|
bool configureCodec(bool isAsync, bool signalEOSWithLastFrame);
|
|
void resetContext(bool isAsync, bool signalEOSWithLastFrame);
|
|
void setUpEncoderFormat();
|
|
bool enqueueDecoderInput(size_t bufferIndex);
|
|
bool dequeueDecoderOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo);
|
|
bool dequeueEncoderOutput(size_t bufferIndex, AMediaCodecBufferInfo* info);
|
|
bool tryEncoderOutput(long timeOutUs);
|
|
bool waitForAllEncoderOutputs();
|
|
bool queueEOS();
|
|
bool enqueueDecoderEOS(size_t bufferIndex);
|
|
bool doWork(int frameLimit);
|
|
bool hasSeenError() { return mAsyncHandleDecoder.getError() || mAsyncHandleEncoder.getError(); }
|
|
|
|
public:
|
|
CodecEncoderSurfaceTest(const char* mime, int bitrate, int framerate);
|
|
~CodecEncoderSurfaceTest();
|
|
|
|
bool testSimpleEncode(const char* encoder, const char* decoder, const char* srcPath,
|
|
const char* muxOutPath);
|
|
};
|
|
|
|
CodecEncoderSurfaceTest::CodecEncoderSurfaceTest(const char* mime, int bitrate, int framerate)
|
|
: mMime{mime}, mEncBitrate{bitrate}, mEncFramerate{framerate} {
|
|
mWindow = nullptr;
|
|
mExtractor = nullptr;
|
|
mDecFormat = nullptr;
|
|
mEncFormat = nullptr;
|
|
mMuxer = nullptr;
|
|
mDecoder = nullptr;
|
|
mEncoder = nullptr;
|
|
resetContext(false, false);
|
|
mMaxBFrames = 0;
|
|
mLatency = mMaxBFrames;
|
|
mReviseLatency = false;
|
|
mMuxTrackID = -1;
|
|
}
|
|
|
|
CodecEncoderSurfaceTest::~CodecEncoderSurfaceTest() {
|
|
deleteExtractor();
|
|
if (mWindow) {
|
|
ANativeWindow_release(mWindow);
|
|
mWindow = nullptr;
|
|
}
|
|
if (mEncFormat) {
|
|
AMediaFormat_delete(mEncFormat);
|
|
mEncFormat = nullptr;
|
|
}
|
|
if (mMuxer) {
|
|
AMediaMuxer_delete(mMuxer);
|
|
mMuxer = nullptr;
|
|
}
|
|
if (mDecoder) {
|
|
AMediaCodec_delete(mDecoder);
|
|
mDecoder = nullptr;
|
|
}
|
|
if (mEncoder) {
|
|
AMediaCodec_delete(mEncoder);
|
|
mEncoder = nullptr;
|
|
}
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::setUpExtractor(const char* srcFile) {
|
|
FILE* fp = fopen(srcFile, "rbe");
|
|
struct stat buf {};
|
|
if (fp && !fstat(fileno(fp), &buf)) {
|
|
deleteExtractor();
|
|
mExtractor = AMediaExtractor_new();
|
|
media_status_t res =
|
|
AMediaExtractor_setDataSourceFd(mExtractor, fileno(fp), 0, buf.st_size);
|
|
if (res != AMEDIA_OK) {
|
|
deleteExtractor();
|
|
} else {
|
|
for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor);
|
|
trackID++) {
|
|
AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID);
|
|
const char* mime = nullptr;
|
|
AMediaFormat_getString(currFormat, AMEDIAFORMAT_KEY_MIME, &mime);
|
|
if (mime && strncmp(mime, "video/", strlen("video/")) == 0) {
|
|
AMediaExtractor_selectTrack(mExtractor, trackID);
|
|
AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
|
|
COLOR_FormatYUV420Flexible);
|
|
mDecFormat = currFormat;
|
|
break;
|
|
}
|
|
AMediaFormat_delete(currFormat);
|
|
}
|
|
}
|
|
}
|
|
if (fp) fclose(fp);
|
|
return mDecFormat != nullptr;
|
|
}
|
|
|
|
void CodecEncoderSurfaceTest::deleteExtractor() {
|
|
if (mExtractor) {
|
|
AMediaExtractor_delete(mExtractor);
|
|
mExtractor = nullptr;
|
|
}
|
|
if (mDecFormat) {
|
|
AMediaFormat_delete(mDecFormat);
|
|
mDecFormat = nullptr;
|
|
}
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::configureCodec(bool isAsync, bool signalEOSWithLastFrame) {
|
|
resetContext(isAsync, signalEOSWithLastFrame);
|
|
CHECK_STATUS(mAsyncHandleEncoder.setCallBack(mEncoder, isAsync),
|
|
"AMediaCodec_setAsyncNotifyCallback failed");
|
|
CHECK_STATUS(AMediaCodec_configure(mEncoder, mEncFormat, nullptr, nullptr,
|
|
AMEDIACODEC_CONFIGURE_FLAG_ENCODE),
|
|
"AMediaCodec_configure failed");
|
|
AMediaFormat* inpFormat = AMediaCodec_getInputFormat(mEncoder);
|
|
mReviseLatency = AMediaFormat_getInt32(inpFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
|
|
AMediaFormat_delete(inpFormat);
|
|
CHECK_STATUS(AMediaCodec_createInputSurface(mEncoder, &mWindow),
|
|
"AMediaCodec_createInputSurface failed");
|
|
CHECK_STATUS(mAsyncHandleDecoder.setCallBack(mDecoder, isAsync),
|
|
"AMediaCodec_setAsyncNotifyCallback failed");
|
|
CHECK_STATUS(AMediaCodec_configure(mDecoder, mDecFormat, mWindow, nullptr, 0),
|
|
"AMediaCodec_configure failed");
|
|
return !hasSeenError();
|
|
}
|
|
|
|
void CodecEncoderSurfaceTest::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
|
|
mAsyncHandleDecoder.resetContext();
|
|
mAsyncHandleEncoder.resetContext();
|
|
mIsCodecInAsyncMode = isAsync;
|
|
mSawDecInputEOS = false;
|
|
mSawDecOutputEOS = false;
|
|
mSawEncOutputEOS = false;
|
|
mSignalEOSWithLastFrame = signalEOSWithLastFrame;
|
|
mDecInputCount = 0;
|
|
mDecOutputCount = 0;
|
|
mEncOutputCount = 0;
|
|
}
|
|
|
|
void CodecEncoderSurfaceTest::setUpEncoderFormat() {
|
|
if (mEncFormat) AMediaFormat_delete(mEncFormat);
|
|
mEncFormat = AMediaFormat_new();
|
|
int width, height;
|
|
AMediaFormat_getInt32(mDecFormat, AMEDIAFORMAT_KEY_WIDTH, &width);
|
|
AMediaFormat_getInt32(mDecFormat, AMEDIAFORMAT_KEY_HEIGHT, &height);
|
|
AMediaFormat_setString(mEncFormat, AMEDIAFORMAT_KEY_MIME, mMime);
|
|
AMediaFormat_setInt32(mEncFormat, AMEDIAFORMAT_KEY_WIDTH, width);
|
|
AMediaFormat_setInt32(mEncFormat, AMEDIAFORMAT_KEY_HEIGHT, height);
|
|
AMediaFormat_setInt32(mEncFormat, AMEDIAFORMAT_KEY_BIT_RATE, mEncBitrate);
|
|
AMediaFormat_setInt32(mEncFormat, AMEDIAFORMAT_KEY_FRAME_RATE, mEncFramerate);
|
|
AMediaFormat_setInt32(mEncFormat, TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES, mMaxBFrames);
|
|
AMediaFormat_setInt32(mEncFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatSurface);
|
|
AMediaFormat_setFloat(mEncFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1.0F);
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::enqueueDecoderEOS(size_t bufferIndex) {
|
|
if (!hasSeenError() && !mSawDecInputEOS) {
|
|
CHECK_STATUS(AMediaCodec_queueInputBuffer(mDecoder, bufferIndex, 0, 0, 0,
|
|
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM),
|
|
"Queued Decoder End of Stream Failed");
|
|
mSawDecInputEOS = true;
|
|
ALOGV("Queued Decoder End of Stream");
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::enqueueDecoderInput(size_t bufferIndex) {
|
|
if (AMediaExtractor_getSampleSize(mExtractor) < 0) {
|
|
return enqueueDecoderEOS(bufferIndex);
|
|
} else {
|
|
uint32_t flags = 0;
|
|
size_t bufSize = 0;
|
|
uint8_t* buf = AMediaCodec_getInputBuffer(mDecoder, bufferIndex, &bufSize);
|
|
if (buf == nullptr) {
|
|
ALOGE("AMediaCodec_getInputBuffer failed");
|
|
return false;
|
|
}
|
|
ssize_t size = AMediaExtractor_getSampleSize(mExtractor);
|
|
int64_t pts = AMediaExtractor_getSampleTime(mExtractor);
|
|
if (size > bufSize) {
|
|
ALOGE("extractor sample size exceeds codec input buffer size %zu %zu", size, bufSize);
|
|
return false;
|
|
}
|
|
if (size != AMediaExtractor_readSampleData(mExtractor, buf, bufSize)) {
|
|
ALOGE("AMediaExtractor_readSampleData failed");
|
|
return false;
|
|
}
|
|
if (!AMediaExtractor_advance(mExtractor) && mSignalEOSWithLastFrame) {
|
|
flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
|
|
mSawDecInputEOS = true;
|
|
}
|
|
CHECK_STATUS(AMediaCodec_queueInputBuffer(mDecoder, 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);
|
|
mDecInputCount++;
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::dequeueDecoderOutput(size_t bufferIndex,
|
|
AMediaCodecBufferInfo* bufferInfo) {
|
|
if ((bufferInfo->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
mSawDecOutputEOS = true;
|
|
}
|
|
if (bufferInfo->size > 0 && (bufferInfo->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
|
mDecOutputCount++;
|
|
}
|
|
ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, bufferInfo->size,
|
|
bufferInfo->presentationTimeUs, bufferInfo->flags);
|
|
CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mDecoder, bufferIndex, mWindow != nullptr),
|
|
"AMediaCodec_releaseOutputBuffer failed");
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::dequeueEncoderOutput(size_t bufferIndex,
|
|
AMediaCodecBufferInfo* info) {
|
|
if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
mSawEncOutputEOS = true;
|
|
}
|
|
if (info->size > 0) {
|
|
size_t buffSize;
|
|
uint8_t* buf = AMediaCodec_getOutputBuffer(mEncoder, bufferIndex, &buffSize);
|
|
if (mSaveToMem) {
|
|
mOutputBuff->saveToMemory(buf, info);
|
|
}
|
|
if (mMuxer != nullptr) {
|
|
if (mMuxTrackID == -1) {
|
|
mMuxTrackID = AMediaMuxer_addTrack(mMuxer, AMediaCodec_getOutputFormat(mEncoder));
|
|
CHECK_STATUS(AMediaMuxer_start(mMuxer), "AMediaMuxer_start failed");
|
|
}
|
|
CHECK_STATUS(AMediaMuxer_writeSampleData(mMuxer, mMuxTrackID, buf, info),
|
|
"AMediaMuxer_writeSampleData failed");
|
|
}
|
|
if ((info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
|
mOutputBuff->saveOutPTS(info->presentationTimeUs);
|
|
mEncOutputCount++;
|
|
}
|
|
}
|
|
ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size,
|
|
info->presentationTimeUs, info->flags);
|
|
CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mEncoder, bufferIndex, false),
|
|
"AMediaCodec_releaseOutputBuffer failed");
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::tryEncoderOutput(long timeOutUs) {
|
|
if (mIsCodecInAsyncMode) {
|
|
if (!hasSeenError() && !mSawEncOutputEOS) {
|
|
int retry = 0;
|
|
while (mReviseLatency) {
|
|
if (mAsyncHandleEncoder.hasOutputFormatChanged()) {
|
|
int actualLatency;
|
|
mReviseLatency = false;
|
|
if (AMediaFormat_getInt32(mAsyncHandleEncoder.getOutputFormat(),
|
|
AMEDIAFORMAT_KEY_LATENCY, &actualLatency)) {
|
|
if (mLatency < actualLatency) {
|
|
mLatency = actualLatency;
|
|
return !hasSeenError();
|
|
}
|
|
}
|
|
} else {
|
|
if (retry > kRetryLimit) return false;
|
|
usleep(kQDeQTimeOutUs);
|
|
retry ++;
|
|
}
|
|
}
|
|
callbackObject element = mAsyncHandleEncoder.getOutput();
|
|
if (element.bufferIndex >= 0) {
|
|
if (!dequeueEncoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
|
|
}
|
|
}
|
|
} else {
|
|
AMediaCodecBufferInfo outInfo;
|
|
if (!mSawEncOutputEOS) {
|
|
int bufferID = AMediaCodec_dequeueOutputBuffer(mEncoder, &outInfo, timeOutUs);
|
|
if (bufferID >= 0) {
|
|
if (!dequeueEncoderOutput(bufferID, &outInfo)) return false;
|
|
} else if (bufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
|
AMediaFormat* outFormat = AMediaCodec_getOutputFormat(mEncoder);
|
|
AMediaFormat_getInt32(outFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
|
|
AMediaFormat_delete(outFormat);
|
|
} else if (bufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else if (bufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueOutputBuffer: %d", bufferID);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::waitForAllEncoderOutputs() {
|
|
if (mIsCodecInAsyncMode) {
|
|
while (!hasSeenError() && !mSawEncOutputEOS) {
|
|
if (!tryEncoderOutput(kQDeQTimeOutUs)) return false;
|
|
}
|
|
} else {
|
|
while (!mSawEncOutputEOS) {
|
|
if (!tryEncoderOutput(kQDeQTimeOutUs)) return false;
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::queueEOS() {
|
|
if (mIsCodecInAsyncMode) {
|
|
while (!hasSeenError() && !mSawDecInputEOS) {
|
|
callbackObject element = mAsyncHandleDecoder.getWork();
|
|
if (element.bufferIndex >= 0) {
|
|
if (element.isInput) {
|
|
if (!enqueueDecoderEOS(element.bufferIndex)) return false;
|
|
} else {
|
|
if (!dequeueDecoderOutput(element.bufferIndex, &element.bufferInfo)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
AMediaCodecBufferInfo outInfo;
|
|
while (!mSawDecInputEOS) {
|
|
ssize_t oBufferID = AMediaCodec_dequeueOutputBuffer(mDecoder, &outInfo, kQDeQTimeOutUs);
|
|
if (oBufferID >= 0) {
|
|
if (!dequeueDecoderOutput(oBufferID, &outInfo)) return false;
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueOutputBuffer: %zd", oBufferID);
|
|
return false;
|
|
}
|
|
ssize_t iBufferId = AMediaCodec_dequeueInputBuffer(mDecoder, kQDeQTimeOutUs);
|
|
if (iBufferId >= 0) {
|
|
if (!enqueueDecoderEOS(iBufferId)) return false;
|
|
} else if (iBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueInputBuffer: %zd", iBufferId);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mIsCodecInAsyncMode) {
|
|
// output processing after queuing EOS is done in waitForAllOutputs()
|
|
while (!hasSeenError() && !mSawDecOutputEOS) {
|
|
callbackObject element = mAsyncHandleDecoder.getOutput();
|
|
if (element.bufferIndex >= 0) {
|
|
if (!dequeueDecoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
|
|
}
|
|
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
|
|
if (mDecOutputCount - mEncOutputCount > mLatency) {
|
|
if (!tryEncoderOutput(-1)) return false;
|
|
}
|
|
}
|
|
} else {
|
|
AMediaCodecBufferInfo outInfo;
|
|
// output processing after queuing EOS is done in waitForAllOutputs()
|
|
while (!mSawDecOutputEOS) {
|
|
if (!mSawDecOutputEOS) {
|
|
ssize_t oBufferID =
|
|
AMediaCodec_dequeueOutputBuffer(mDecoder, &outInfo, kQDeQTimeOutUs);
|
|
if (oBufferID >= 0) {
|
|
if (!dequeueDecoderOutput(oBufferID, &outInfo)) return false;
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueOutputBuffer: %zd", oBufferID);
|
|
return false;
|
|
}
|
|
}
|
|
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
|
|
if (mDecOutputCount - mEncOutputCount > mLatency) {
|
|
if (!tryEncoderOutput(-1)) return false;
|
|
}
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::doWork(int frameLimit) {
|
|
int frameCnt = 0;
|
|
if (mIsCodecInAsyncMode) {
|
|
// output processing after queuing EOS is done in waitForAllOutputs()
|
|
while (!hasSeenError() && !mSawDecInputEOS && frameCnt < frameLimit) {
|
|
callbackObject element = mAsyncHandleDecoder.getWork();
|
|
if (element.bufferIndex >= 0) {
|
|
if (element.isInput) {
|
|
if (!enqueueDecoderInput(element.bufferIndex)) return false;
|
|
frameCnt++;
|
|
} else {
|
|
if (!dequeueDecoderOutput(element.bufferIndex, &element.bufferInfo)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// check decoder EOS
|
|
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
|
|
// encoder output
|
|
if (mDecOutputCount - mEncOutputCount > mLatency) {
|
|
if (!tryEncoderOutput(-1)) return false;
|
|
}
|
|
}
|
|
} else {
|
|
AMediaCodecBufferInfo outInfo;
|
|
// output processing after queuing EOS is done in waitForAllOutputs()
|
|
while (!mSawDecInputEOS && frameCnt < frameLimit) {
|
|
ssize_t oBufferID = AMediaCodec_dequeueOutputBuffer(mDecoder, &outInfo, kQDeQTimeOutUs);
|
|
if (oBufferID >= 0) {
|
|
if (!dequeueDecoderOutput(oBufferID, &outInfo)) return false;
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueOutputBuffer: %zd", oBufferID);
|
|
return false;
|
|
}
|
|
ssize_t iBufferId = AMediaCodec_dequeueInputBuffer(mDecoder, kQDeQTimeOutUs);
|
|
if (iBufferId >= 0) {
|
|
if (!enqueueDecoderInput(iBufferId)) return false;
|
|
frameCnt++;
|
|
} else if (iBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
|
|
} else {
|
|
ALOGE("unexpected return value from *_dequeueInputBuffer: %zd", iBufferId);
|
|
return false;
|
|
}
|
|
if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
|
|
if (mDecOutputCount - mEncOutputCount > mLatency) {
|
|
if (!tryEncoderOutput(-1)) return false;
|
|
}
|
|
}
|
|
}
|
|
return !hasSeenError();
|
|
}
|
|
|
|
bool CodecEncoderSurfaceTest::testSimpleEncode(const char* encoder, const char* decoder,
|
|
const char* srcPath, const char* muxOutPath) {
|
|
bool isPass = true;
|
|
if (!setUpExtractor(srcPath)) {
|
|
ALOGE("setUpExtractor failed");
|
|
return false;
|
|
}
|
|
setUpEncoderFormat();
|
|
bool muxOutput = true;
|
|
|
|
/* TODO(b/149027258) */
|
|
if (true) mSaveToMem = false;
|
|
else mSaveToMem = true;
|
|
auto ref = &mRefBuff;
|
|
auto test = &mTestBuff;
|
|
int loopCounter = 0;
|
|
const bool boolStates[]{true, false};
|
|
for (bool isAsync : boolStates) {
|
|
if (!isPass) break;
|
|
AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_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 */
|
|
mEncoder = AMediaCodec_createCodecByName(encoder);
|
|
mDecoder = AMediaCodec_createCodecByName(decoder);
|
|
if (!mDecoder || !mEncoder) {
|
|
ALOGE("unable to create media codec by name %s or %s", encoder, decoder);
|
|
isPass = false;
|
|
continue;
|
|
}
|
|
|
|
FILE* ofp = nullptr;
|
|
if (muxOutput && loopCounter == 0) {
|
|
int muxerFormat = 0;
|
|
if (!strcmp(mMime, AMEDIA_MIMETYPE_VIDEO_VP8) ||
|
|
!strcmp(mMime, AMEDIA_MIMETYPE_VIDEO_VP9)) {
|
|
muxerFormat = OUTPUT_FORMAT_WEBM;
|
|
} else {
|
|
muxerFormat = OUTPUT_FORMAT_MPEG_4;
|
|
}
|
|
ofp = fopen(muxOutPath, "wbe+");
|
|
if (ofp) {
|
|
mMuxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)muxerFormat);
|
|
}
|
|
}
|
|
if (!configureCodec(mIsCodecInAsyncMode, mSignalEOSWithLastFrame)) return false;
|
|
CHECK_STATUS(AMediaCodec_start(mEncoder), "AMediaCodec_start failed");
|
|
CHECK_STATUS(AMediaCodec_start(mDecoder), "AMediaCodec_start failed");
|
|
if (!doWork(INT32_MAX)) return false;
|
|
if (!queueEOS()) return false;
|
|
if (!waitForAllEncoderOutputs()) return false;
|
|
if (muxOutput) {
|
|
if (mMuxer != nullptr) {
|
|
CHECK_STATUS(AMediaMuxer_stop(mMuxer), "AMediaMuxer_stop failed");
|
|
mMuxTrackID = -1;
|
|
CHECK_STATUS(AMediaMuxer_delete(mMuxer), "AMediaMuxer_delete failed");
|
|
mMuxer = nullptr;
|
|
}
|
|
if (ofp) fclose(ofp);
|
|
}
|
|
CHECK_STATUS(AMediaCodec_stop(mDecoder), "AMediaCodec_stop failed");
|
|
CHECK_STATUS(AMediaCodec_stop(mEncoder), "AMediaCodec_stop failed");
|
|
char log[1000];
|
|
snprintf(log, sizeof(log), "format: %s \n codec: %s, file: %s, mode: %s:: ",
|
|
AMediaFormat_toString(mEncFormat), encoder, srcPath, (isAsync ? "async" : "sync"));
|
|
CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
|
|
CHECK_ERR((0 == mDecInputCount), log, "no input sent", isPass);
|
|
CHECK_ERR((0 == mDecOutputCount), log, "no decoder output received", isPass);
|
|
CHECK_ERR((0 == mEncOutputCount), log, "no encoder output received", isPass);
|
|
CHECK_ERR((mDecInputCount != mDecOutputCount), log, "decoder input count != output count",
|
|
isPass);
|
|
/* TODO(b/153127506)
|
|
* Currently disabling all encoder output checks. Added checks only for encoder timeStamp
|
|
* is in increasing order or not.
|
|
* Once issue is fixed remove increasing timestamp check and enable encoder checks.
|
|
*/
|
|
/*CHECK_ERR((mEncOutputCount != mDecOutputCount), log,
|
|
"encoder output count != decoder output count", isPass);
|
|
CHECK_ERR((loopCounter != 0 && !ref->equals(test)), log, "output is flaky", isPass);
|
|
CHECK_ERR((loopCounter == 0 && !ref->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
|
|
log, "input pts list and output pts list are not identical", isPass);*/
|
|
CHECK_ERR(loopCounter == 0 && (!ref->isPtsStrictlyIncreasing(INT32_MIN)), log,
|
|
"Ref pts is not strictly increasing", isPass);
|
|
CHECK_ERR(loopCounter != 0 && (!test->isPtsStrictlyIncreasing(INT32_MIN)), log,
|
|
"Test pts is not strictly increasing", isPass);
|
|
|
|
loopCounter++;
|
|
ANativeWindow_release(mWindow);
|
|
mWindow = nullptr;
|
|
CHECK_STATUS(AMediaCodec_delete(mEncoder), "AMediaCodec_delete failed");
|
|
mEncoder = nullptr;
|
|
CHECK_STATUS(AMediaCodec_delete(mDecoder), "AMediaCodec_delete failed");
|
|
mDecoder = nullptr;
|
|
}
|
|
return isPass;
|
|
}
|
|
|
|
static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jDecoder,
|
|
jstring jMime, jstring jtestFile, jstring jmuxFile,
|
|
jint jBitrate, jint jFramerate) {
|
|
const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
|
|
const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
|
|
const char* cMime = env->GetStringUTFChars(jMime, nullptr);
|
|
const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
|
|
const char* cMuxFile = env->GetStringUTFChars(jmuxFile, nullptr);
|
|
auto codecEncoderSurfaceTest =
|
|
new CodecEncoderSurfaceTest(cMime, (int)jBitrate, (int)jFramerate);
|
|
bool isPass =
|
|
codecEncoderSurfaceTest->testSimpleEncode(cEncoder, cDecoder, cTestFile, cMuxFile);
|
|
delete codecEncoderSurfaceTest;
|
|
env->ReleaseStringUTFChars(jEncoder, cEncoder);
|
|
env->ReleaseStringUTFChars(jDecoder, cDecoder);
|
|
env->ReleaseStringUTFChars(jMime, cMime);
|
|
env->ReleaseStringUTFChars(jtestFile, cTestFile);
|
|
env->ReleaseStringUTFChars(jmuxFile, cMuxFile);
|
|
return static_cast<jboolean>(isPass);
|
|
}
|
|
|
|
int registerAndroidMediaV2CtsEncoderSurfaceTest(JNIEnv* env) {
|
|
const JNINativeMethod methodTable[] = {
|
|
{"nativeTestSimpleEncode",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
|
|
"String;II)Z",
|
|
(void*)nativeTestSimpleEncode},
|
|
};
|
|
jclass c = env->FindClass("android/mediav2/cts/CodecEncoderSurfaceTest");
|
|
return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
|
|
}
|