科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件VC DirectShow对视频进行图片处理之二

VC DirectShow对视频进行图片处理之二

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

Filter要做以下工作:接受24bit RGB格式的图片,这由上级Filter肢解视频得到,并把它处理成32bit ARGB图片,之后传给外部函数进行进一步处理。

作者:longway 来源:天极开发 2007年10月16日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
我在上面曾提过在Filter中要在接受到新数据时调用外部函数进行处理,因此我定义了一个回调类(我自己称呼的)和一个回调函数指针。这样可以把回调类作为MFC视图类的一个基类,以方便地使用MFC视图类中的成员变量。而同时提供回调函数指针就可以满足同时播放多个视频文件、使用多个摄像头的需要。这是我在使用中感到有必要而后来修改得来的,使Filter的使用具有足够的灵活性。下面看看以上Filter类中函数的具体实现。

//===========================================================
// 创建进程。

CUnknown* WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk,HRESULT* phr)
{
 return new CVideoRenderer(pUnk,phr);
}

//===========================================================
// 构造函数

CVideoRenderer::CVideoRenderer(LPUNKNOWN pUnk,HRESULT *phr) : CBaseVideoRenderer(CLSID_lwVideoRenderer,"lw Video Renderer",pUnk,phr)
{
 m_pCopyBuffer = NULL;
 m_pFunCLS = NULL;
 m_pPF = NULL;
 m_pixelNum = 0;
}

//===========================================================
// 释构函数

CVideoRenderer::~CVideoRenderer()
{
 if(this->m_pCopyBuffer){
  delete [] m_pCopyBuffer;
 }
}

//===========================================================
// 检查媒体类型

HRESULT CVideoRenderer::CheckMediaType(const CMediaType* pmt)
{
 VIDEOINFO *pvi;
 // 只接受视频
 if( *pmt->FormatType() != FORMAT_VideoInfo ) {
  return E_INVALIDARG;
 }
 // 只接受 RGB24 格式,即 R、G、B各 1 Byte
 pvi = (VIDEOINFO *)pmt->Format();
 if(IsEqualGUID( *pmt->Type(),MEDIATYPE_Video) && IsEqualGUID( *pmt->Subtype(),MEDIASUBTYPE_RGB24)){
  return S_OK;
 }
 return E_INVALIDARG;
}

//===========================================================
// 设置媒体类型,获取图像的各种信息(宽、高等具体信息),处理图像要用到

HRESULT CVideoRenderer::SetMediaType(const CMediaType* pmt)
{
 VIDEOINFO *pviBmp; // Bitmap info header
 pviBmp = (VIDEOINFO *)pmt->Format();
 memset(&m_bmpInfo,0,sizeof(BITMAPINFO)); // 清零
 m_bmpInfo.bmiHeader = pviBmp->bmiHeader;
 // 改为 32bit,因为我会把它处理成 32bit 的
 m_bmpInfo.bmiHeader.biBitCount = 32;
 // 当然,缓冲区大小也变了
 m_bmpInfo.bmiHeader.biSizeImage = m_bmpInfo.bmiHeader.biSizeImage * 4 / 3;
 // 开辟新 32bit 图片的缓冲区
 if(m_pCopyBuffer){ delete [] m_pCopyBuffer;}
 m_pCopyBuffer = new BYTE[m_bmpInfo.bmiHeader.biSizeImage];
 m_pixelNum = m_bmpInfo.bmiHeader.biWidth * m_bmpInfo.bmiHeader.biHeight;
 return S_OK;
}

//===========================================================
// 处理媒体采样

HRESULT CVideoRenderer::DoRenderSample(IMediaSample* pMediaSample)
{
 // 获取采样的数据区指针,即 24bit 图片的数据区指针
 BYTE* pb = NULL;
 pMediaSample->GetPointer(&pb);
 if(!pb){
  return E_FAIL;
 }
 // 加锁!锁住我要操作的数据区,以防处理到一半的时候被打断而造成错误
 // 其实就是多线程编程中经常使用的临界区的类形式,
 // 利用构造函数和释构函数来进入和退出临界区
 // m_RendererLock 是 CBaseVideoRenderer 的成员,继承得来。
 CAutoLock cAutoLock(&this->m_RendererLock);
 // 把 24bit 图片处理成 32bit
 BYTE* pb32 = m_pCopyBuffer; // 指向 32bit 缓冲区的指针
 for(UINT i = 0; i < m_pixelNum; i ++){
  pb32[0] = pb[0];
  pb32[1] = pb[1];
  pb32[2] = pb[2];
  pb32[3] = 0xff; // 0xff 即 255
  pb += 3;
  pb32 += 4;
 }
 // 如果有回调类,进行回调处理
 if(m_pFunCLS){
  m_pFunCLS->procFun(&m_bmpInfo,m_pCopyBuffer);
 }
 // 如果有回调函数,进行处理
 if(m_pPF){
  m_pPF(&m_bmpInfo,m_pCopyBuffer);
 }
 return S_OK;
}

  至此,一个简单的 Filter 完成了,可以编译成功、用regsvr32.exe 注册并到GraphEdit.exe 中进行测试了。不过如果要在程序中使用的话,您会发现无法设置回调函数或回调类。这个Filter 是如此的无用,除了IBaseFilter 接口的基本功能外我们从它身上得不到任何有用的东西。所以,还得给它写个接口,让我们可以设置一些东西。写接口也不是难事,只要有一个接口的例子,随便谁都可以对照写出一个来,我就抄写了一个。新建一个 IVRControl.h 文件,加入下面代码。

// {244DF760-7E93-4cf0-92F4-DCB79F646B7E} 接口的 GUID

static const GUID IID_IVRControl = {0x244df760, 0x7e93, 0x4cf0, {0x92, 0xf4, 0xdc, 0xb7, 0x9f, 0x64, 0x6b, 0x7e}};

// 接口定义

DECLARE_INTERFACE_(IVRControl, IUnknown)
{
 STDMETHOD(GetBmpInfo) (THIS_ // 方法一:获取图片信息
  BITMAPINFO** ppBmpInfo ) PURE;

 STDMETHOD(GetPointer) (THIS_ // 方法二:获取缓冲区指针
  BYTE** ppb // 缓冲区指针的指针 ) PURE;

 STDMETHOD(SetFunCLS) (THIS_ // 方法三:设置回调类
  FunCLS* pFunCLS // 回调类指针 ) PURE;

 STDMETHOD(SetFun) (THIS_ // 方法四:设置回调函数
  pProcFun pPF ) PURE;
};

  写完接口后就要实现它,在VR.h 中添加 #include "IVRControl.h",而把接口作为Filter 类的一个基类,像这样:

class CVideoRenderer : public CBaseVideoRenderer, public IVRControl

  在CVideoRenderer 类中加入接口函数和询问接口函数:

// 询问接口,一般可以不要的,但这里需要使用接口,也重载了
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
// 接口函数
DECLARE_IUNKNOWN;
STDMETHODIMP GetBmpInfo(BITMAPINFO** ppBmpInfo);
STDMETHODIMP GetPointer(BYTE** ppb);
STDMETHODIMP SetFunCLS(FunCLS* pFunCLS);
STDMETHODIMP SetFun(pProcFun pPF);

  再在VR.cpp 中加入上述函数的具体实现代码:

//===========================================================
// 询问接口

STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void** ppv)
{
 CheckPointer(ppv,E_POINTER);
 if(riid == IID_IVRControl){
  // 返回接口。这里有个细节:返回接口时,Filter 的引用计数会增一,所以外部程序用完接口后也要对它进行释放
  return GetInterface((IVRControl*) this,ppv);
 }else{
  return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv);
 }
}

//===========================================================
// 以下为接口函数的具体实现,只是简单的赋值

STDMETHODIMP CVideoRenderer::GetBmpInfo(BITMAPINFO** ppBmpInfo)
{
 *ppBmpInfo = &this->m_bmpInfo;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::GetPointer(BYTE** ppb)
{
 *ppb = m_pCopyBuffer;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::SetFunCLS(FunCLS* pFunCLS)
{
 m_pFunCLS = pFunCLS;
 return S_OK;
}

STDMETHODIMP CVideoRenderer::SetFun(pProcFun pPF)
{
 m_pPF = pPF;
 return S_OK;
}

  不知您注意到了没有:接口其实就是一个虚基类。类在 C++ 等现代编程语言中无处不在,也没什么好惊奇的,只是有利于更好理解。再有一个,看似功能强大的接口可能偏偏很容易实现,它依附于对象,它的复杂可能都隐藏在对象内了。

  可以看出在接口定义中也要用到回调类和回调函数指针的定义,所以我把它们连同 Filter CLSID 的定义一起移到 IVRControl.h 文件的开头,使用到此 Filter 时只把 IVRControl.h 这一个文件包含进去就行了。

  不错,我们已经一步步、一个个函数的把设想中的 Filter 写出来了,已成功完成了Filter,以 Release 模式把它编译出来足有80多K,用 UPX 压缩后就是30 多K。这样把代码铺出来看,好像蛮多的,不过我在敲代码时一点也不觉得,因为每个函数所做的的确很少,循着逻辑规矩、步步为营地写真的很easy。
 

查看本文源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章