openh264/test/encoder/EncUT_MotionEstimate.cpp
Martin Storsjö 4f594deff9 Don't reset the random number generator within the unit tests
This makes sure we don't accidentally return the same sequence
of random numbers multiple times within one test (which would
be very non-random).

Every time srand(time()) is called, the pseudo random number
generator is initialized to the same value (as long as time()
returned the same value).

By initializing the random number generator once and for all
before starting to run the unit tests, we are sure we don't
need to reinitialize it within all the tests and all the
functions that use random numbers.

This fixes occasional errors in MotionEstimateTest.

MotionEstimateTest was designed to allow the test to occasionally
not succeed - if it didn't succeed, it tried again, up to 100 times.
However, since the YUVPixelDataGenerator function reset the random
seed to time(), every attempt actually ran with the same random
data (as long as all 100 attempts ran within 1 second) - thus if
one attempt in MotionEstimateTest failed, all 100 of them would
fail. If the utility functions don't touch the random seed,
this is not an issue.
2014-07-01 10:20:45 +03:00

397 lines
15 KiB
C++

#include <stdlib.h>
#include "gtest/gtest.h"
#include "utils/DataGenerator.h"
#include "md.h"
#include "sample.h"
#include "svc_motion_estimate.h"
#include "wels_func_ptr_def.h"
#include "cpu.h"
using namespace WelsSVCEnc;
void CopyTargetBlock (uint8_t* pSrcBlock, const int32_t kiBlockSize, SMVUnitXY sTargetMv, const int32_t kiRefPicStride,
uint8_t* pRefPic) {
uint8_t* pTargetPos = pRefPic + sTargetMv.iMvY * kiRefPicStride + sTargetMv.iMvX;
uint8_t* pSourcePos = pSrcBlock;
for (int i = 0; i < kiBlockSize; i++) {
memcpy (pSourcePos, pTargetPos, kiBlockSize * sizeof (uint8_t));
pTargetPos += kiRefPicStride;
pSourcePos += kiBlockSize;
}
}
void InitMe (const uint8_t kuiQp, const uint32_t kuiMvdTableMiddle, const uint32_t kuiMvdTableStride,
uint16_t* pMvdCostTable, SWelsME* pMe) {
MvdCostInit (pMvdCostTable, kuiMvdTableStride);
pMe->pMvdCost = &pMvdCostTable[kuiQp * kuiMvdTableStride + kuiMvdTableMiddle];
pMe->sMvp.iMvX = pMe->sMvp.iMvY = 0;
pMe->sMvBase.iMvX = pMe->sMvBase.iMvY = 0;
pMe->sMv.iMvX = pMe->sMv.iMvY = 0;
}
class MotionEstimateTest : public ::testing::Test {
public:
virtual void SetUp() {
m_pRefData = NULL;
m_pSrcBlock = NULL;
m_pMvdCostTable = NULL;
m_iWidth = 64;//size of search window
m_iHeight = 64;//size of search window
m_iMaxSearchBlock = 16;
m_uiMvdTableSize = (1 + (648 << 1));
pMa = new CMemoryAlign (0);
m_pRefData = static_cast<uint8_t*>
(pMa->WelsMalloc (m_iWidth * m_iHeight, "RefPic"));
ASSERT_TRUE (NULL != m_pRefData);
m_pSrcBlock = static_cast<uint8_t*>
(pMa->WelsMalloc (m_iMaxSearchBlock * m_iMaxSearchBlock, "SrcBlock"));
ASSERT_TRUE (NULL != m_pSrcBlock);
m_pMvdCostTable = new uint16_t[52 * m_uiMvdTableSize];
ASSERT_TRUE (NULL != m_pMvdCostTable);
}
void DoLineTest (PLineFullSearchFunc func, bool horizontal);
virtual void TearDown() {
delete [] m_pMvdCostTable;
pMa->WelsFree (m_pRefData, "RefPic");
pMa->WelsFree (m_pSrcBlock, "SrcBlock");
delete pMa;
}
public:
uint8_t* m_pRefData;
uint8_t* m_pSrcBlock;
uint32_t m_uiMvdTableSize;
uint16_t* m_pMvdCostTable;
int32_t m_iWidth;
int32_t m_iHeight;
int32_t m_iMaxSearchBlock;
CMemoryAlign* pMa;
};
TEST_F (MotionEstimateTest, TestDiamondSearch) {
#define TEST_POS (5)
const int32_t kiPositionToCheck[TEST_POS][2] = {{0, 0}, {0, 1}, {1, 0}, {0, -1}, { -1, 0}};
const int32_t kiMaxBlock16Sad = 72000;//a rough number
SWelsFuncPtrList sFuncList;
SWelsME sMe;
SSlice sSlice;
const uint8_t kuiQp = rand() % 52;
InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);
SMVUnitXY sTargetMv;
WelsInitSampleSadFunc (&sFuncList, 0); //test c functions
uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);
bool bDataGeneratorSucceed = false;
bool bFoundMatch = false;
int32_t i, iTryTimes;
for (i = 0; i < TEST_POS; i++) {
sTargetMv.iMvX = kiPositionToCheck[i][0];
sTargetMv.iMvY = kiPositionToCheck[i][1];
iTryTimes = 100;
bDataGeneratorSucceed = false;
bFoundMatch = false;
while (!bFoundMatch && (iTryTimes--) > 0) {
if (!YUVPixelDataGenerator (m_pRefData, m_iWidth, m_iHeight, m_iWidth))
continue;
bDataGeneratorSucceed = true;
CopyTargetBlock (m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter);
//clean the sMe status
sMe.uiBlockSize = rand() % 5;
sMe.pEncMb = m_pSrcBlock;
sMe.pRefMb = pRefPicCenter;
sMe.sMv.iMvX = sMe.sMv.iMvY = 0;
sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;
WelsDiamondSearch (&sFuncList, &sMe, &sSlice, m_iMaxSearchBlock, m_iWidth);
//the last selection may be affected by MVDcost, that is when (0,0) will be better
//when comparing (1,1) and (1,0), due to the difference between MVD cost, it is possible that (1,0) is selected while the best match is (1,1)
bFoundMatch = ((sMe.sMv.iMvX == (sTargetMv.iMvX)) || (sMe.sMv.iMvX == 0)) && ((sMe.sMv.iMvY == (sTargetMv.iMvY))
|| (sMe.sMv.iMvY == 0));
}
if (bDataGeneratorSucceed) {
//if DataGenerator never succeed, there is no meaning to check iTryTimes
ASSERT_TRUE (iTryTimes > 0);
//it is possible that ref at differnt position is identical, but that should be under a low probability
}
}
}
void MotionEstimateTest::DoLineTest (PLineFullSearchFunc func, bool vertical) {
const int32_t kiMaxBlock16Sad = 72000;//a rough number
SWelsFuncPtrList sFuncList;
SWelsME sMe;
const uint8_t kuiQp = rand() % 52;
InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);
SMVUnitXY sTargetMv;
WelsInitSampleSadFunc (&sFuncList, 0); //test c functions
WelsInitMeFunc (&sFuncList, WelsCPUFeatureDetect (NULL), 1);
uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);
sMe.iCurMeBlockPixX = (m_iWidth / 2);
sMe.iCurMeBlockPixY = (m_iHeight / 2);
bool bDataGeneratorSucceed = false;
bool bFoundMatch = false;
int32_t iTryTimes = 100;
if (vertical) {
sTargetMv.iMvX = 0;
sTargetMv.iMvY = -sMe.iCurMeBlockPixY + INTPEL_NEEDED_MARGIN + rand() % (m_iHeight - 16 - 2 * INTPEL_NEEDED_MARGIN);
} else {
sTargetMv.iMvX = -sMe.iCurMeBlockPixX + INTPEL_NEEDED_MARGIN + rand() % (m_iWidth - 16 - 2 * INTPEL_NEEDED_MARGIN);
sTargetMv.iMvY = 0;
}
bDataGeneratorSucceed = false;
bFoundMatch = false;
while (!bFoundMatch && (iTryTimes--) > 0) {
if (!YUVPixelDataGenerator (m_pRefData, m_iWidth, m_iHeight, m_iWidth))
continue;
bDataGeneratorSucceed = true;
CopyTargetBlock (m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter);
//clean the sMe status
sMe.uiBlockSize = rand() % 5;
sMe.pEncMb = m_pSrcBlock;
sMe.pRefMb = pRefPicCenter;
sMe.pColoRefMb = pRefPicCenter;
sMe.sMv.iMvX = sMe.sMv.iMvY = 0;
sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;
const int32_t iCurMeBlockPixX = sMe.iCurMeBlockPixX;
const int32_t iCurMeBlockQpelPixX = ((iCurMeBlockPixX) << 2);
const int32_t iCurMeBlockPixY = sMe.iCurMeBlockPixY;
const int32_t iCurMeBlockQpelPixY = ((iCurMeBlockPixY) << 2);
uint16_t* pMvdCostX = sMe.pMvdCost - iCurMeBlockQpelPixX - sMe.sMvp.iMvX; //do the offset here
uint16_t* pMvdCostY = sMe.pMvdCost - iCurMeBlockQpelPixY - sMe.sMvp.iMvY;
uint16_t* pMvdCost = vertical ? pMvdCostY : pMvdCostX;
int iSize = vertical ? m_iHeight : m_iWidth;
int iFixedMvd = vertical ? pMvdCostX[ iCurMeBlockQpelPixX ] : pMvdCostY[ iCurMeBlockQpelPixY ];
func (&sFuncList, &sMe,
pMvdCost, iFixedMvd,
m_iMaxSearchBlock, m_iWidth,
INTPEL_NEEDED_MARGIN,
iSize - INTPEL_NEEDED_MARGIN - 16, vertical);
//the last selection may be affected by MVDcost, that is when smaller MvY will be better
if (vertical) {
bFoundMatch = (sMe.sMv.iMvX == 0
&& (sMe.sMv.iMvY == sTargetMv.iMvY || abs (sMe.sMv.iMvY) < abs (sTargetMv.iMvY)));
} else {
bFoundMatch = (sMe.sMv.iMvY == 0
&& (sMe.sMv.iMvX == sTargetMv.iMvX || abs (sMe.sMv.iMvX) < abs (sTargetMv.iMvX)));
}
//printf("DoLineTest Target: %d,%d\n", sTargetMv.iMvX, sTargetMv.iMvY);
}
if (bDataGeneratorSucceed) {
//if DataGenerator never succeed, there is no meaning to check iTryTimes
ASSERT_TRUE (iTryTimes > 0);
//it is possible that ref at differnt position is identical, but that should be under a low probability
}
}
TEST_F (MotionEstimateTest, TestVerticalSearch) {
DoLineTest (LineFullSearch_c, true);
}
TEST_F (MotionEstimateTest, TestHorizontalSearch) {
DoLineTest (LineFullSearch_c, false);
}
#ifdef X86_ASM
TEST_F (MotionEstimateTest, TestVerticalSearch_SSE41) {
int32_t iTmp = 1;
uint32_t uiCPUFlags = WelsCPUFeatureDetect (&iTmp);
if ((uiCPUFlags & WELS_CPU_SSE41) == 0) return ;
DoLineTest (VerticalFullSearchUsingSSE41, true);
}
TEST_F (MotionEstimateTest, TestHorizontalSearch_SSE41) {
int32_t iTmp = 1;
uint32_t uiCPUFlags = WelsCPUFeatureDetect (&iTmp);
if ((uiCPUFlags & WELS_CPU_SSE41) == 0) return ;
DoLineTest (HorizontalFullSearchUsingSSE41, false);
}
#endif
class FeatureMotionEstimateTest : public ::testing::Test {
public:
virtual void SetUp() {
m_pRefData = NULL;
m_pSrcBlock = NULL;
m_pMvdCostTable = NULL;
m_iWidth = 64;//size of search window
m_iHeight = 64;//size of search window
m_iMaxSearchBlock = 8;
m_uiMvdTableSize = (1 + (648 << 1));
m_pMa = new CMemoryAlign (16);
ASSERT_TRUE (NULL != m_pMa);
m_pRefData = (uint8_t*)m_pMa->WelsMalloc (m_iWidth * m_iHeight * sizeof (uint8_t), "m_pRefData");
ASSERT_TRUE (NULL != m_pRefData);
m_pSrcBlock = (uint8_t*)m_pMa->WelsMalloc (m_iMaxSearchBlock * m_iMaxSearchBlock * sizeof (uint8_t), "m_pSrcBlock");
ASSERT_TRUE (NULL != m_pSrcBlock);
m_pMvdCostTable = (uint16_t*)m_pMa->WelsMalloc (52 * m_uiMvdTableSize * sizeof (uint16_t), "m_pMvdCostTable");
ASSERT_TRUE (NULL != m_pMvdCostTable);
m_pFeatureSearchPreparation = (SFeatureSearchPreparation*)m_pMa->WelsMalloc (sizeof (SFeatureSearchPreparation),
"m_pFeatureSearchPreparation");
ASSERT_TRUE (NULL != m_pFeatureSearchPreparation);
m_pScreenBlockFeatureStorage = (SScreenBlockFeatureStorage*)m_pMa->WelsMalloc (sizeof (SScreenBlockFeatureStorage),
"m_pScreenBlockFeatureStorage");
ASSERT_TRUE (NULL != m_pScreenBlockFeatureStorage);
}
virtual void TearDown() {
if (m_pMa) {
if (m_pRefData) {
m_pMa->WelsFree (m_pRefData, "m_pRefData");
m_pRefData = NULL;
}
if (m_pSrcBlock) {
m_pMa->WelsFree (m_pSrcBlock, "m_pSrcBlock");
m_pSrcBlock = NULL;
}
if (m_pMvdCostTable) {
m_pMa->WelsFree (m_pMvdCostTable, "m_pMvdCostTable");
m_pMvdCostTable = NULL;
}
if (m_pFeatureSearchPreparation) {
ReleaseFeatureSearchPreparation (m_pMa, m_pFeatureSearchPreparation->pFeatureOfBlock);
m_pMa->WelsFree (m_pFeatureSearchPreparation, "m_pFeatureSearchPreparation");
m_pFeatureSearchPreparation = NULL;
}
if (m_pScreenBlockFeatureStorage) {
ReleaseScreenBlockFeatureStorage (m_pMa, m_pScreenBlockFeatureStorage);
m_pMa->WelsFree (m_pScreenBlockFeatureStorage, "m_pScreenBlockFeatureStorage");
m_pScreenBlockFeatureStorage = NULL;
}
delete m_pMa;
m_pMa = NULL;
}
}
void InitRefPicForMeTest (SPicture* pRefPic) {
pRefPic->pData[0] = m_pRefData;
pRefPic->iLineSize[0] = m_iWidth;
pRefPic->iFrameAverageQp = rand() % 52;
pRefPic->iWidthInPixel = m_iWidth;
pRefPic->iHeightInPixel = m_iHeight;
}
public:
CMemoryAlign* m_pMa;
SFeatureSearchPreparation* m_pFeatureSearchPreparation;
SScreenBlockFeatureStorage* m_pScreenBlockFeatureStorage;
uint8_t* m_pRefData;
uint8_t* m_pSrcBlock;
uint16_t* m_pMvdCostTable;
uint32_t m_uiMvdTableSize;
int32_t m_iWidth;
int32_t m_iHeight;
int32_t m_iMaxSearchBlock;
};
TEST_F (FeatureMotionEstimateTest, TestFeatureSearch) {
const int32_t kiMaxBlock16Sad = 72000;//a rough number
SWelsFuncPtrList sFuncList;
WelsInitSampleSadFunc (&sFuncList, 0); //test c functions
WelsInitMeFunc (&sFuncList, 0, true);
SWelsME sMe;
const uint8_t kuiQp = rand() % 52;
InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);
sMe.iCurMeBlockPixX = (m_iWidth / 2);
sMe.iCurMeBlockPixY = (m_iHeight / 2);
uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);
SPicture sRef;
InitRefPicForMeTest (&sRef);
SSlice sSlice;
const int32_t kiSupposedPaddingLength = 16;
SetMvWithinIntegerMvRange (m_iWidth / 16 - kiSupposedPaddingLength, m_iHeight / 16 - kiSupposedPaddingLength,
m_iWidth / 2 / 16, m_iHeight / 2 / 16, 508,
& (sSlice.sMvStartMin), & (sSlice.sMvStartMax));
int32_t iReturn;
const int32_t kiNeedFeatureStorage = ME_DIA_CROSS_FME;
iReturn = RequestFeatureSearchPreparation (m_pMa, m_iWidth, m_iHeight, kiNeedFeatureStorage,
m_pFeatureSearchPreparation);
ASSERT_TRUE (ENC_RETURN_SUCCESS == iReturn);
iReturn = RequestScreenBlockFeatureStorage (m_pMa, m_iWidth, m_iHeight, kiNeedFeatureStorage,
m_pScreenBlockFeatureStorage);
ASSERT_TRUE (ENC_RETURN_SUCCESS == iReturn);
SMVUnitXY sTargetMv;
for (int i = sSlice.sMvStartMin.iMvX; i <= sSlice.sMvStartMax.iMvX; i++) {
for (int j = sSlice.sMvStartMin.iMvY; j <= sSlice.sMvStartMax.iMvY; j++) {
if (i == 0 || j == 0) continue; //exclude x=0 or y=0 since that will be skipped by FME
bool bDataGeneratorSucceed = false;
bool bFoundMatch = false;
if (!YUVPixelDataGenerator (m_pRefData, m_iWidth, m_iHeight, m_iWidth))
continue;
bDataGeneratorSucceed = true;
sTargetMv.iMvX = i;
sTargetMv.iMvY = j;
CopyTargetBlock (m_pSrcBlock, m_iMaxSearchBlock, sTargetMv, m_iWidth, pRefPicCenter);
//clean sMe status
sMe.uiBlockSize = BLOCK_8x8;
sMe.pEncMb = m_pSrcBlock;
sMe.pRefMb = pRefPicCenter;
sMe.pColoRefMb = pRefPicCenter;
sMe.sMv.iMvX = sMe.sMv.iMvY = 0;
sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;
//begin FME process
PerformFMEPreprocess (&sFuncList, &sRef, m_pFeatureSearchPreparation->pFeatureOfBlock,
m_pScreenBlockFeatureStorage);
m_pScreenBlockFeatureStorage->uiSadCostThreshold[BLOCK_8x8] = UINT_MAX;//to avoid early skip
uint32_t uiMaxSearchPoint = INT_MAX;
SFeatureSearchIn sFeatureSearchIn = {0};
if (SetFeatureSearchIn (&sFuncList, sMe, &sSlice, m_pScreenBlockFeatureStorage,
m_iMaxSearchBlock, m_iWidth,
&sFeatureSearchIn)) {
MotionEstimateFeatureFullSearch (sFeatureSearchIn, uiMaxSearchPoint, &sMe);
}
bool bMvMatch = sMe.sMv.iMvX == sTargetMv.iMvX && sMe.sMv.iMvY == sTargetMv.iMvY;
bool bFeatureMatch =
(* (m_pScreenBlockFeatureStorage->pFeatureOfBlockPointer + (m_iHeight / 2 + sTargetMv.iMvY) * (m_iWidth - 8) +
(m_iWidth / 2 + sTargetMv.iMvX))
== * (m_pScreenBlockFeatureStorage->pFeatureOfBlockPointer + (m_iHeight / 2 + sMe.sMv.iMvY) * (m_iWidth - 8) +
(m_iWidth / 2 + sMe.sMv.iMvX)))
&& ((sMe.pMvdCost[sMe.sMv.iMvY << 2] + sMe.pMvdCost[sMe.sMv.iMvX << 2]) <= (sMe.pMvdCost[sTargetMv.iMvY << 2] +
sMe.pMvdCost[sTargetMv.iMvX << 2]));
//the last selection may be affected by MVDcost, that is when smaller Mv will be better
bFoundMatch = bMvMatch || bFeatureMatch;
if (bDataGeneratorSucceed) {
//if DataGenerator never succeed, there is no meaning to check iTryTimes
if (!bFoundMatch) {
printf ("TestFeatureSearch Target: %d,%d, Result: %d,%d\n", sTargetMv.iMvX, sTargetMv.iMvY, sMe.sMv.iMvX, sMe.sMv.iMvY);
}
EXPECT_TRUE (bFoundMatch);
}
}
}
}