/* * 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 #include #include #include #include #include #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> { 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 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(src), mWidth); src += srcYStride; } /* U buffer */ src = srcStart + vStride * srcYStride; for (size_t i = 0; i < mHeight / 2; ++i) { ostrm.write(reinterpret_cast(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(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 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(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(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 &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 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 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 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 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("qtype0_mpeg4.m4v", "qtype0_mpeg4.info", true), make_tuple("qtype1_mpeg4.m4v", "qtype1_mpeg4.info", true), make_tuple("qtype1_qmatrix_mpeg4.m4v", "qtype1_qmatrix_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; }