| /* |
| * 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)); |
| } |