Motion Estimation Library (часть третья, интерфейсы)

Давно я не писал заметок с кодом. Связано это с моим неумением объяснять «на бумаге» (да и чего уж там, вообще) и приличными объемами кода. При этом, увеличение объемов кода, как ни странно, ведет к усложнению процесса пояснения. Но выкручиваться как-то надо.


В последний раз мы остановились на демонстрации использования нашей библиотеки оценки движения. Для тех кто не читал помнит, я напомню, что весь процесс был разбит на несколько частей: инициализация, подготовка входных данных, подготовка выходных структур и запуск процесса. Сегодня мы обсудим некоторые из частей.


Пара центов про архитектуру библиотеки. Картинка ниже иллюстрирует основную концепцию. Дело в том, что приложение, использующее нашу библиотеку, ничего не знает о деталях ее реализации. Оно лишь оперирует примитивами: инициализация, уничтожение, запрос результата. Это позволяет нам скрыть детали внутри библиотеки. Библиотека реализует специальный объект HANDLE, с которым приложение общается через интерфейсные примитивы. HANDLE – ядро библиотеки и отвечает за ресурсы, алгоритмы, реализует схему параллелизации и синхронизации. Мощная такая штука.





Главная особенность в том, что HANDL'ов может быть несколько. Каждый под свою железку. Например, библиотека может предоставлять объект-ядро для софтовой реализации алгоритмов и для железо-специфичной Nvidia и ATI. Понятно, что для каждого объекта механизмы инициализации, синхронизации и управления памятью будут особенными.





После небольшого экскурса в архитектуру рассмотрим основные интерфейсные функции доступные приложению. Благо их немного.


Начинаем с инициализации


Вспомним, как выглядела инициализации с точки зрения приложения.




// the main hardware me structure

hw_me_handle_t hwhandle = new _HW_ME_HANDLE;


// parameters of hardware me

HW_ME_PARAMETERS hwPar;


// auxiliary variables

Ipp32u sts, numMb, imageStep, imageSize;


// initialize hardware me parameters

hwPar.codecStandard = HW_ME_H264;

hwPar.meType = HW_ME_WHOLE_PIXEL;

hwPar.algType = HW_ME_RESIDUAL;

hwPar.strictWithinFrame = ippFalse;

hwPar.minSubblockSize = HW_ME_DIV_16X16;

hwPar.imageSize.width = width;

hwPar.imageSize.height = height;


pMeInfo.pMbInfo = NULL;


// number of reference frames for motion estimation

hwPar.maxNumRef = 2;


// initialize me handle by me parameters 

InitializeHWME(&hwPar, &hwhandle);

Довольно просто, не так ли? Так вот внутренность интерфейсной функции не намного сложнее.



IppStatus InitializeHWME(const HW_ME_PARAMETERS *pHWMEParams, hw_me_handle_t *pHandle)

{

    hw_me_handle_t handle;


    // release the existing object

    if (*pHandle)

    {

        ReleaseHWME(*pHandle);

        *pHandle = NULL;

    }


    // create new handle object

    handle = new _HW_ME_HANDLE();


    if (NULL == handle)

    {

        return ippStsMemAllocErr;

    }


    // initialize handle

    handle->Init(pHWMEParams);


    switch (pHWMEParams->meType)

    {

    case HW_ME_QUARTER_PIXEL:


        switch (pHWMEParams->codecStandard)

        {

            case HW_ME_H264:

                handle->pEst = new H264EstimatorQuarter;

                break;

            case HW_ME_VC1:

                break;

            case HW_ME_MPEG2:

                break;

            default: // MPEG4 standard

                break;

        }

        break;


    case HW_ME_HALF_PIXEL:


        switch (pHWMEParams->codecStandard)

        {

            case HW_ME_H264:

                handle->pEst = new H264EstimatorHalf;

                break;

            case HW_ME_VC1:

                break;

            case HW_ME_MPEG2:

                break;

            default: // MPEG4 standard

                break;

        }

        break;


    default:


        handle->pEst = new CEstimatorWhole;

        break;

    }


    handle->pEst->Initialize(pHWMEParams);


    *pHandle = handle;


    return ippStsNoErr;


} // IppStatus InitializeHWME(HW_ME_PARAMETERS *pHWMEParams, hw_me_handle_t *pHandle)


Функция принимает на вход параметры оценки движения и указатель на объект-ядро, осуществляет проверку на повторную инициализацию, создает определенный объект оценки движения (тут приведено только для h264 стандарта) и возвращает готовый к употреблению объект-ядро. Вуаля.


Загрузка данных в объект-ядро


Выделенную память следует заполнить данными, а данные прикрепить к объекту оценки движения. Приложение делает:




// YUV reader

CYUVReader reader;


// initialize YUV reader

sts = reader.Init(filename, width, height);

CHECK_STS(sts);


// read the first frame

sts = reader.ReadYUVData();

CHECK_STS(sts);


// copy data from yuv reader to source image memory

IppiSize iSize;

iSize.height = height;

iSize.width = width;


CopyPlane(reader.m_pYUVData[0], width, hwSrcImage.pPlanes[0], imageStep, iSize);


iSize.width = iSize.width / 2;

iSize.height = iSize.height / 2;


CopyPlane(reader.m_pYUVData[1], width / 2, hwSrcImage.pPlanes[1], imageStep / 2, iSize);

CopyPlane(reader.m_pYUVData[2], width / 2, hwSrcImage.pPlanes[2], imageStep / 2, iSize);


sts = CopyHWMESource(hwhandle, &hwSrcImage);

CHECK_STS(sts);


// start preparing of reference frames

for (Ipp32u i = 0; i < hwPar.maxNumRef; i += 1)

{

    // read the next frame

    reader.ReadYUVData();


    // copy data from YUV reader to reference image memory


    iSize.height = height;

    iSize.width = width;


    CopyPlane(reader.m_pYUVData[0], width, hwRefImage.pPlanes[0], imageStep, iSize);


    iSize.width = iSize.width / 2;

    iSize.height = iSize.height / 2;


    CopyPlane(reader.m_pYUVData[1], width / 2, hwRefImage.pPlanes[1], imageStep / 2, iSize);

    CopyPlane(reader.m_pYUVData[2], width / 2, hwRefImage.pPlanes[2], imageStep / 2, iSize);


    // add reference image to the handle

    sts = CopyHWMEReference(hwhandle, i, &hwRefImage);

    CHECK_STS(sts);

}




А внутри все просто - копирование данных во внутренние структуры объекта-ядра.





static

IppStatus CopyPlane(const Ipp8u* pSrc, Ipp32s srcStep, Ipp8u* pDst, Ipp32s dstStep, IppiSize roiSize)

{

    Ipp32s y;


    for (y = 0; y < roiSize.height; y++)

    {

        memcpy(pDst, pSrc, roiSize.width);

        pSrc += srcStep;

        pDst += dstStep;

    }


    return ippStsNoErr;


} // IppStatus CopyPlane(const Ipp8u* pSrc, Ipp32s srcStep, Ipp8u* pDst, Ipp32s dstStep, IppiSize roiSize)


IppStatus CopyHWMESource(hw_me_handle_t handle,const HW_ME_IMAGE *pImage)

{


    if (NULL == handle || NULL == pImage)

    {

        return ippStsNullPtrErr;

    }


    HW_ME_IMAGE* sImage = handle->pSource;


    sImage->imageStructure = pImage->imageStructure;


    // copy 3 planes by line

    CopyPlane(pImage->pPlanes[0], pImage->imageStep, sImage->pPlanes[0], sImage->imageStep, iSize);


    iSize.width = iSize.width / 2;

    iSize.height = iSize.height / 2;


    CopyPlane(pImage->pPlanes[1], pImage->imageStep / 2, sImage->pPlanes[1], sImage->imageStep / 2, iSize);

    CopyPlane(pImage->pPlanes[2], pImage->imageStep / 2, sImage->pPlanes[2], sImage->imageStep / 2, iSize);


    return ippStsNoErr;


} // IppStatus CopyHWMESource(hw_me_handle_t handle, const HW_ME_IMAGE *pImage)


IppStatus CopyHWMEReference(hw_me_handle_t handle, const Ipp32u refIdx, const HW_ME_IMAGE *pImage)

{


    if (NULL == handle || NULL == pImage)

    {

        return ippStsNullPtrErr;

    }


    if (handle->params.maxNumRef pReferences + refIdx;


    IppiSize iSize = handle->params.imageSize;


    sImage->imageStructure = pImage->imageStructure;


    // copy 3 planes by line

    CopyPlane(pImage->pPlanes[0], pImage->imageStep, sImage->pPlanes[0], sImage->imageStep, iSize);


    iSize.width = iSize.width / 2;

    iSize.height = iSize.height / 2;


    CopyPlane(pImage->pPlanes[1], pImage->imageStep / 2, sImage->pPlanes[1], sImage->imageStep / 2, iSize);

    CopyPlane(pImage->pPlanes[2], pImage->imageStep / 2, sImage->pPlanes[2], sImage->imageStep / 2, iSize);


    handle->m_numRef++;


    return ippStsNoErr;


} // IppStatus CopyHWMEReference(hw_me_handle_t handle, Ipp32u refIdx, const HW_ME_IMAGE *pImage)



Нажимаем красную кнопку



Запуск процесса оценки движения мы осуществляли следующим образом:





// results storage of me

HW_ME_INFO pMeInfo;


pMeInfo.numFrameRefs[1] = hwPar.maxNumRef;


// calculate the number of macro blocks

numMb = (width * height) / (16 * 16);


// allocate mb info 

pMeInfo.pMbInfo = new HW_ME_MB_INFO [numMb];

pMeInfo.numFrameRefs[1] = hwPar.maxNumRef;


sts = DoHWME(hwhandle, &pMeInfo);

CHECK_STS(sts);




Соответствующая интерфейсная функция обращается к объекту эстиматора.





IppStatus DoHWME(hw_me_handle_t handle, HW_ME_INFO *pMeInfo, void **ppEvent)

{

    IppStatus sts;

    Ipp32u width;

    Ipp32u height;


    HW_ME_IMAGE *pRefImages, *pSrcImage;


    // touch unreferenced parameters

    ppEvent = ppEvent;


    // check error(s)

    if (NULL == handle || NULL == pImage || NULL == pMeInfo)

    {

        return ippStsNullPtrErr;

    }


    // set initial parameters

    width =  handle->params.imageSize.width;

    height =  handle->params.imageSize.height;


    pRefImages = handle->pReferences;

    pSrcImage = handle->pSource;


    sts = handle->pEst->EstimateByFrames(pRefImages, NULL, pSrcImage, pMeInfo);


    return sts;


} // IppStatus DoHWME(hw_me_handle_t handle, HW_ME_INFO *pMeInfo, void **ppEvent)



Все!


Собственно, да. На этом покончено с интерфейсными функциями. Они по сути являются лишь обертками вокруг объекта-ядра. Что дальше? А дальше мы запустим руки во внутренности объекта-ядра и эстиматора. Надеюсь будет продолжение.


For more complete information about compiler optimizations, see our Optimization Notice.