427 lines
15 KiB
C++
427 lines
15 KiB
C++
/*
|
|
* 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("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;
|
|
}
|