| /* |
| * 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 "Mpeg4H263DecoderTest" |
| #include <utils/Log.h> |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <utils/String8.h> |
| #include <fstream> |
| |
| #include <media/stagefright/foundation/AUtils.h> |
| #include "mp4dec_api.h" |
| |
| #include "Mpeg4H263DecoderTestEnvironment.h" |
| |
| using namespace android; |
| |
| #define OUTPUT_FILE_NAME "/data/local/tmp/Output.yuv" |
| #define CODEC_CONFIG_FLAG 32 |
| #define SYNC_FRAME 1 |
| #define MPEG4_MAX_WIDTH 1920 |
| #define MPEG4_MAX_HEIGHT 1080 |
| #define H263_MAX_WIDTH 352 |
| #define H263_MAX_HEIGHT 288 |
| |
| constexpr uint32_t kNumOutputBuffers = 2; |
| |
| struct FrameInfo { |
| int32_t bytesCount; |
| uint32_t flags; |
| int64_t timestamp; |
| }; |
| |
| struct tagvideoDecControls; |
| |
| static Mpeg4H263DecoderTestEnvironment *gEnv = nullptr; |
| |
| class Mpeg4H263DecoderTest : public ::testing::TestWithParam<tuple<string, string, bool>> { |
| public: |
| Mpeg4H263DecoderTest() |
| : mDecHandle(nullptr), |
| mInputBuffer(nullptr), |
| mInitialized(false), |
| mFramesConfigured(false), |
| mNumSamplesOutput(0), |
| mWidth(352), |
| mHeight(288) { |
| memset(mOutputBuffer, 0x0, sizeof(mOutputBuffer)); |
| } |
| |
| ~Mpeg4H263DecoderTest() { |
| if (mEleStream.is_open()) mEleStream.close(); |
| if (mDecHandle) { |
| delete mDecHandle; |
| mDecHandle = nullptr; |
| } |
| if (mInputBuffer) { |
| free(mInputBuffer); |
| mInputBuffer = nullptr; |
| } |
| freeOutputBuffer(); |
| } |
| |
| status_t initDecoder(); |
| void allocOutputBuffer(size_t outputBufferSize); |
| void dumpOutput(ofstream &ostrm); |
| void freeOutputBuffer(); |
| void processMpeg4H263Decoder(vector<FrameInfo> Info, int32_t offset, int32_t range, |
| ifstream &mEleStream, ofstream &ostrm, MP4DecodingMode inputMode); |
| void deInitDecoder(); |
| |
| ifstream mEleStream; |
| tagvideoDecControls *mDecHandle; |
| char *mInputBuffer; |
| uint8_t *mOutputBuffer[kNumOutputBuffers]; |
| bool mInitialized; |
| bool mFramesConfigured; |
| uint32_t mNumSamplesOutput; |
| uint32_t mWidth; |
| uint32_t mHeight; |
| }; |
| |
| status_t Mpeg4H263DecoderTest::initDecoder() { |
| if (!mDecHandle) { |
| mDecHandle = new tagvideoDecControls; |
| } |
| if (!mDecHandle) { |
| return NO_MEMORY; |
| } |
| memset(mDecHandle, 0, sizeof(tagvideoDecControls)); |
| |
| return OK; |
| } |
| |
| void Mpeg4H263DecoderTest::allocOutputBuffer(size_t outputBufferSize) { |
| for (int32_t i = 0; i < kNumOutputBuffers; ++i) { |
| if (!mOutputBuffer[i]) { |
| mOutputBuffer[i] = (uint8_t *)malloc(outputBufferSize); |
| ASSERT_NE(mOutputBuffer[i], nullptr) << "Output buffer allocation failed"; |
| } |
| } |
| } |
| |
| void Mpeg4H263DecoderTest::dumpOutput(ofstream &ostrm) { |
| uint8_t *src = mOutputBuffer[mNumSamplesOutput & 1]; |
| size_t vStride = align(mHeight, 16); |
| size_t srcYStride = align(mWidth, 16); |
| size_t srcUVStride = srcYStride / 2; |
| uint8_t *srcStart = src; |
| |
| /* Y buffer */ |
| for (size_t i = 0; i < mHeight; ++i) { |
| ostrm.write(reinterpret_cast<char *>(src), mWidth); |
| src += srcYStride; |
| } |
| /* U buffer */ |
| src = srcStart + vStride * srcYStride; |
| for (size_t i = 0; i < mHeight / 2; ++i) { |
| ostrm.write(reinterpret_cast<char *>(src), mWidth / 2); |
| src += srcUVStride; |
| } |
| /* V buffer */ |
| src = srcStart + vStride * srcYStride * 5 / 4; |
| for (size_t i = 0; i < mHeight / 2; ++i) { |
| ostrm.write(reinterpret_cast<char *>(src), mWidth / 2); |
| src += srcUVStride; |
| } |
| } |
| |
| void Mpeg4H263DecoderTest::freeOutputBuffer() { |
| for (int32_t i = 0; i < kNumOutputBuffers; ++i) { |
| if (mOutputBuffer[i]) { |
| free(mOutputBuffer[i]); |
| mOutputBuffer[i] = nullptr; |
| } |
| } |
| } |
| |
| void Mpeg4H263DecoderTest::processMpeg4H263Decoder(vector<FrameInfo> Info, int32_t offset, |
| int32_t range, ifstream &mEleStream, |
| ofstream &ostrm, MP4DecodingMode inputMode) { |
| size_t maxWidth = (inputMode == MPEG4_MODE) ? MPEG4_MAX_WIDTH : H263_MAX_WIDTH; |
| size_t maxHeight = (inputMode == MPEG4_MODE) ? MPEG4_MAX_HEIGHT : H263_MAX_HEIGHT; |
| size_t outputBufferSize = align(maxWidth, 16) * align(maxHeight, 16) * 3 / 2; |
| uint32_t frameIndex = offset; |
| bool status = true; |
| ASSERT_GE(range, 0) << "Invalid range"; |
| ASSERT_TRUE(offset >= 0 && offset <= Info.size() - 1) << "Invalid offset"; |
| ASSERT_LE(range + offset, Info.size()) << "range+offset can't be greater than the no of frames"; |
| |
| while (1) { |
| if (frameIndex == Info.size() || frameIndex == (offset + range)) break; |
| |
| int32_t bytesCount = Info[frameIndex].bytesCount; |
| ASSERT_GT(bytesCount, 0) << "Size for the memory allocation is negative"; |
| mInputBuffer = (char *)malloc(bytesCount); |
| ASSERT_NE(mInputBuffer, nullptr) << "Insufficient memory to read frame"; |
| mEleStream.read(mInputBuffer, bytesCount); |
| ASSERT_EQ(mEleStream.gcount(), bytesCount) << "mEleStream.gcount() != bytesCount"; |
| static const uint8_t volInfo[] = {0x00, 0x00, 0x01, 0xB0}; |
| bool volHeader = memcmp(mInputBuffer, volInfo, 4) == 0; |
| if (volHeader) { |
| PVCleanUpVideoDecoder(mDecHandle); |
| mInitialized = false; |
| } |
| |
| if (!mInitialized) { |
| uint8_t *volData[1]{}; |
| int32_t volSize = 0; |
| |
| uint32_t flags = Info[frameIndex].flags; |
| bool codecConfig = flags == CODEC_CONFIG_FLAG; |
| if (codecConfig || volHeader) { |
| volData[0] = reinterpret_cast<uint8_t *>(mInputBuffer); |
| volSize = bytesCount; |
| } |
| |
| status = PVInitVideoDecoder(mDecHandle, volData, &volSize, 1, maxWidth, maxHeight, |
| inputMode); |
| ASSERT_TRUE(status) << "PVInitVideoDecoder failed. Unsupported content"; |
| |
| mInitialized = true; |
| MP4DecodingMode actualMode = PVGetDecBitstreamMode(mDecHandle); |
| ASSERT_EQ(inputMode, actualMode) |
| << "Decoded mode not same as actual mode of the decoder"; |
| |
| PVSetPostProcType(mDecHandle, 0); |
| |
| int32_t dispWidth, dispHeight; |
| PVGetVideoDimensions(mDecHandle, &dispWidth, &dispHeight); |
| |
| int32_t bufWidth, bufHeight; |
| PVGetBufferDimensions(mDecHandle, &bufWidth, &bufHeight); |
| |
| ASSERT_LE(dispWidth, bufWidth) << "Display width is greater than buffer width"; |
| ASSERT_LE(dispHeight, bufHeight) << "Display height is greater than buffer height"; |
| |
| if (dispWidth != mWidth || dispHeight != mHeight) { |
| mWidth = dispWidth; |
| mHeight = dispHeight; |
| freeOutputBuffer(); |
| if (inputMode == H263_MODE) { |
| PVCleanUpVideoDecoder(mDecHandle); |
| |
| uint8_t *volData[1]{}; |
| int32_t volSize = 0; |
| |
| status = PVInitVideoDecoder(mDecHandle, volData, &volSize, 1, maxWidth, |
| maxHeight, H263_MODE); |
| ASSERT_TRUE(status) << "PVInitVideoDecoder failed for H263"; |
| } |
| mFramesConfigured = false; |
| } |
| |
| if (codecConfig) { |
| frameIndex++; |
| continue; |
| } |
| } |
| |
| uint32_t yFrameSize = sizeof(uint8) * mDecHandle->size; |
| ASSERT_GE(outputBufferSize, yFrameSize * 3 / 2) |
| << "Too small output buffer: " << outputBufferSize << " bytes"; |
| ASSERT_NO_FATAL_FAILURE(allocOutputBuffer(outputBufferSize)); |
| |
| if (!mFramesConfigured) { |
| PVSetReferenceYUV(mDecHandle, mOutputBuffer[1]); |
| mFramesConfigured = true; |
| } |
| |
| // Need to check if header contains new info, e.g., width/height, etc. |
| VopHeaderInfo headerInfo; |
| uint32_t useExtTimestamp = 1; |
| int32_t inputSize = (Info)[frameIndex].bytesCount; |
| uint32_t timestamp = frameIndex; |
| |
| uint8_t *bitstreamTmp = reinterpret_cast<uint8_t *>(mInputBuffer); |
| |
| status = PVDecodeVopHeader(mDecHandle, &bitstreamTmp, ×tamp, &inputSize, &headerInfo, |
| &useExtTimestamp, mOutputBuffer[mNumSamplesOutput & 1]); |
| ASSERT_EQ(status, PV_TRUE) << "failed to decode vop header"; |
| |
| // H263 doesn't have VOL header, the frame size information is in short header, i.e. the |
| // decoder may detect size change after PVDecodeVopHeader. |
| int32_t dispWidth, dispHeight; |
| PVGetVideoDimensions(mDecHandle, &dispWidth, &dispHeight); |
| |
| int32_t bufWidth, bufHeight; |
| PVGetBufferDimensions(mDecHandle, &bufWidth, &bufHeight); |
| |
| ASSERT_LE(dispWidth, bufWidth) << "Display width is greater than buffer width"; |
| ASSERT_LE(dispHeight, bufHeight) << "Display height is greater than buffer height"; |
| if (dispWidth != mWidth || dispHeight != mHeight) { |
| mWidth = dispWidth; |
| mHeight = dispHeight; |
| } |
| |
| status = PVDecodeVopBody(mDecHandle, &inputSize); |
| ASSERT_EQ(status, PV_TRUE) << "failed to decode video frame No = %d" << frameIndex; |
| |
| dumpOutput(ostrm); |
| |
| ++mNumSamplesOutput; |
| ++frameIndex; |
| } |
| freeOutputBuffer(); |
| } |
| |
| void Mpeg4H263DecoderTest::deInitDecoder() { |
| if (mInitialized) { |
| if (mDecHandle) { |
| PVCleanUpVideoDecoder(mDecHandle); |
| delete mDecHandle; |
| mDecHandle = nullptr; |
| } |
| mInitialized = false; |
| } |
| freeOutputBuffer(); |
| } |
| |
| void getInfo(string infoFileName, vector<FrameInfo> &Info) { |
| ifstream eleInfo; |
| eleInfo.open(infoFileName); |
| ASSERT_EQ(eleInfo.is_open(), true) << "Failed to open " << infoFileName; |
| int32_t bytesCount = 0; |
| uint32_t flags = 0; |
| uint32_t timestamp = 0; |
| while (1) { |
| if (!(eleInfo >> bytesCount)) { |
| break; |
| } |
| eleInfo >> flags; |
| eleInfo >> timestamp; |
| Info.push_back({bytesCount, flags, timestamp}); |
| } |
| if (eleInfo.is_open()) eleInfo.close(); |
| } |
| |
| TEST_P(Mpeg4H263DecoderTest, DecodeTest) { |
| tuple<string /* InputFileName */, string /* InfoFileName */, bool /* mode */> params = |
| GetParam(); |
| |
| string inputFileName = gEnv->getRes() + get<0>(params); |
| mEleStream.open(inputFileName, ifstream::binary); |
| ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << get<0>(params); |
| |
| string infoFileName = gEnv->getRes() + get<1>(params); |
| vector<FrameInfo> Info; |
| ASSERT_NO_FATAL_FAILURE(getInfo(infoFileName, Info)); |
| ASSERT_NE(Info.empty(), true) << "Invalid Info file"; |
| |
| ofstream ostrm; |
| ostrm.open(OUTPUT_FILE_NAME, std::ofstream::binary); |
| ASSERT_EQ(ostrm.is_open(), true) << "Failed to open output stream for " << get<0>(params); |
| |
| status_t err = initDecoder(); |
| ASSERT_EQ(err, OK) << "initDecoder: failed to create decoder " << err; |
| |
| bool isMpeg4 = get<2>(params); |
| MP4DecodingMode inputMode = isMpeg4 ? MPEG4_MODE : H263_MODE; |
| ASSERT_NO_FATAL_FAILURE( |
| processMpeg4H263Decoder(Info, 0, Info.size(), mEleStream, ostrm, inputMode)); |
| deInitDecoder(); |
| ostrm.close(); |
| Info.clear(); |
| } |
| |
| TEST_P(Mpeg4H263DecoderTest, FlushTest) { |
| tuple<string /* InputFileName */, string /* InfoFileName */, bool /* mode */> params = |
| GetParam(); |
| |
| string inputFileName = gEnv->getRes() + get<0>(params); |
| mEleStream.open(inputFileName, ifstream::binary); |
| ASSERT_EQ(mEleStream.is_open(), true) << "Failed to open " << get<0>(params); |
| |
| string infoFileName = gEnv->getRes() + get<1>(params); |
| vector<FrameInfo> Info; |
| ASSERT_NO_FATAL_FAILURE(getInfo(infoFileName, Info)); |
| ASSERT_NE(Info.empty(), true) << "Invalid Info file"; |
| |
| ofstream ostrm; |
| ostrm.open(OUTPUT_FILE_NAME, std::ofstream::binary); |
| ASSERT_EQ(ostrm.is_open(), true) << "Failed to open output stream for " << get<0>(params); |
| |
| status_t err = initDecoder(); |
| ASSERT_EQ(err, OK) << "initDecoder: failed to create decoder " << err; |
| |
| bool isMpeg4 = get<2>(params); |
| MP4DecodingMode inputMode = isMpeg4 ? MPEG4_MODE : H263_MODE; |
| // Number of frames to be decoded before flush |
| int32_t numFrames = Info.size() / 3; |
| ASSERT_NO_FATAL_FAILURE( |
| processMpeg4H263Decoder(Info, 0, numFrames, mEleStream, ostrm, inputMode)); |
| |
| if (mInitialized) { |
| int32_t status = PVResetVideoDecoder(mDecHandle); |
| ASSERT_EQ(status, PV_TRUE); |
| } |
| |
| // Seek to next key frame and start decoding till the end |
| int32_t index = numFrames; |
| bool keyFrame = false; |
| uint32_t flags = 0; |
| while (index < (int32_t)Info.size()) { |
| if (Info[index].flags) flags = 1u << (Info[index].flags - 1); |
| if ((flags & SYNC_FRAME) == SYNC_FRAME) { |
| keyFrame = true; |
| break; |
| } |
| flags = 0; |
| mEleStream.ignore(Info[index].bytesCount); |
| index++; |
| } |
| ALOGV("Index= %d", index); |
| if (keyFrame) { |
| mNumSamplesOutput = 0; |
| ASSERT_NO_FATAL_FAILURE(processMpeg4H263Decoder(Info, index, (int32_t)Info.size() - index, |
| mEleStream, ostrm, inputMode)); |
| } |
| deInitDecoder(); |
| ostrm.close(); |
| Info.clear(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Mpeg4H263DecoderTestAll, Mpeg4H263DecoderTest, |
| ::testing::Values(make_tuple("swirl_128x96_h263.h263", "swirl_128x96_h263.info", false), |
| make_tuple("swirl_176x144_h263.h263", "swirl_176x144_h263.info", false), |
| make_tuple("swirl_352x288_h263.h263", "swirl_352x288_h263.info", false), |
| make_tuple("bbb_352x288_h263.h263", "bbb_352x288_h263.info", false), |
| make_tuple("bbb_352x288_mpeg4.m4v", "bbb_352x288_mpeg4.info", true), |
| make_tuple("swirl_128x128_mpeg4.m4v", "swirl_128x128_mpeg4.info", true), |
| make_tuple("swirl_130x132_mpeg4.m4v", "swirl_130x132_mpeg4.info", true), |
| make_tuple("swirl_132x130_mpeg4.m4v", "swirl_132x130_mpeg4.info", true), |
| make_tuple("swirl_136x144_mpeg4.m4v", "swirl_136x144_mpeg4.info", true), |
| make_tuple("swirl_144x136_mpeg4.m4v", "swirl_144x136_mpeg4.info", true))); |
| |
| int main(int argc, char **argv) { |
| gEnv = new Mpeg4H263DecoderTestEnvironment(); |
| ::testing::AddGlobalTestEnvironment(gEnv); |
| ::testing::InitGoogleTest(&argc, argv); |
| int status = gEnv->initFromOptions(argc, argv); |
| if (status == 0) { |
| status = RUN_ALL_TESTS(); |
| ALOGD("Decoder Test Result = %d\n", status); |
| } |
| return status; |
| } |