视频特效滤镜 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、媒体类型、PreRoll、Discontinuity 等属性。
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 的属性设置界面。
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;
}
其他框架的滤镜
– EOF –