411 lines
17 KiB
C++
411 lines
17 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 "AVCUtilsUnitTest"
|
|
#include <utils/Log.h>
|
|
|
|
#include <fstream>
|
|
#include <memory>
|
|
|
|
#include "media/stagefright/foundation/ABitReader.h"
|
|
#include "media/stagefright/foundation/avc_utils.h"
|
|
|
|
#include "AVCUtilsTestEnvironment.h"
|
|
|
|
constexpr size_t kSmallBufferSize = 2;
|
|
constexpr uint8_t kSPSmask = 0x1f;
|
|
constexpr uint8_t kSPSStartCode = 0x07;
|
|
constexpr uint8_t kConfigVersion = 0x01;
|
|
|
|
using namespace android;
|
|
|
|
static AVCUtilsTestEnvironment *gEnv = nullptr;
|
|
|
|
class MpegAudioUnitTest
|
|
: public ::testing::TestWithParam<
|
|
tuple</*audioHeader*/ uint32_t, /*frameSize*/ int32_t, /*sampleRate*/ int32_t,
|
|
/*numChannels*/ int32_t, /*bitRate*/ int32_t, /*numSamples*/ int32_t>> {};
|
|
|
|
class VOLDimensionTest
|
|
: public ::testing::TestWithParam<
|
|
tuple</*fileName*/ string, /*volWidth*/ int32_t, /*volHeight*/ int32_t>> {};
|
|
|
|
class AVCUtils {
|
|
public:
|
|
bool SetUpAVCUtils(string fileName, string infoFileName) {
|
|
mInputFile = gEnv->getRes() + fileName;
|
|
mInputFileStream.open(mInputFile, ifstream::in);
|
|
if (!mInputFileStream.is_open()) return false;
|
|
|
|
mInfoFile = gEnv->getRes() + infoFileName;
|
|
mInfoFileStream.open(mInfoFile, ifstream::in);
|
|
if (!mInputFileStream.is_open()) return false;
|
|
return true;
|
|
}
|
|
|
|
~AVCUtils() {
|
|
if (mInputFileStream.is_open()) mInputFileStream.close();
|
|
if (mInfoFileStream.is_open()) mInfoFileStream.close();
|
|
}
|
|
|
|
string mInputFile;
|
|
string mInfoFile;
|
|
|
|
ifstream mInputFileStream;
|
|
ifstream mInfoFileStream;
|
|
};
|
|
|
|
class AVCDimensionTest
|
|
: public AVCUtils,
|
|
public ::testing::TestWithParam<
|
|
tuple</*fileName*/ string, /*infoFileName*/ string,
|
|
/*avcWidth*/ size_t, /*avcHeight*/ size_t, /*numberOfNALUnits*/ int32_t>> {
|
|
public:
|
|
virtual void SetUp() override {
|
|
tuple<string, string, size_t, size_t, size_t> params = GetParam();
|
|
string fileName = get<0>(params);
|
|
string infoFileName = get<1>(params);
|
|
AVCUtils::SetUpAVCUtils(fileName, infoFileName);
|
|
|
|
mFrameWidth = get<2>(params);
|
|
mFrameHeight = get<3>(params);
|
|
mNalUnitsExpected = get<4>(params);
|
|
}
|
|
|
|
size_t mFrameWidth;
|
|
size_t mFrameHeight;
|
|
int32_t mNalUnitsExpected;
|
|
};
|
|
|
|
class AvccBoxTest : public AVCDimensionTest {
|
|
public:
|
|
virtual void SetUp() override { AVCDimensionTest::SetUp(); }
|
|
};
|
|
|
|
class AVCFrameTest
|
|
: public AVCUtils,
|
|
public ::testing::TestWithParam<pair</*fileName*/ string, /*infoFileName*/ string>> {
|
|
public:
|
|
virtual void SetUp() override {
|
|
string fileName = GetParam().first;
|
|
string infoFileName = GetParam().second;
|
|
AVCUtils::SetUpAVCUtils(fileName, infoFileName);
|
|
}
|
|
};
|
|
|
|
TEST_P(MpegAudioUnitTest, AudioProfileTest) {
|
|
tuple<uint32_t, size_t, int, int, int, int> params = GetParam();
|
|
uint32_t header = get<0>(params);
|
|
|
|
size_t audioFrameSize = get<1>(params);
|
|
int audioSampleRate = get<2>(params);
|
|
int audioNumChannels = get<3>(params);
|
|
int audioBitRate = get<4>(params);
|
|
int audioNumSamples = get<5>(params);
|
|
|
|
size_t frameSize = 0;
|
|
int sampleRate = 0;
|
|
int numChannels = 0;
|
|
int bitRate = 0;
|
|
int numSamples = 0;
|
|
|
|
bool status = GetMPEGAudioFrameSize(header, &frameSize, &sampleRate, &numChannels, &bitRate,
|
|
&numSamples);
|
|
ASSERT_TRUE(status) << "Failed to get Audio properties";
|
|
|
|
ASSERT_EQ(frameSize, audioFrameSize) << "Wrong frame size found";
|
|
|
|
ASSERT_EQ(sampleRate, audioSampleRate) << "Wrong sample rate found";
|
|
|
|
ASSERT_EQ(numChannels, audioNumChannels) << "Wrong number of channels found";
|
|
|
|
ASSERT_EQ(bitRate, audioBitRate) << "Wrong bit rate found";
|
|
|
|
ASSERT_EQ(numSamples, audioNumSamples) << "Wrong number of samples found";
|
|
}
|
|
|
|
TEST_P(VOLDimensionTest, DimensionTest) {
|
|
tuple<string, int32_t, int32_t> params = GetParam();
|
|
string inputFile = gEnv->getRes() + get<0>(params);
|
|
ifstream inputFileStream;
|
|
inputFileStream.open(inputFile, ifstream::in);
|
|
ASSERT_TRUE(inputFileStream.is_open()) << "Failed to open: " << inputFile;
|
|
|
|
struct stat buf;
|
|
int8_t err = stat(inputFile.c_str(), &buf);
|
|
ASSERT_EQ(err, 0) << "Failed to get information for file: " << inputFile;
|
|
|
|
size_t fileSize = buf.st_size;
|
|
ASSERT_NE(fileSize, 0) << "Invalid file size found";
|
|
|
|
std::unique_ptr<uint8_t[]> volBuffer(new uint8_t[fileSize]);
|
|
ASSERT_NE(volBuffer, nullptr) << "Failed to allocate VOL buffer of size: " << fileSize;
|
|
|
|
inputFileStream.read((char *)(volBuffer.get()), fileSize);
|
|
ASSERT_EQ(inputFileStream.gcount(), fileSize)
|
|
<< "Failed to read complete file, bytes read: " << inputFileStream.gcount();
|
|
|
|
int32_t width = get<1>(params);
|
|
int32_t height = get<2>(params);
|
|
int32_t volWidth = -1;
|
|
int32_t volHeight = -1;
|
|
|
|
bool status = ExtractDimensionsFromVOLHeader(volBuffer.get(), fileSize, &volWidth, &volHeight);
|
|
ASSERT_TRUE(status)
|
|
<< "Failed to get VOL dimensions from function: ExtractDimensionsFromVOLHeader()";
|
|
|
|
ASSERT_EQ(volWidth, width) << "Expected width: " << width << "Found: " << volWidth;
|
|
|
|
ASSERT_EQ(volHeight, height) << "Expected height: " << height << "Found: " << volHeight;
|
|
}
|
|
|
|
TEST_P(AVCDimensionTest, DimensionTest) {
|
|
int32_t numNalUnits = 0;
|
|
int32_t avcWidth = -1;
|
|
int32_t avcHeight = -1;
|
|
string line;
|
|
string type;
|
|
size_t chunkLength;
|
|
while (getline(mInfoFileStream, line)) {
|
|
istringstream stringLine(line);
|
|
stringLine >> type >> chunkLength;
|
|
ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
|
|
|
|
std::unique_ptr<uint8_t[]> dataArray(new uint8_t[chunkLength]);
|
|
const uint8_t *data = dataArray.get();
|
|
ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
|
|
|
|
const uint8_t *nalStart;
|
|
size_t nalSize;
|
|
|
|
mInputFileStream.read((char *)data, chunkLength);
|
|
ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
|
|
<< "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
|
|
|
|
size_t smallBufferSize = kSmallBufferSize;
|
|
uint8_t sanityDataBuffer[smallBufferSize];
|
|
const uint8_t *sanityData = sanityDataBuffer;
|
|
memcpy((void *)sanityData, (void *)data, smallBufferSize);
|
|
|
|
// sanityData could be changed, but sanityDataPtr is not and can be cleaned up.
|
|
status_t result = getNextNALUnit(&sanityData, &smallBufferSize, &nalStart, &nalSize, true);
|
|
ASSERT_EQ(result, -EAGAIN) << "Invalid result found when wrong NAL unit passed";
|
|
|
|
while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
|
|
numNalUnits++;
|
|
// Check if it's an SPS
|
|
if ((nalStart[0] & kSPSmask) != kSPSStartCode) continue;
|
|
ASSERT_TRUE(nalSize > 0) << "NAL unit size must be greater than 0";
|
|
|
|
sp<ABuffer> spsBuffer = new ABuffer(nalSize);
|
|
ASSERT_NE(spsBuffer, nullptr) << "ABuffer returned null for size: " << nalSize;
|
|
|
|
memcpy(spsBuffer->data(), nalStart, nalSize);
|
|
FindAVCDimensions(spsBuffer, &avcWidth, &avcHeight);
|
|
spsBuffer.clear();
|
|
ASSERT_EQ(avcWidth, mFrameWidth)
|
|
<< "Expected width: " << mFrameWidth << "Found: " << avcWidth;
|
|
|
|
ASSERT_EQ(avcHeight, mFrameHeight)
|
|
<< "Expected height: " << mFrameHeight << "Found: " << avcHeight;
|
|
}
|
|
}
|
|
if (mNalUnitsExpected < 0) {
|
|
ASSERT_GT(numNalUnits, 0) << "Failed to find an NAL Unit";
|
|
} else {
|
|
ASSERT_EQ(numNalUnits, mNalUnitsExpected)
|
|
<< "Expected number of NAL units: " << mNalUnitsExpected
|
|
<< " found: " << numNalUnits;
|
|
}
|
|
}
|
|
|
|
TEST_P(AvccBoxTest, AvccBoxValidationTest) {
|
|
int32_t avcWidth = -1;
|
|
int32_t avcHeight = -1;
|
|
int32_t accessUnitLength = 0;
|
|
int32_t profile = -1;
|
|
int32_t level = -1;
|
|
string line;
|
|
string type;
|
|
size_t chunkLength;
|
|
while (getline(mInfoFileStream, line)) {
|
|
istringstream stringLine(line);
|
|
stringLine >> type >> chunkLength;
|
|
|
|
if (type.compare("SPS") && type.compare("PPS")) continue;
|
|
ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
|
|
|
|
accessUnitLength += chunkLength;
|
|
|
|
if (!type.compare("SPS")) {
|
|
std::unique_ptr<uint8_t[]> dataArray(new uint8_t[chunkLength]);
|
|
const uint8_t *data = dataArray.get();
|
|
ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
|
|
|
|
const uint8_t *nalStart;
|
|
size_t nalSize;
|
|
|
|
mInputFileStream.read((char *)data, (uint32_t)chunkLength);
|
|
ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
|
|
<< "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
|
|
|
|
while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
|
|
// Check if it's an SPS
|
|
ASSERT_TRUE(nalSize > 0 && (nalStart[0] & kSPSmask) == kSPSStartCode)
|
|
<< "Failed to get SPS";
|
|
|
|
ASSERT_GE(nalSize, 4) << "SPS size must be greater than or equal to 4";
|
|
|
|
profile = nalStart[1];
|
|
level = nalStart[3];
|
|
}
|
|
}
|
|
}
|
|
std::unique_ptr<uint8_t[]> accessUnitData(new uint8_t[accessUnitLength]);
|
|
ASSERT_NE(accessUnitData, nullptr) << "Failed to create a buffer of size: " << accessUnitLength;
|
|
|
|
mInputFileStream.seekg(0, ios::beg);
|
|
mInputFileStream.read((char *)accessUnitData.get(), accessUnitLength);
|
|
ASSERT_EQ(mInputFileStream.gcount(), accessUnitLength)
|
|
<< "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
|
|
|
|
sp<ABuffer> accessUnit = new ABuffer(accessUnitLength);
|
|
ASSERT_NE(accessUnit, nullptr)
|
|
<< "Failed to create an android data buffer of size: " << accessUnitLength;
|
|
|
|
memcpy(accessUnit->data(), accessUnitData.get(), accessUnitLength);
|
|
sp<ABuffer> csdDataBuffer = MakeAVCCodecSpecificData(accessUnit, &avcWidth, &avcHeight);
|
|
ASSERT_NE(csdDataBuffer, nullptr) << "No data returned from MakeAVCCodecSpecificData()";
|
|
|
|
ASSERT_EQ(avcWidth, mFrameWidth) << "Expected width: " << mFrameWidth << "Found: " << avcWidth;
|
|
|
|
ASSERT_EQ(avcHeight, mFrameHeight)
|
|
<< "Expected height: " << mFrameHeight << "Found: " << avcHeight;
|
|
|
|
uint8_t *csdData = csdDataBuffer->data();
|
|
ASSERT_EQ(*csdData, kConfigVersion) << "Invalid configuration version";
|
|
|
|
ASSERT_GE(csdDataBuffer->size(), 4) << "CSD data size must be greater than or equal to 4";
|
|
|
|
ASSERT_EQ(*(csdData + 1), profile)
|
|
<< "Expected AVC profile: " << profile << " found: " << *(csdData + 1);
|
|
|
|
ASSERT_EQ(*(csdData + 3), level)
|
|
<< "Expected AVC level: " << level << " found: " << *(csdData + 3);
|
|
csdDataBuffer.clear();
|
|
accessUnit.clear();
|
|
}
|
|
|
|
TEST_P(AVCFrameTest, FrameTest) {
|
|
string line;
|
|
string type;
|
|
size_t chunkLength;
|
|
int32_t frameLayerID;
|
|
while (getline(mInfoFileStream, line)) {
|
|
uint32_t layerID = 0;
|
|
istringstream stringLine(line);
|
|
stringLine >> type >> chunkLength >> frameLayerID;
|
|
ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
|
|
|
|
std::unique_ptr<char[]> data(new char[chunkLength]);
|
|
ASSERT_NE(data, nullptr) << "Failed to allocation data buffer of size: " << chunkLength;
|
|
|
|
mInputFileStream.read(data.get(), chunkLength);
|
|
ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
|
|
<< "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
|
|
|
|
if (!type.compare("IDR")) {
|
|
bool isIDR = IsIDR((uint8_t *)data.get(), chunkLength);
|
|
ASSERT_TRUE(isIDR);
|
|
|
|
layerID = FindAVCLayerId((uint8_t *)data.get(), chunkLength);
|
|
ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
|
|
} else if (!type.compare("P") || !type.compare("B")) {
|
|
sp<ABuffer> accessUnit = new ABuffer(chunkLength);
|
|
ASSERT_NE(accessUnit, nullptr) << "Unable to create access Unit";
|
|
|
|
memcpy(accessUnit->data(), data.get(), chunkLength);
|
|
bool isReferenceFrame = IsAVCReferenceFrame(accessUnit);
|
|
ASSERT_TRUE(isReferenceFrame);
|
|
|
|
accessUnit.clear();
|
|
layerID = FindAVCLayerId((uint8_t *)data.get(), chunkLength);
|
|
ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, MpegAudioUnitTest,
|
|
::testing::Values(make_tuple(0xFFFB9204, 418, 44100, 2, 128, 1152),
|
|
make_tuple(0xFFFB7604, 289, 48000, 2, 96, 1152),
|
|
make_tuple(0xFFFE5604, 164, 48000, 2, 160, 384)));
|
|
|
|
// Info File contains the type and length for each chunk/frame
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
AVCUtilsTestAll, AVCDimensionTest,
|
|
::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
|
|
"crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
|
|
make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
|
|
"crowd_640x360p24f300_1000kbps_bp.info", 640, 360, 11),
|
|
make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
|
|
"crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
|
|
make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
|
|
"crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14),
|
|
make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
|
|
"crowd_3840x2160p60f300_68000kbps_bp.info", 3840, 2160, 14)));
|
|
|
|
// Info File contains the type and length for each chunk/frame
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
AVCUtilsTestAll, AvccBoxTest,
|
|
::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
|
|
"crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
|
|
make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
|
|
"crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
|
|
make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
|
|
"crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14)));
|
|
|
|
// Info File contains the type and length for each chunk/frame
|
|
INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, VOLDimensionTest,
|
|
::testing::Values(make_tuple("volData_720_480", 720, 480),
|
|
make_tuple("volData_1280_720", 1280, 720),
|
|
make_tuple("volData_1920_1080", 1920, 1080)));
|
|
|
|
// Info File contains the type, length and layer ID for each chunk/frame
|
|
INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, AVCFrameTest,
|
|
::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
|
|
"crowd_8x8p50f32_200kbps_bp.info"),
|
|
make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
|
|
"crowd_640x360p24f300_1000kbps_bp.info"),
|
|
make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
|
|
"crowd_1280x720p30f300_5000kbps_bp.info"),
|
|
make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
|
|
"crowd_1920x1080p50f300_12000kbps_bp.info"),
|
|
make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
|
|
"crowd_3840x2160p60f300_68000kbps_bp.info")));
|
|
|
|
int main(int argc, char **argv) {
|
|
gEnv = new AVCUtilsTestEnvironment();
|
|
::testing::AddGlobalTestEnvironment(gEnv);
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
int status = gEnv->initFromOptions(argc, argv);
|
|
if (status == 0) {
|
|
status = RUN_ALL_TESTS();
|
|
ALOGV("Test result = %d\n", status);
|
|
}
|
|
return status;
|
|
}
|