Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 14b66fae authored by Snehal N Bhamare's avatar Snehal N Bhamare Committed by Snehal Bhamare
Browse files

MediaTesting: Add Mpeg4 H263 Decoder Test

Test: Mpeg4H263DecoderTest -P /data/local/tmp/

Bug: 146043033

Change-Id: I1f30d6e433bf35f0d56128a99ca9c15d58a46ded
parent 9d33c634
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

cc_test {
    name: "Mpeg4H263DecoderTest",
    gtest: true,

    srcs: [
        "Mpeg4H263DecoderTest.cpp",
    ],

    shared_libs: [
        "liblog",
    ],

    static_libs: [
        "libstagefright_m4vh263dec",
        "libstagefright_foundation",
    ],

    cflags: [
        "-DOSCL_IMPORT_REF=",
        "-Werror",
        "-Wall",
    ],

    sanitize: {
        misc_undefined: [
            "unsigned-integer-overflow",
            "signed-integer-overflow",
        ],
        cfi: true,
    },
}
+423 −0
Original line number Diff line number Diff line
/*
 * 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, &timestamp, &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;
}
+73 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#ifndef __MPEG4_H263_DECODER_TEST_ENVIRONMENT_H__
#define __MPEG4_H263_DECODER_TEST_ENVIRONMENT_H__

#include <gtest/gtest.h>

#include <getopt.h>

using namespace std;

class Mpeg4H263DecoderTestEnvironment : public ::testing::Environment {
  public:
    Mpeg4H263DecoderTestEnvironment() : res("/data/local/tmp/") {}

    // Parses the command line arguments
    int initFromOptions(int argc, char **argv);

    void setRes(const char *_res) { res = _res; }

    const string getRes() const { return res; }

  private:
    string res;
};

int Mpeg4H263DecoderTestEnvironment::initFromOptions(int argc, char **argv) {
    static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};

    while (true) {
        int index = 0;
        int c = getopt_long(argc, argv, "P:", options, &index);
        if (c == -1) {
            break;
        }

        switch (c) {
            case 'P': {
                setRes(optarg);
                break;
            }
            default:
                break;
        }
    }

    if (optind < argc) {
        fprintf(stderr,
                "unrecognized option: %s\n\n"
                "usage: %s <gtest options> <test options>\n\n"
                "test options are:\n\n"
                "-P, --path: Resource files directory location\n",
                argv[optind ?: 1], argv[0]);
        return 2;
    }
    return 0;
}

#endif  // __MPEG4_H263_DECODER_TEST_ENVIRONMENT_H__
+34 −0
Original line number Diff line number Diff line
## Media Testing ##
---
#### Mpeg4H263Decoder :
The Mpeg4H263Decoder Test Suite validates the Mpeg4 and H263 decoder available in libstagefright.

Run the following steps to build the test suite:
```
m Mpeg4H263DecoderTest
```

The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/

The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/

To test 64-bit binary push binaries from nativetest64.
```
adb push ${OUT}/data/nativetest64/Mpeg4H263DecoderTest/Mpeg4H263DecoderTest /data/local/tmp/
```

To test 32-bit binary push binaries from nativetest.
```
adb push ${OUT}/data/nativetest/Mpeg4H263DecoderTest/Mpeg4H263DecoderTest /data/local/tmp/
```

The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/13cM4tAaVFrmr-zGFqaAzFBbKs75pnm9b).
Download Mpeg4H263Decoder folder and push all the files in this folder to /data/local/tmp/ on the device for testing.
```
adb push Mpeg4H263Decoder/. /data/local/tmp/
```

usage: Mpeg4H263DecoderTest -P \<path_to_folder\>
```
adb shell /data/local/tmp/Mpeg4H263DecoderTest -P /data/local/tmp/
```