VMR技术(Video Mixer Render)

来源:互联网 发布:蔡夫人知乎 编辑:程序博客网 时间:2024/06/02 23:21

Video Mixer Render

流畅预览视频的同时捕捉图像帧,转换颜色空间RGB到YUV420/I420,供视频图像处理算法处理,或直接保存RGB/YUV格式文件。视频显示根据有无窗口可分为有窗口模式和无窗口模式。Video Renderer只支持窗口模式,VMR支持有窗口(Window)和无窗口(Windowless)两种模式,默认支持有窗口模式。在无窗口模式中,可以把视频和应用程序主界面的窗口捆绑。VMR在清晰、流畅显示视频的同时,可以灵活地捕获图像帧,这也是选择VMR技术的一个重要原因,因为我们想直接对图像数据进行处理。


主要类分析如下:

CVMR_Capture完成采集视频、预览视频和捕获图像帧的任务

根据GraphEdit的滤波器内容和链表的操作,编程实现使用VMR技术显示、捕捉图像。

 CVMR_Capture定义在头文件VMR_Capture.h中。

enum PLAYER_STATE {INIT,RUNNING,PAUSED,STOPPED};

class CVMR_Capture

{

public:

 CVMR_Capture();                                  //类构造器

 virtual ~CVMR_Capture();                        //类析构器

 int EnumDevices(HWND hList);               //枚举设备

 HRESULT Init(int iDeviceID,HWND hWnd,int iWidth,int iHeight); 
 //根据设备索引号初始化

 DWORD GetFrame(BYTE ** pFrame);        //获取捕获的图像帧

 BOOL Pause();                              //暂停预览、捕获

 DWORD GrabFrame();                         //截获图像

 void CloseInterfaces(void);                    //关闭接口,释放资源

 void SaveGraph(CString wFileName);           //保存滤波器链表

protected:

 IGraphBuilder          *m_pGB;             //滤波器链表管理器

 IMediaControl          *m_pMC;             //媒体控制接口

 IMediaEventEx          *m_pME;             //媒体事件接口

 IVMRWindowlessControl9 *m_pWC;             //VMR-9接口

 IPin                   *m_pCamOutPin;      //视频采集滤波器引脚

 IBaseFilter                *m_pDF;             //视频采集滤波器

 PLAYER_STATE            m_psCurrent;

 int        m_nWidth;                           //图像帧宽度

 int        m_nHeight;                          //图像帧高度

 BYTE   *m_pFrame;                          //捕获的图像帧数据指针

 long   m_nFramelen;                           //捕获的图像帧数据大小

 bool BindFilter(int deviceId, IBaseFilter **pFilter);      //设备与滤波器捆绑

 HRESULT InitializeWindowlessVMR(HWND hWnd);    //初始化无窗口模式的VMR

 HRESULT InitVideoWindow(HWND hWnd,int width, int height);  //初始化视频窗口

 void StopCapture();

 void DeleteMediaType(AM_MEDIA_TYPE *pmt);                        //删除媒体类型

 bool Convert24Image(BYTE *p32Img,BYTE *p24Img,DWORD dwSize32); //颜色空间转换

private:

};

CVMR_Capture类封装了使用VMR技术的成员函数和成员变量,包括类的构造器、析构器,滤波器链表管理器接口、媒体控制接口、媒体事件接口,图像帧的宽度、高度及操作VMR的保护成员函数等。

 CVMR_Capture有关初始化、析构等,类实现VMR_Capture.cpp文件中。

//释放资源的宏

#define RELEASE_POINTER (x) { if (x) x->Release(); x = NULL; }

 

/**************************华丽分割线*******************************/

/*类构造器*/

CVMR_Capture::CVMR_Capture()

{

 CoInitialize(NULL);                          //初始化COM

//接口指针清空

 m_pGB = NULL;      m_pMC = NULL;       

m_pME = NULL;       m_pWC = NULL;

 m_pDF =NULL;             m_pCamOutPin =NULL; 

m_pFrame=NULL;        m_nFramelen=0;

 m_psCurrent=STOPPED;

}

/*类析构器*/

CVMR_Capture::~CVMR_Capture()

{

 CloseInterfaces();                           //清空指针、释放资源

 CoUninitialize( );                       //卸载COM

}

/*释放所有资源,断开链接*/

void CVMR_Capture::CloseInterfaces(void)

{

     HRESULT hr;

      //停止媒体回放

     if(m_pMC) hr = m_pMC->Stop();

     m_psCurrent = STOPPED;

     //释放并清零接口指针

 if(m_pCamOutPin)

      m_pCamOutPin->Disconnect();       //断开引脚的链接

RELEASE_POINTER(m_pCamOutPin);          //视频采集滤波器引脚清空

RELEASE_POINTER(m_pMC);                     //媒体控制接口清空

RELEASE_POINTER(m_pGB);                     //滤波器链表管理器接口清空

RELEASE_POINTER(m_pWC);                 //VMR-9接口清空

RELEASE_POINTER(m_pDF);                 //视频采集滤波器接口清空

 //释放分配的内存

 if(m_pFrame!=NULL)

       delete []m_pFrame;                   //释放图像空间缓存

}

上述程序完成资源释放、指针清空等工作,在退出应用程序时调用此函数。

 枚举本地系统的采集设备。

/* 枚举本地系统的采集设备*/

int  CVMR_Capture::EnumDevices(HWND hList)

{

 if (!hList) return  -1;

 int id = 0;

        //枚举所有视频采集设备

       ICreateDevEnum *pCreateDevEnum;

        //生成设备枚举器 

 HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,

                         CLSCTX_INPROC_SERVER,

                         IID_ICreateDevEnum, (void**)&pCreateDevEnum);

    if (hr != NOERROR) return -1;

       IEnumMoniker *pEm;

       //创建视频类枚举器

    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,       &pEm, 0);

    if (hr != NOERROR) return -1;

    pEm->Reset();                                            //复位设备

    ULONG cFetched;

    IMoniker *pM;

    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)   //根据索引获取设备

    {

     IPropertyBag *pBag;

     //获取属性集

     hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);

     if(SUCCEEDED(hr))

     {

           VARIANT var;

           var.vt = VT_BSTR;                              //保存二进制数据

           hr = pBag->Read(L"FriendlyName", &var, NULL);
               //FriendlyName获取设备信息

           if (hr == NOERROR)

           {

                   id++;

                   //把该设备的信息添加到组合框

                   (long)SendMessage(hList, CB_ADDSTRING, 0,(LPARAM)var. 
                       bstrVal);

                   SysFreeString(var.bstrVal);          //释放资源

            }

            pBag->Release();                                //释放资源

     }

     pM->Release();                                     //释放资源

    }

 return id;

}

上述程序实现枚举系统所有的视频采集设备,然后把FriendlyName信息添加到组合框中,这里的设备枚举与使用经典采集技术实现视频捕获实例的设备枚举相同。

 构建滤波器链表,添加各个滤波器、链接并运行链表。应用程序调用该函数实现视频采集、图像预览。

/**************************华丽分割线*******************************/

HRESULT CVMR_Capture::Init(int iDeviceID,HWND hWnd, int iWidth, int iHeight)

{

     HRESULT hr;

     //再次调用函数,释放已经建立的链表

     CloseInterfaces();

     //创建IGraphBuilder

    hr = CoCreateInstance(CLSID_FilterGraph, NULL,   CLSCTX_INPROC_SERVER,

                    IID_IGraphBuilder, (void **)&m_pGB);

    if (SUCCEEDED(hr))

    {

     //创建VMR并添加到Graph

        InitializeWindowlessVMR(hWnd);

     //把指定的设备捆绑到一个滤波器

     if(!BindFilter(iDeviceID, &m_pDF))

            return S_FALSE;

     //添加采集设备滤波器到Graph

     hr = m_pGB->AddFilter(m_pDF, L"Video Capture");

     if (FAILED(hr)) return hr;

     //获取捕获滤波器的引脚

     IEnumPins  *pEnum;

     m_pDF->EnumPins(&pEnum);

     hr |= pEnum->Reset();

     hr |= pEnum->Next(1, &m_pCamOutPin, NULL);

     //获取媒体控制和事件接口

          hr |= m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);

          hr |= m_pGB->QueryInterface(IID_IMediaEventEx, (void **)&m_pME);

     //设置窗口通知消息处理

          //hr = pME->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0);

     //匹配视频分辨率,对视频显示窗口设置

     hr |= InitVideoWindow(hWnd,iWidth, iHeight);

     //为捕获图像帧申请内存

     m_nFramelen=iWidth*iHeight*3;

     m_pFrame=(BYTE*) new BYTE[m_nFramelen];       

     //运行Graph,捕获视频

     m_psCurrent = STOPPED;

     hr |= m_pGB->Render(m_pCamOutPin);

     hr |= m_pMC->Run();

     if (FAILED(hr)) return hr;

     m_psCurrent = RUNNING;

 }

 return hr;

}

上述是一个独立而又完整的使用VMR-9技术实现视频的采集、存储任务的程序。首先如果再次调用该函数,则关闭所有接口、释放有关资源;接着创建IGraphBuilder作为滤波器链表管理器;然后添加VMR滤波器到链表中,把指定的采集设备索引与捕获滤波器捆绑,并把该滤波器添加到链表中;接着获取捕获滤波器的引脚、获取媒体控制接口和事件接口;设置窗口通知消息处理,根据输入的视频分辨率匹配采集设备的分辨率;最后使用自动渲染功能Render方法把滤波器链表链接起来,使用媒体控制接口的方法Run开始运行媒体。以下分别是该函数的子功能实现。

/**************************华丽分割线*******************************/

 创建VMR滤波器,并添加到Graph链表中。

/*创建VMR,添加、设置VMR*/

HRESULT CVMR_Capture::InitializeWindowlessVMR(HWND hWnd)

{

    IBaseFilter* pVmr = NULL;

    //创建VMR

    HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,

                                     CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);

    if (SUCCEEDED(hr))

    {

     //添加VMR到滤波器链表中

          hr = m_pGB->AddFilter(pVmr, L"Video Mixing Renderer");

          if (SUCCEEDED(hr))

          {

               //设置无窗口渲染模式

               IVMRFilterConfig* pConfig;

               hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);

               if( SUCCEEDED(hr))

               {

                      pConfig->SetRenderingMode(VMRMode_Windowless);

                      pConfig->Release();

               }

             //设置传入的窗口为显示窗口

               hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&m_pWC);

               if( SUCCEEDED(hr))

               {

                      m_pWC->SetVideoClippingWindow(hWnd); //设置视频剪辑窗口

               }

        }

        pVmr->Release();

    }

    return hr;

}

首先使用CoCreateInstance创建VMR的接口pVmr,然后把VMR滤波器添加到滤波器链表中。设置视频显示为无窗口模式,首先在pVmr接口下查询IVMRFilterConfig接口,以参数VMRMode_Windowless使用SetRenderingMode方法设置完成。最后设置传入的窗口为视频剪辑窗口。

/**************************华丽分割线*******************************/

 设置捕获图像帧的格式,遍历所有格式是否有预定格式,若没有则以默认格式捕获。

HRESULT CVMR_Capture::InitVideoWindow(HWND hWnd,int width, int height)

{

     HRESULT hr;                                //返回值

     RECT rcDest;                               //矩形区域

    IAMStreamConfig *pConfig;                        //流配置接口

    IEnumMediaTypes *pMedia;                     //枚举媒体类型接口

    AM_MEDIA_TYPE *pmt = NULL, *pfnt = NULL;         //媒体类型

    hr = m_pCamOutPin->EnumMediaTypes( &pMedia );    //获取捕获设备的所有媒体类型

    if(SUCCEEDED(hr))

    {

        //把视频的所有格式遍历一遍,看是否有预定的格式

        while(pMedia->Next(1, &pmt, 0) == S_OK)

        {

                 if( pmt->formattype == FORMAT_VideoInfo )

                 {

                VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)pmt->pbFormat;

                //当前的格式是否与预定格式相同,即宽和高是否相同

                if( vih->bmiHeader.biWidth == width && vih->bmiHeader.biHeight                  == height )

                {

                       pfnt = pmt;      //记录当前媒体格式

                       break;           //退出循环

                }

                DeleteMediaType( pmt ); //格式不匹配,则删除当前查询的媒体格式

        } 

    }

    pMedia->Release();

}

 //获取流配置接口

  hr = m_pCamOutPin->QueryInterface( IID_IAMStreamConfig, (void **) &pConfig );

  if(SUCCEEDED(hr))

  {

   //有预定的媒体格式

     if( pfnt != NULL )

     {

            hr=pConfig->SetFormat( pfnt );

            DeleteMediaType( pfnt );

     }

   //没有预定的格式,读取默认媒体格式

     hr = pConfig->GetFormat( &pfnt );

     if(SUCCEEDED(hr))

     {

            m_nWidth = ((VIDEOINFOHEADER *)pfnt->pbFormat)->bmiHeader.biWidth;
              //读取高

            m_nHeight = ((VIDEOINFOHEADER *)pfnt->pbFormat)->bmiHeader.biHeight;
              //读取宽

          DeleteMediaType( pfnt );

     }

    }

     //获取传入窗口的区域,以设置显示窗口

    ::GetClientRect (hWnd,&rcDest);

    hr = m_pWC->SetVideoPosition(NULL, &rcDest); //设置视频窗口位置

    return hr;

}

/* 删除媒体类型*/

void CVMR_Capture::DeleteMediaType(AM_MEDIA_TYPE *pmt)

{

      if (pmt == NULL) {                         //为空则直接返回

            return;

      }

      if (pmt->cbFormat != 0) {                  //格式块大小不为零

           //释放由CoTaskMemAllocCoTaskMemRealloc申请的内存块

             CoTaskMemFree((PVOID)pmt->pbFormat);

             pmt->cbFormat = 0;

             pmt->pbFormat = NULL;

         }

      if (pmt->pUnk != NULL) {                   //IUnknown接口不为空

             pmt->pUnk->Release();                    //释放资源

             pmt->pUnk = NULL;

      }

      CoTaskMemFree((PVOID)pmt);

}

无论采用CoTaskMemAlloc函数还是采用CreateMediaType函数分配的内存都可以用这个函数来释放。

 

 

/**************************华丽分割线*******************************/

 VMR中获取一帧图像数据,转换目标颜色空间为RGB24m_pFrame指向RGB24格式的数据。

 

/* VMR中获取一帧图像*/

DWORD CVMR_Capture::GrabFrame()

{

 if(m_pWC ) {

      BYTE* lpCurrImage = NULL;

        //获取当前的图像数据,实际的数据内容是BMP位图格式

        if(m_pWC->GetCurrentImage(&lpCurrImage) == S_OK)

        {

           //读取图像帧数据lpCurrImage

           LPBITMAPINFOHEADER  pdib = (LPBITMAPINFOHEADER) lpCurrImage;

           //判断图像的宽度和高度是否正确

           if(m_pFrame==NULL || (pdib->biHeight * pdib->biWidth * 3) !=m_nFramelen )

           {

                if(m_pFrame!=NULL)  delete []m_pFrame;       //删除以前申请的内存

                m_nFramelen = pdib->biHeight * pdib->biWidth * 3; //重新申请内存

                m_pFrame = new BYTE [pdib->biHeight * pdib->biWidth * 3] ;
                    //申请的内存大小

           }       

           if(pdib->biBitCount == 32)

           {

                 DWORD  dwSize=0, dwWritten=0;         

                 BYTE *pTemp32;

                 pTemp32=lpCurrImage + sizeof(BITMAPINFOHEADER);

                 //由于采集的是32RGB,目标要求24位,需要进行转换

                 this->Convert24Image(pTemp32, m_pFrame, pdib->biSizeImage);

           }

           //释放该图像lpCurrImage

           CoTaskMemFree(lpCurrImage);

        }

        else {  return -1;  }

 }else{  return -1;  }

        return m_nFramelen;

}

上述程序实现获取当前显示图像,如果格式不正确则重新申请空间。由于采集的图像多数为32位,所以需要转换成24位以便于算法直接处理,捕获的图像帧放置在类CVMR_Capture的成员变量m_pFrame中,该函数返回图像帧的数据大小。

 

/**************************华丽分割线*******************************/

 颜色空间转换ARGB32RGB24

/* ARGB32 to RGB24 */

bool CVMR_Capture::Convert24Image(BYTE *p32Img, BYTE *p24Img,DWORD dwSize32)

{

 if(p32Img != NULL && p24Img != NULL && dwSize32>0)     //确认指针合法

 {

        DWORD dwSize24;

        dwSize24=(dwSize32 * 3)/4;         //RGB32RGB24的像素点空间只差了一个字节

        BYTE *pTemp=p32Img,*ptr=p24Img;

        for (DWORD index = 0; index < dwSize32/4 ; index++)

        {                                   

             unsigned char r = *(pTemp++); unsigned char g = *(pTemp++);

             unsigned char b = *(pTemp++);  (pTemp++);  //跳过alpha分量

             *(ptr++) = b; *(ptr++) = g;   *(ptr++) = r;记录RGB 3分量

        }

 } else {

        return false; //指针不合法

}

 return true;

}

RGB32RGB24格式只差一个分量alpha,所以转换时把每个像素点的该分量丢掉,即可得到RGB24格式。另外类CVMR_Capture还有其他常见成员函数,如保存滤波器链表。


效果图如下:

 

0 0
原创粉丝点击