视频特效滤镜 via DirectShow Filter

视频特效定义

视频特效(Video effects 或 Visual effects)是对每帧图像进行各种数字化处理达到的效果。如对画面的尺寸、位置、亮度及色度等参数进行处理,就可获得缩放、旋转、黑白、油画等各种效果。

常见的特效技术有:缩放、旋转、裁剪、叠加、老电影、黑白、淡入淡出、水印、去噪、慢动作、2D 转 3D 等等。

DirectShow Filter

DShow 中插件是以 Filter 的形式创建的,需要继承自下面两个接口之一:

  • CTransformFilter:拷贝原始 sample 的 buffer 再修改,一般输入输出格式不一样
  • CTransInPlaceFilter:直接修改原始 sample 的 buffer,输入输出格式一样。该接口其实继承自上面的接口。

下面我们以比较通用的 Transform Filter 说明,相对简单的 In Place Filter 请参考我的另一篇文章:音频特效插件 via DirectShow
代码包含在 Windows SDK 7.x 的 samples\multimedia\directshow\filters\ 目录下。

CEZrgb24 Filter

下面的视频插件实现了颜色滤镜,如 R/G/B 单通道、模糊、灰度等:

class CEZrgb24 : public CTransformFilter,
    public IIPEffect,
    public ISpecifyPropertyPages,
    public CPersistStream
{
    DECLARE_IUNKNOWN;
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);
    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
    
    // Overrriden from CTransformFilter base class
    HRESULT CheckInputType(const CMediaType *mtIn);
    HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
    HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut); 
    
    // These implement the custom IIPEffect interface
    STDMETHODIMP get_IPEffect(int *IPEffect, REFTIME *StartTime, REFTIME *Length);
    STDMETHODIMP put_IPEffect(int IPEffect, REFTIME StartTime, REFTIME Length);
    
    // ISpecifyPropertyPages interface
    STDMETHODIMP GetPages(CAUUID *pPages);
    
    // CPersistStream override
    STDMETHODIMP GetClassID(CLSID *pClsid);
    
    // CPersistStream stuff
    HRESULT ScribbleToStream(IStream *pStream);
    HRESULT ReadFromStream(IStream *pStream);
}; 

CTransformFilter::CheckInputType 函数

本 filter 要求输入类型为 RGB24。

HRESULT CEZrgb24::CheckInputType(const CMediaType *mtIn)
{
    CheckPointer(mtIn, E_POINTER);

    if (*mtIn->FormatType() != FORMAT_VideoInfo)
        return E_INVALIDARG;

    if (CanPerformEZrgb24(mtIn))
        return NOERROR;
        
    return E_FAIL;
}

BOOL CEZrgb24::CanPerformEZrgb24(const CMediaType *pMediaType) const
{
    CheckPointer(pMediaType, FALSE);
    
    if (IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) {
        if (IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB24)) {
            VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*)pMediaType->Format();
            return (pvi->bmiHeader.biBitCount == 24);
        }
    }
    
    return FALSE;
}

CTransformFilter::CheckTransform 函数

本 filter 要求输入输出类型一致,即都是 RGB24。

HRESULT CEZrgb24::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)
{
    CheckPointer(mtIn, E_POINTER);
    CheckPointer(mtOut, E_POINTER);

    if (CanPerformEZrgb24(mtIn)) {
        if (*mtIn == *mtOut) 
            return NOERROR;
    }

    return E_FAIL;
}

CTransformFilter::DecideBufferSize 函数

决定输出 buffer 的大小,这里要求和输入 sample 的 buffer 一样(还不如用 In Place Filter。。)

HRESULT CEZrgb24::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
    if (m_pInput->IsConnected() == FALSE)
        return E_UNEXPECTED;
        
    CheckPointer(pAlloc, E_POINTER);
    CheckPointer(pProperties, E_POINTER);
    
    HRESULT hr = NOERROR;
    pProperties->cBuffers = 1;
    pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize();
    
    // Ask the allocator to reserve us some sample memory, NOTE the function
    // can succeed (that is return NOERROR) but still not have allocated the
    // memory that we requested, so we must check we got whatever we wanted
    ALLOCATOR_PROPERTIES actual;
    hr = pAlloc->SetProperties(pProperties, &actual);
    RETURN_IF_FAILED(hr);
    
    if (pProperties->cBuffers > actual.cBuffers ||
            pProperties->cbBuffer > actual.cbBuffer) {
                return E_FAIL;
    }
    
    return NOERROR;
}

CTransformFilter::Transform 函数

先拷贝输入 sample 到输出 sample,再 in place 处理。

HRESULT CEZrgb24::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
    CheckPointer(pIn, E_POINTER);   
    CheckPointer(pOut, E_POINTER);   

    // Copy the properties across

    HRESULT hr = Copy(pIn, pOut);
    RETURN_IF_FAILED(hr);

    // Check to see if it is time to do the sample

    CRefTime tStart, tStop ;
    hr = pIn->GetTime((REFERENCE_TIME *) &tStart, (REFERENCE_TIME *) &tStop);

    if (tStart >= m_effectStartTime) {
        if (tStop <= (m_effectStartTime + m_effectTime)) 
            return Transform(pOut);
    }

    return NOERROR;
}
CEZrgb24::Copy 函数

拷贝 数据、时间戳、SyncPoint、媒体类型、PreRollDiscontinuity 等属性。

HRESULT CEZrgb24::Copy(IMediaSample *pSource, IMediaSample *pDest) const
{	
    // Copy the sample data
    BYTE *pSourceBuffer, *pDestBuffer;
    long lSourceSize = pSource->GetActualDataLength();
    pSource->GetPointer(&pSourceBuffer);    
    pDest->GetPointer(&pDestBuffer);
    CopyMemory( (PVOID) pDestBuffer,(PVOID) pSourceBuffer, lSourceSize);

    // Copy the sample time
    REFERENCE_TIME TimeStart, TimeEnd; 	
    if (NOERROR == pSource->GetTime(&TimeStart, &TimeEnd))
        pDest->SetTime(&TimeStart, &TimeEnd);
    LONGLONG MediaStart, MediaEnd;
    if (pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR)
        pDest->SetMediaTime(&MediaStart, &MediaEnd);

    // Copy the Sync point property
    HRESULT hr = pSource->IsSyncPoint();
    if (hr == S_OK)
        pDest->SetSyncPoint(TRUE);
    else if (hr == S_FALSE) 
        pDest->SetSyncPoint(FALSE);
    else
        return E_UNEXPECTED;

    // Copy the media type
    AM_MEDIA_TYPE *pMediaType; 		
    pSource->GetMediaType(&pMediaType);    
    pDest->SetMediaType(pMediaType);
    DeleteMediaType(pMediaType);
    
    // Copy the preroll property
    hr = pSource->IsPreroll(); 		
    if (hr == S_OK) 
        pDest->SetPreroll(TRUE);
    else if (hr == S_FALSE)
        pDest->SetPreroll(FALSE);
    else
        return E_UNEXPECTED;
        
    // Copy the discontinuity property
    hr = pSource->IsDiscontinuity(); 
    if (hr == S_OK)
        pDest->SetDiscontinuity(TRUE);
    else if (hr == S_FALSE)
        pDest->SetDiscontinuity(FALSE);
    else
        return E_UNEXPECTED;
        
    // Copy the actual data length
    long lDataLength = pSource->GetActualDataLength(); 
    pDest->SetActualDataLength(lDataLength);
    
    return NOERROR;
}
CEZrgb24::Transform 函数

得到 buffer 指针然后逐像素处理。下面只展示了红色单通道的算法。

HRESULT CEZrgb24::Transform(IMediaSample *pMediaSample)
{
    BYTE* pData = NULL;             // Pointer to the actual image buffer
    long lDataLen = 0;              // Holds length of any given sample
    RGBTRIPLE* prgb = NULL;         // Holds a pointer to the current pixel
    
    AM_MEDIA_TYPE* pType = &m_pInput->CurrentMediaType();
    VIDEOINFOHEADER* pvi = (VIDEOINFOHEADER*) pType->pbFormat;
    pMediaSample->GetPointer(&pData);
    lDataLen = pMediaSample->GetSize();
    
    // Get the image properties from the BITMAPINFOHEADER
    int cxImage    = pvi->bmiHeader.biWidth;
    int cyImage    = pvi->bmiHeader.biHeight;
    int numPixels  = cxImage * cyImage;
    
    switch (m_effect) {
       case IDC_RED: // Zero out the green and blue components to leave only the red
            prgb = (RGBTRIPLE*) pData;
            for (int iPixel = 0; iPixel < numPixels; iPixel++, prgb++) {
                prgb->rgbtGreen = 0;
                prgb->rgbtBlue = 0;
            }
            break;
            
        case IDC_XXX: // Other effects 
            break;
    }
    
    return NOERROR;
}

ISpecifyPropertyPages::GetPages 函数

返回可设置本 filter 属性的 page 对象(CLSID)。

STDMETHODIMP CEZrgb24::GetPages(CAUUID *pPages)
{
    CheckPointer(pPages, E_POINTER);

    pPages->cElems = 1;
    pPages->pElems = (GUID*)CoTaskMemAlloc(sizeof(GUID));
    if (pPages->pElems == NULL)
        return E_OUTOFMEMORY;

    *(pPages->pElems) = CLSID_EZrgb24PropertyPage;
    return NOERROR;
}

CEZrgb24Properties 类

提供本 filter 的属性设置界面。
ezrgb prop

class CEZrgb24Properties : public CBasePropertyPage
{
public:
    static CUnknown* WINAPI CreateInstance(LPUNKNOWN lpunk, HRESULT *phr);

private:
	// Overrides from CBasePropertyPage
	HRESULT OnConnect(IUnknown *pUnknown);
	HRESULT OnDisconnect();
	HRESULT OnActivate();
	HRESULT OnDeactivate();
	HRESULT OnApplyChanges();
	INT_PTR OnReceiveMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    void    GetControlValues();

    CEZrgb24Properties(LPUNKNOWN lpunk, HRESULT *phr);
};

CPersistStream override 函数

当 DShow dump 一个 filter graph 时,继承自 CPersistStream 的 filter 的属性值可以被保存起来供下次读取。

HRESULT CEZrgb24::WriteToStream(IStream *pStream)
{
    HRESULT hr;

    WRITEOUT(m_effect);
    WRITEOUT(m_effectStartTime);
    WRITEOUT(m_effectTime);

    return NOERROR;
}

HRESULT CEZrgb24::ReadFromStream(IStream *pStream)
{
    HRESULT hr;

    READIN(m_effect);
    READIN(m_effectStartTime);
    READIN(m_effectTime);

    return NOERROR;
}

其他框架的滤镜

  • 关于 FFmpeg 的视频滤镜请参考 这里
  • 关于 Media Foundation 的视频滤镜请参考 这里

Blueware
EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值