| /* |
| * 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 "NativeCodecDecoderTest" |
| #include <log/log.h> |
| #include <android/native_window_jni.h> |
| #include <NdkMediaExtractor.h> |
| #include <jni.h> |
| #include <sys/stat.h> |
| |
| #include <array> |
| #include <fstream> |
| #include <string> |
| |
| #include "NativeCodecTestBase.h" |
| #include "NativeMediaCommon.h" |
| |
| class CodecDecoderTest final : public CodecTestBase { |
| private: |
| bool mIsInterlaced; |
| uint8_t* mRefData; |
| size_t mRefLength; |
| AMediaExtractor* mExtractor; |
| AMediaFormat* mInpDecFormat; |
| AMediaFormat* mInpDecDupFormat; |
| std::vector<std::pair<void*, size_t>> mCsdBuffers; |
| int mCurrCsdIdx; |
| ANativeWindow* mWindow; |
| |
| void setUpAudioReference(const char* refFile); |
| void deleteReference(); |
| bool setUpExtractor(const char* srcFile, int colorFormat); |
| void deleteExtractor(); |
| bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame, |
| bool isEncoder) override; |
| bool enqueueInput(size_t bufferIndex) override; |
| bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override; |
| bool queueCodecConfig(); |
| bool enqueueCodecConfig(int32_t bufferIndex); |
| bool decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit, |
| OutputManager* ref, int64_t pts, SeekMode mode); |
| |
| public: |
| explicit CodecDecoderTest(const char* mime, ANativeWindow* window); |
| ~CodecDecoderTest(); |
| |
| bool testSimpleDecode(const char* decoder, const char* testFile, const char* refFile, |
| int colorFormat, float rmsError, uLong checksum); |
| bool testFlush(const char* decoder, const char* testFile, int colorFormat); |
| bool testOnlyEos(const char* decoder, const char* testFile, int colorFormat); |
| bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile, int colorFormat); |
| }; |
| |
| CodecDecoderTest::CodecDecoderTest(const char* mime, ANativeWindow* window) |
| : CodecTestBase(mime), |
| mRefData(nullptr), |
| mRefLength(0), |
| mExtractor(nullptr), |
| mInpDecFormat(nullptr), |
| mInpDecDupFormat(nullptr), |
| mCurrCsdIdx(0), |
| mWindow{window} {} |
| |
| CodecDecoderTest::~CodecDecoderTest() { |
| deleteReference(); |
| deleteExtractor(); |
| } |
| |
| void CodecDecoderTest::setUpAudioReference(const char* refFile) { |
| FILE* fp = fopen(refFile, "rbe"); |
| struct stat buf {}; |
| if (fp && !fstat(fileno(fp), &buf)) { |
| deleteReference(); |
| mRefLength = buf.st_size; |
| mRefData = new uint8_t[mRefLength]; |
| fread(mRefData, sizeof(uint8_t), mRefLength, fp); |
| } else { |
| ALOGE("unable to open input file %s", refFile); |
| } |
| if (fp) fclose(fp); |
| } |
| |
| void CodecDecoderTest::deleteReference() { |
| if (mRefData) { |
| delete[] mRefData; |
| mRefData = nullptr; |
| } |
| mRefLength = 0; |
| } |
| |
| bool CodecDecoderTest::setUpExtractor(const char* srcFile, int colorFormat) { |
| FILE* fp = fopen(srcFile, "rbe"); |
| 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 { |
| mBytesPerSample = (colorFormat == COLOR_FormatYUVP010) ? 2 : 1; |
| for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor); |
| trackID++) { |
| AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID); |
| const char* mime = nullptr; |
| AMediaFormat_getString(currFormat, AMEDIAFORMAT_KEY_MIME, &mime); |
| if (mime && strcmp(mMime, mime) == 0) { |
| AMediaExtractor_selectTrack(mExtractor, trackID); |
| if (!mIsAudio) { |
| AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, |
| colorFormat); |
| } |
| mInpDecFormat = currFormat; |
| // TODO: determine this from the extractor format when it becomes exposed. |
| mIsInterlaced = strstr(srcFile, "_interlaced_") != nullptr; |
| break; |
| } |
| AMediaFormat_delete(currFormat); |
| } |
| } |
| } |
| if (fp) fclose(fp); |
| return mInpDecFormat != nullptr; |
| } |
| |
| void CodecDecoderTest::deleteExtractor() { |
| if (mExtractor) { |
| AMediaExtractor_delete(mExtractor); |
| mExtractor = nullptr; |
| } |
| if (mInpDecFormat) { |
| AMediaFormat_delete(mInpDecFormat); |
| mInpDecFormat = nullptr; |
| } |
| if (mInpDecDupFormat) { |
| AMediaFormat_delete(mInpDecDupFormat); |
| mInpDecDupFormat = nullptr; |
| } |
| } |
| |
| bool CodecDecoderTest::configureCodec(AMediaFormat* format, bool isAsync, |
| bool signalEOSWithLastFrame, bool isEncoder) { |
| resetContext(isAsync, signalEOSWithLastFrame); |
| CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync), |
| "AMediaCodec_setAsyncNotifyCallback failed"); |
| CHECK_STATUS(AMediaCodec_configure(mCodec, format, mWindow, nullptr, |
| isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0), |
| "AMediaCodec_configure failed"); |
| return true; |
| } |
| |
| bool CodecDecoderTest::enqueueCodecConfig(int32_t bufferIndex) { |
| size_t bufSize; |
| uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize); |
| if (buf == nullptr) { |
| ALOGE("AMediaCodec_getInputBuffer failed"); |
| return false; |
| } |
| void* csdBuffer = mCsdBuffers[mCurrCsdIdx].first; |
| size_t csdSize = mCsdBuffers[mCurrCsdIdx].second; |
| if (bufSize < csdSize) { |
| ALOGE("csd exceeds input buffer size, csdSize: %zu bufSize: %zu", csdSize, bufSize); |
| return false; |
| } |
| memcpy((void*)buf, csdBuffer, csdSize); |
| uint32_t flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG; |
| CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, csdSize, 0, flags), |
| "AMediaCodec_queueInputBuffer failed"); |
| return !hasSeenError(); |
| } |
| |
| bool CodecDecoderTest::enqueueInput(size_t bufferIndex) { |
| if (AMediaExtractor_getSampleSize(mExtractor) < 0) { |
| return enqueueEOS(bufferIndex); |
| } else { |
| uint32_t flags = 0; |
| size_t bufSize; |
| uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize); |
| 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; |
| mSawInputEOS = true; |
| } |
| CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags), |
| "AMediaCodec_queueInputBuffer failed"); |
| ALOGV("input: id: %zu size: %zu pts: %" PRId64 " flags: %d", bufferIndex, size, pts, |
| flags); |
| if (size > 0) { |
| mOutputBuff->saveInPTS(pts); |
| mInputCount++; |
| } |
| } |
| return !hasSeenError(); |
| } |
| |
| bool CodecDecoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) { |
| if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) { |
| mSawOutputEOS = true; |
| } |
| if (info->size > 0 && (info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) { |
| if (mSaveToMem) { |
| size_t buffSize; |
| uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize); |
| if (mIsAudio) { |
| mOutputBuff->saveToMemory(buf, info); |
| mOutputBuff->updateChecksum(buf, info); |
| } else { |
| AMediaFormat* format = |
| mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat; |
| int32_t width, height, stride; |
| AMediaFormat_getInt32(format, "width", &width); |
| AMediaFormat_getInt32(format, "height", &height); |
| AMediaFormat_getInt32(format, "stride", &stride); |
| mOutputBuff->updateChecksum(buf, info, width, height, stride, mBytesPerSample); |
| } |
| } |
| mOutputBuff->saveOutPTS(info->presentationTimeUs); |
| mOutputCount++; |
| } |
| ALOGV("output: id: %zu size: %d pts: %" PRId64 " flags: %d", bufferIndex, info->size, |
| info->presentationTimeUs, info->flags); |
| CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, mWindow != nullptr), |
| "AMediaCodec_releaseOutputBuffer failed"); |
| return !hasSeenError(); |
| } |
| |
| bool CodecDecoderTest::queueCodecConfig() { |
| bool isOk = true; |
| if (mIsCodecInAsyncMode) { |
| for (mCurrCsdIdx = 0; !hasSeenError() && isOk && mCurrCsdIdx < mCsdBuffers.size(); |
| mCurrCsdIdx++) { |
| callbackObject element = mAsyncHandle.getInput(); |
| if (element.bufferIndex >= 0) { |
| isOk = enqueueCodecConfig(element.bufferIndex); |
| } |
| } |
| } else { |
| int bufferIndex; |
| for (mCurrCsdIdx = 0; isOk && mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) { |
| bufferIndex = AMediaCodec_dequeueInputBuffer(mCodec, -1); |
| if (bufferIndex >= 0) { |
| isOk = enqueueCodecConfig(bufferIndex); |
| } else { |
| ALOGE("unexpected return value from *_dequeueInputBuffer: %d", bufferIndex); |
| return false; |
| } |
| } |
| } |
| return !hasSeenError() && isOk; |
| } |
| |
| bool CodecDecoderTest::decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit, |
| OutputManager* ref, int64_t pts, SeekMode mode) { |
| mSaveToMem = (mWindow == nullptr); |
| mOutputBuff = ref; |
| AMediaExtractor_seekTo(mExtractor, pts, mode); |
| mCodec = AMediaCodec_createCodecByName(decoder); |
| if (!mCodec) { |
| ALOGE("unable to create codec %s", decoder); |
| return false; |
| } |
| if (!configureCodec(format, false, true, false)) return false; |
| 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(); |
| } |
| |
| bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile, |
| const char* refFile, int colorFormat, float rmsError, |
| uLong checksum) { |
| bool isPass = true; |
| if (!setUpExtractor(testFile, colorFormat)) return false; |
| mSaveToMem = (mWindow == nullptr); |
| auto ref = &mRefBuff; |
| auto test = &mTestBuff; |
| const bool boolStates[]{true, false}; |
| int loopCounter = 0; |
| for (auto eosType : boolStates) { |
| if (!isPass) break; |
| for (auto isAsync : boolStates) { |
| if (!isPass) break; |
| bool validateFormat = true; |
| char log[1000]; |
| snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n", |
| decoder, testFile, (isAsync ? "async" : "sync"), |
| (eosType ? "eos with last frame" : "eos separate")); |
| mOutputBuff = loopCounter == 0 ? ref : test; |
| mOutputBuff->reset(); |
| AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_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(decoder); |
| if (!mCodec) { |
| ALOGE("unable to create codec %s", decoder); |
| return false; |
| } |
| char* name = nullptr; |
| if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) { |
| if (!name || strcmp(name, decoder) != 0) { |
| ALOGE("%s error codec-name act/got: %s/%s", log, name, decoder); |
| if (name) AMediaCodec_releaseName(mCodec, name); |
| return false; |
| } |
| } else { |
| ALOGE("AMediaCodec_getName failed unexpectedly"); |
| return false; |
| } |
| if (name) AMediaCodec_releaseName(mCodec, name); |
| if (!configureCodec(mInpDecFormat, isAsync, eosType, false)) return false; |
| AMediaFormat* decFormat = AMediaCodec_getOutputFormat(mCodec); |
| if (isFormatSimilar(mInpDecFormat, decFormat)) { |
| ALOGD("Input format is same as default for format for %s", decoder); |
| validateFormat = false; |
| } |
| AMediaFormat_delete(decFormat); |
| 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(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); |
| // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders |
| // produce multiple progressive frames?) For now, do not verify timestamps. |
| if (!mIsInterlaced) { |
| CHECK_ERR(loopCounter == 0 && !mIsAudio && |
| (!ref->isOutPtsListIdenticalToInpPtsList(false)), |
| log, "input pts list and output pts list are not identical", isPass); |
| } |
| if (validateFormat) { |
| if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged() |
| : !mSignalledOutFormatChanged) { |
| ALOGE("%s%s", log, "not received format change"); |
| isPass = false; |
| } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode |
| ? mAsyncHandle.getOutputFormat() |
| : mOutFormat)) { |
| ALOGE("%s%s", log, "configured format and output format are not similar"); |
| isPass = false; |
| } |
| } |
| if (checksum != ref->getChecksum()) { |
| ALOGE("%s%s", log, "sdk output and ndk output differ"); |
| isPass = false; |
| } |
| loopCounter++; |
| } |
| } |
| if (mSaveToMem && refFile && rmsError >= 0) { |
| setUpAudioReference(refFile); |
| float currError = ref->getRmsError(mRefData, mRefLength); |
| float errMargin = rmsError * kRmsErrorTolerance; |
| if (currError > errMargin) { |
| isPass = false; |
| ALOGE("rms error too high for file %s, ref/exp/got: %f/%f/%f", testFile, rmsError, |
| errMargin, currError); |
| } |
| } |
| return isPass; |
| } |
| |
| bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile, int colorFormat) { |
| bool isPass = true; |
| if (!setUpExtractor(testFile, colorFormat)) return false; |
| mCsdBuffers.clear(); |
| for (int i = 0;; i++) { |
| char csdName[16]; |
| void* csdBuffer; |
| size_t csdSize; |
| snprintf(csdName, sizeof(csdName), "csd-%d", i); |
| if (AMediaFormat_getBuffer(mInpDecFormat, csdName, &csdBuffer, &csdSize)) { |
| mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize)); |
| } else break; |
| } |
| const int64_t pts = 500000; |
| const SeekMode mode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; |
| auto ref = &mRefBuff; |
| if (!decodeToMemory(decoder, mInpDecFormat, INT32_MAX, ref, pts, mode)) { |
| ALOGE("decodeToMemory failed for file: %s codec: %s", testFile, decoder); |
| return false; |
| } |
| CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "", |
| "pts is not strictly increasing", isPass); |
| // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders |
| // produce multiple progressive frames?) For now, do not verify timestamps. |
| if (!mIsInterlaced) { |
| CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)), "", |
| "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), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile, |
| (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(decoder); |
| if (!mCodec) { |
| ALOGE("unable to create codec %s", decoder); |
| return false; |
| } |
| AMediaExtractor_seekTo(mExtractor, 0, mode); |
| if (!configureCodec(mInpDecFormat, isAsync, true, false)) return false; |
| AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec); |
| bool validateFormat = true; |
| if (isFormatSimilar(mInpDecFormat, defFormat)) { |
| ALOGD("Input format is same as default for format for %s", decoder); |
| validateFormat = false; |
| } |
| AMediaFormat_delete(defFormat); |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| |
| /* test flush in running state before queuing input */ |
| if (!flushCodec()) return false; |
| if (mIsCodecInAsyncMode) { |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| } |
| if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */ |
| if (!doWork(1)) return false; |
| |
| if (!flushCodec()) return false; |
| if (mIsCodecInAsyncMode) { |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| } |
| if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */ |
| AMediaExtractor_seekTo(mExtractor, 0, mode); |
| test->reset(); |
| if (!doWork(23)) return false; |
| if (!mIsInterlaced) { |
| CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "", |
| "pts is not strictly increasing", isPass); |
| } |
| |
| /* test flush in running state */ |
| if (!flushCodec()) return false; |
| if (mIsCodecInAsyncMode) { |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| } |
| mSaveToMem = (mWindow == nullptr); |
| test->reset(); |
| AMediaExtractor_seekTo(mExtractor, pts, mode); |
| if (!doWork(INT32_MAX)) return false; |
| if (!queueEOS()) return false; |
| if (!waitForAllOutputs()) return false; |
| 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((!ref->equals(test)), log, "output is flaky", isPass); |
| if (!isPass) continue; |
| |
| /* test flush in eos state */ |
| if (!flushCodec()) return false; |
| if (mIsCodecInAsyncMode) { |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| } |
| test->reset(); |
| AMediaExtractor_seekTo(mExtractor, pts, mode); |
| if (!doWork(INT32_MAX)) return false; |
| if (!queueEOS()) return false; |
| if (!waitForAllOutputs()) return false; |
| 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((!ref->equals(test)), log, "output is flaky", isPass); |
| if (validateFormat) { |
| if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged() |
| : !mSignalledOutFormatChanged) { |
| ALOGE("%s%s", log, "not received format change"); |
| isPass = false; |
| } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode |
| ? mAsyncHandle.getOutputFormat() |
| : mOutFormat)) { |
| ALOGE("%s%s", log, "configured format and output format are not similar"); |
| isPass = false; |
| } |
| } |
| mSaveToMem = false; |
| } |
| return isPass; |
| } |
| |
| bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile, int colorFormat) { |
| bool isPass = true; |
| if (!setUpExtractor(testFile, colorFormat)) return false; |
| mSaveToMem = (mWindow == nullptr); |
| auto ref = &mRefBuff; |
| auto test = &mTestBuff; |
| const bool boolStates[]{true, false}; |
| int loopCounter = 0; |
| for (auto isAsync : boolStates) { |
| if (!isPass) break; |
| char log[1000]; |
| snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile, |
| (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(decoder); |
| if (!mCodec) { |
| ALOGE("unable to create codec %s", decoder); |
| return false; |
| } |
| if (!configureCodec(mInpDecFormat, isAsync, false, false)) 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); |
| // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders |
| // produce multiple progressive frames?) For now, do not verify timestamps. |
| if (!mIsInterlaced) { |
| CHECK_ERR(loopCounter == 0 && !mIsAudio && |
| (!ref->isOutPtsListIdenticalToInpPtsList(false)), |
| log, "input pts list and output pts list are not identical", isPass); |
| } |
| loopCounter++; |
| } |
| return isPass; |
| } |
| |
| bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile, |
| int colorFormat) { |
| bool isPass = true; |
| if (!setUpExtractor(testFile, colorFormat)) return false; |
| std::vector<AMediaFormat*> formats; |
| formats.push_back(mInpDecFormat); |
| mInpDecDupFormat = AMediaFormat_new(); |
| AMediaFormat_copy(mInpDecDupFormat, mInpDecFormat); |
| formats.push_back(mInpDecDupFormat); |
| mCsdBuffers.clear(); |
| for (int i = 0;; i++) { |
| char csdName[16]; |
| void* csdBuffer; |
| size_t csdSize; |
| snprintf(csdName, sizeof(csdName), "csd-%d", i); |
| if (AMediaFormat_getBuffer(mInpDecDupFormat, csdName, &csdBuffer, &csdSize)) { |
| mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize)); |
| AMediaFormat_setBuffer(mInpDecFormat, csdName, nullptr, 0); |
| } else break; |
| } |
| |
| const bool boolStates[]{true, false}; |
| mSaveToMem = true; |
| auto ref = &mRefBuff; |
| auto test = &mTestBuff; |
| int loopCounter = 0; |
| for (int i = 0; i < formats.size() && isPass; i++) { |
| auto fmt = formats[i]; |
| for (auto eosType : boolStates) { |
| if (!isPass) break; |
| for (auto isAsync : boolStates) { |
| if (!isPass) break; |
| bool validateFormat = true; |
| char log[1000]; |
| snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n", |
| decoder, testFile, (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(decoder); |
| if (!mCodec) { |
| ALOGE("unable to create codec"); |
| return false; |
| } |
| AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC); |
| if (!configureCodec(fmt, isAsync, eosType, false)) return false; |
| AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec); |
| if (isFormatSimilar(defFormat, mInpDecFormat)) { |
| ALOGD("Input format is same as default for format for %s", decoder); |
| validateFormat = false; |
| } |
| AMediaFormat_delete(defFormat); |
| CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed"); |
| /* formats[0] doesn't contain csd-data, so queuing csd separately, formats[1] |
| * contain csd-data */ |
| if (i == 0 && !queueCodecConfig()) return false; |
| if (!doWork(INT32_MAX)) return false; |
| if (!queueEOS()) return false; |
| if (!waitForAllOutputs()) return false; |
| 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(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); |
| // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders |
| // produce multiple progressive frames?) For now, do not verify timestamps. |
| if (!mIsInterlaced) { |
| CHECK_ERR(loopCounter == 0 && !mIsAudio && |
| (!ref->isOutPtsListIdenticalToInpPtsList(false)), |
| log, "input pts list and output pts list are not identical", isPass); |
| } |
| if (validateFormat) { |
| if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged() |
| : !mSignalledOutFormatChanged) { |
| ALOGE("%s%s", log, "not received format change"); |
| isPass = false; |
| } else if (!isFormatSimilar(mInpDecFormat, |
| mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() |
| : mOutFormat)) { |
| ALOGE("%s%s", log, "configured format and output format are not similar"); |
| isPass = false; |
| } |
| } |
| loopCounter++; |
| } |
| } |
| } |
| mSaveToMem = false; |
| return isPass; |
| } |
| |
| static jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jobject surface, |
| jstring jMime, jstring jtestFile, jstring jrefFile, |
| jint jColorFormat, jfloat jrmsError, jlong jChecksum) { |
| const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr); |
| const char* cMime = env->GetStringUTFChars(jMime, nullptr); |
| const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr); |
| const char* cRefFile = env->GetStringUTFChars(jrefFile, nullptr); |
| float cRmsError = jrmsError; |
| uLong cChecksum = jChecksum; |
| ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr; |
| auto* codecDecoderTest = new CodecDecoderTest(cMime, window); |
| bool isPass = codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, jColorFormat, |
| cRmsError, cChecksum); |
| delete codecDecoderTest; |
| if (window) { |
| ANativeWindow_release(window); |
| window = nullptr; |
| } |
| env->ReleaseStringUTFChars(jDecoder, cDecoder); |
| env->ReleaseStringUTFChars(jMime, cMime); |
| env->ReleaseStringUTFChars(jtestFile, cTestFile); |
| env->ReleaseStringUTFChars(jrefFile, cRefFile); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime, |
| jstring jtestFile, jint jColorFormat) { |
| const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr); |
| const char* cMime = env->GetStringUTFChars(jMime, nullptr); |
| const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr); |
| auto* codecDecoderTest = new CodecDecoderTest(cMime, nullptr); |
| bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile, jColorFormat); |
| delete codecDecoderTest; |
| env->ReleaseStringUTFChars(jDecoder, cDecoder); |
| env->ReleaseStringUTFChars(jMime, cMime); |
| env->ReleaseStringUTFChars(jtestFile, cTestFile); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jobject surface, |
| jstring jMime, jstring jtestFile, jint jColorFormat) { |
| const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr); |
| const char* cMime = env->GetStringUTFChars(jMime, nullptr); |
| const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr); |
| ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr; |
| auto* codecDecoderTest = new CodecDecoderTest(cMime, window); |
| bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile, jColorFormat); |
| delete codecDecoderTest; |
| if (window) { |
| ANativeWindow_release(window); |
| window = nullptr; |
| } |
| env->ReleaseStringUTFChars(jDecoder, cDecoder); |
| env->ReleaseStringUTFChars(jMime, cMime); |
| env->ReleaseStringUTFChars(jtestFile, cTestFile); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder, |
| jstring jMime, jstring jtestFile, |
| jint jColorFormat) { |
| const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr); |
| const char* cMime = env->GetStringUTFChars(jMime, nullptr); |
| const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr); |
| auto codecDecoderTest = new CodecDecoderTest(cMime, nullptr); |
| bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile, jColorFormat); |
| delete codecDecoderTest; |
| env->ReleaseStringUTFChars(jDecoder, cDecoder); |
| env->ReleaseStringUTFChars(jMime, cMime); |
| env->ReleaseStringUTFChars(jtestFile, cTestFile); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestSimpleDecode", |
| "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/" |
| "lang/String;IFJ)Z", |
| (void*)nativeTestSimpleDecode}, |
| {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z", |
| (void*)nativeTestOnlyEos}, |
| {"nativeTestFlush", |
| "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;I)Z", |
| (void*)nativeTestFlush}, |
| {"nativeTestSimpleDecodeQueueCSD", |
| "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z", |
| (void*)nativeTestSimpleDecodeQueueCSD}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/CodecDecoderTest"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| int registerAndroidMediaV2CtsDecoderSurfaceTest(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestSimpleDecode", |
| "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/" |
| "lang/String;IFJ)Z", |
| (void*)nativeTestSimpleDecode}, |
| {"nativeTestFlush", |
| "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;I)Z", |
| (void*)nativeTestFlush}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/CodecDecoderSurfaceTest"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |