对于DIrectShow的初学者而言,最大的困难莫过于尝试设计自定义的filter。
设计自定义的transform filter是困难的
因为 首先filter是一种dll (后缀名为.ax)而编写dll工程需要一定的VC基础 所以建议先补充一点dll的知识
其次 dll的注册,GUID的生成和工程的配置都很麻烦。
再次 网上缺乏现成的transform filter的例子。DirectShow给的源码比如NULLINPLACE 和CONTRAST都太复杂,都带有对话框和属性页,不适合初学者,而且这些例子 没有一个涉及到图像格式的转换,而transform filter最大的公用就是媒体类型的转换,因此这些例子不适用
作为一个初学者,我深深受到这些问题的困扰,经过刻苦钻研终于走出了这个泥潭,豁然开朗。于是把它记录下来,希望可以对其他人有帮助,也作为对08年的一个小结。
我的例子是 设计一个 transform filter 把 YUY2 16bit 的媒体转化为RGB24 24bit的类型。
原因是我的摄像头只支持YUY2 16bit这种格式, 我想得到位图。。顺便学习一下Filter的设计
以下为具体步骤:
一 配置开发环境
1. VC中在Tools->Options->Directories 设置好DirectX SDK的头文件和库文件路径
2. 编译了基类源码,生成strmbasd.lib (debug版), strmbase.lib(release版)
3. VC向导新建一个win32 DLL(empty)工程
4. Setting->Link->Output file name: YUV2RGBfilter.ax
5. Setting->Link加入strmbasd.lib winmm.lib quartz.lib vfw32.lib (注意路径)
6. 定义一个同名.def文件,加入到工程,内容如下:
LIBRARY YUV2RGBfilter.ax
EXPORTS
DllMain PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
7.建立一个类 YUV2RGBfilter 建立他的cpp文件和h文件
8. 在YUV2RGBfilter.cpp中定义DLL的入口函数及注册 放在cpp文件的最后
//
// DllEntryPoint
//
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD dwReason,
LPVOID lpReserved)
{
return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}
////////////////////////////////////////////////////////////////////////
//
// Exported entry points for registration and unregistration
// (in this case they only call through to default implementations).
//
////////////////////////////////////////////////////////////////////////
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2( TRUE );
}
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2( FALSE );
}
9. cpp文件中要包含的头文件
#include <streams.h>
#include <windows.h>
#include <initguid.h>
#include <olectl.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#endif
#include "Y2Ruids.h" // our own public guids
#include "YUV2RGBfilter.h"
二 开发Filter
1. 生成GUID( 命令行模式下运行guidgen工具) 为他建立一个文件Y2Ruids.h 单独引用
#include <initguid.h>
// YUV2toRGB24 Filter Object
// {F91FC8FD-B1A6-49b0-A308-D6EDEAF405DA}
DEFINE_GUID(CLSID_YUV2toRGB24,
0xf91fc8fd, 0xb1a6, 0x49b0, 0xa3, 0x8, 0xd6, 0xed, 0xea, 0xf4, 0x5, 0xda);
2. 构造CYUV2RGBfilter类 继承自CTransformFilter 写在TransformFilter.h中
// ----------------------------------------------------------------------------
// Class definitions of CYUV2RGBfilter
// ----------------------------------------------------------------------------
//
//
class CYUV2RGBfilter : public CTransformFilter
{
public:
static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
DECLARE_IUNKNOWN;
// override pure virtual function
HRESULT CheckInputType(const CMediaType *mtIn);
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
private:
//Constructor
CYUV2RGBfilter(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);
// member function
VOID ChangeFormat(AM_MEDIA_TYPE* pAdjustedType);
DWORD ConvertYUV2toRGB(BYTE* yuv, BYTE* rgb, DWORD dsize);
// member variable
const long m_lBufferRequest;
CCritSec m_Y2RLock; // To serialise access.
};
3. 按格式改写构造函数
//
// CNullInPlace::Constructor
//
CYUV2RGBfilter::CYUV2RGBfilter(TCHAR *tszName,LPUNKNOWN punk,HRESULT *phr) :
CTransformFilter(tszName, punk, CLSID_YUV2toRGB24),
m_lBufferRequest(1)
{
ASSERT(tszName);
ASSERT(phr);
} // CYUV2RGBfilter
4. 改写CTransformFilter五个纯虚函数(最重要的地方)
HRESULT CheckInputType(const CMediaType *mtIn);
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
5. 设计自己的私有函数 完成一定的功能
6. 注册Filter信息
// 注册信息
//setup data
const AMOVIESETUP_MEDIATYPE
sudPinTypes = { &MEDIATYPE_Video // clsMajorType
, &MEDIASUBTYPE_NULL } ; // clsMinorType
const AMOVIESETUP_PIN
psudPins[] = { { L"Input" // strName
, FALSE // bRendered
, FALSE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"Output" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes } // lpTypes
, { L"Output" // strName
, FALSE // bRendered
, TRUE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"Input" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes } }; // lpTypes
const AMOVIESETUP_FILTER
sudYUV2RGB = { &CLSID_YUV2toRGB24 // clsID
, L"YUV2RGB" // strName
, MERIT_DO_NOT_USE // dwMerit
, 2 // nPins
, psudPins }; // lpPin
//
// Needed for the CreateInstance mechanism
//
CFactoryTemplate g_Templates[1]=
{ {L"YUV2RGB"
, &CLSID_YUV2toRGB24
, CYUV2RGBfilter::CreateInstance
, NULL
, &sudYUV2RGB }
};
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);
编译成功后生成GrayFilter.ax
命令行运行regsvr32 GrayFilter.ax注册即可 不用反复注册,只用注册一次,如若修改只需将重新编译的.ax覆盖原来的就行了
调试最好在graphEdit中经行 比较方便。
以上就是设计一个filter的总体步骤。
三 下面就关键点 五个重载的纯虚函数做详细介绍。 这才是最关键的地方。
HRESULT CheckInputType(const CMediaType *mtIn);
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
这五个函数全部是都纯虚函数 ,是CTransformFilter为我们提供的接口,必须重载他们才能实例化。
初学者最大的困扰莫过于,是谁调用了这些函数。这些函数调用的时候实参是从哪来的。我一开始就被这些问题困扰。其实DX的帮助文档里就讲的很清楚了只是我一开始没认真看;
CheckInputType是由tranformfiltr的输入pin调用的用来检查本Filter的输入媒体是否合法;
CheckTransform是由tranformfiltr的输出pin调用的用来检查本filter的输出是否和合法;
GetMediaType是有由tranformfiltr的输出pin调用的用来获取该输出端口支持的媒体格式供下游filter的枚举
DecideBufferSize是由tranformfiltr的输出pin调用的来确定buffer的数量和大小
上游filter通过调用filter上输入pin上的IMemInputPin::Receive方法,将sample传递到filter,filter调用CTransformFilter::Transform方法来处理数据
整个过程就是
输入pin调用CheckInputType来筛选上游过来的媒体类型,如果可以接受 就有输出pin通GetMediaType来枚举输出媒体类型,进一步通过输出pin的CheckTransform来找到与输入媒体类型相融合的输出媒 体类型并选中。在通过DecideBufferSize确定输出buffer的属性,所有的检查和筛选通过以后就可以连接了, 并通过tranform 将输入pin上的sample 传个输出pin输出媒体的类型是由GetMediaType来确定的, 只要媒体类型对应了就可以成功连接但是数据的传送还是要通过transform来实现。理论上对于没有压缩的视频, 一个sample就是一帧的数据,可以精确的量化处理。
要实现输出pin上媒体格式的转化 就必须在在GetMediaType函数中修改新的媒体格式,然后在checkTransform中确认 输出的媒体格式是不是期望的输出。例如 要将YUY2 16bit的媒体格式改为RGB8 8bit的媒体格式 就要做如下修改:
在GetMediaType中
CheckPointer(pMediaType,E_POINTER);
VIDEOINFO vih;
memset(&vih, 0, sizeof(vih));
vih.bmiHeader.biCompression = 0;
vih.bmiHeader.biBitCount = 8;
vih.bmiHeader.biSize = 40;
vih.bmiHeader.biWidth = 640;
vih.bmiHeader.biHeight = 480;
vih.bmiHeader.biPlanes = 1;
vih.bmiHeader.biSizeImage = 307200;
vih.bmiHeader.biClrImportant = 0;
vih.bmiHeader.biClrUsed = 256;
//alter the pallete
for (UINT i=0; i<256; i++)
{
vih.bmiColors[i].rgbBlue=(BYTE)i;
vih.bmiColors[i].rgbRed=(BYTE)i;
vih.bmiColors[i].rgbGreen=(BYTE)i;
vih.bmiColors[i].rgbReserved=(BYTE)0;
}
pMediaType->SetType(&MEDIATYPE_Video);
pMediaType->SetFormatType(&FORMAT_VideoInfo);
pMediaType->SetFormat((BYTE*)&vih, sizeof(vih));
pMediaType->SetSubtype(&MEDIASUBTYPE_RGB8);
pMediaType->SetSampleSize(307200);
return NOERROR;
然后在checkTransform中确认是否是期望的输出
BITMAPINFOHEADER *pNewType = HEADER(mtOut->Format());
if ((pNewType->biPlanes==1)
&&(pNewType->biBitCount==8)
&&(pNewType->biWidth==640)
&&(pNewType->biHeight==480)
&&(pNewType->biClrUsed==256)
&&(pNewType->biSizeImage==307200))
{
return S_OK;
}
我的实现过程如下
// GetMediaType
//
// I support one type, namely the type of the input pin
// We must be connected to support the single output type
//
HRESULT CYUV2RGBfilter::GetMediaType(int iPosition, CMediaType *pMediaType)
{
// Is the input pin connected
if(m_pInput->IsConnected() == FALSE)
{
return E_UNEXPECTED;
}
// This should never happen
if(iPosition < 0)
{
return E_INVALIDARG;
}
// Do we have more items to offer
if(iPosition > 0)
{
return VFW_S_NO_MORE_ITEMS;
}
CheckPointer(pMediaType,E_POINTER);
if (iPosition == 0)
{
HRESULT hr = m_pInput->ConnectionMediaType(pMediaType);
if (FAILED(hr))
{
return hr;
}
}
// make some appropriate change
ASSERT(pMediaType->formattype == FORMAT_VideoInfo);
pMediaType->subtype = MEDIASUBTYPE_RGB24;
VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(pMediaType->pbFormat);
pVih->bmiHeader.biCompression = 0;
pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
pVih->bmiHeader.biBitCount = 24;
pVih->bmiHeader.biHeight = 480;
pVih->bmiHeader.biWidth = 640;
return S_OK;
} // GetMediaType
//
// CheckInputType
//
// Check the input type is OK, return an error otherwise
//
HRESULT CYUV2RGBfilter::CheckInputType(const CMediaType *mtIn)
{
CheckPointer(mtIn,E_POINTER);
// Check this is a VIDEOINFO type
if(*mtIn->FormatType() != FORMAT_VideoInfo)
{
return E_INVALIDARG;
}
if((IsEqualGUID(*mtIn->Type(), MEDIATYPE_Video)) &&
(IsEqualGUID(*mtIn->Subtype(), MEDIASUBTYPE_YUY2)))
{
VIDEOINFO *pvi = (VIDEOINFO *) mtIn->Format();
if ((pvi->bmiHeader.biBitCount == 16)
&&(pvi->bmiHeader.biCompression==0))
return S_OK;
else
return FALSE;
}
else
{
return FALSE;
}
} // CheckInputType
// CheckTransform
//
// To be able to transform the formats must be compatible
//mtIn YUV2 16bit
//mtOut RGB24 24bit
HRESULT CYUV2RGBfilter::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)
{
CheckPointer(mtIn,E_POINTER);
CheckPointer(mtOut,E_POINTER);
HRESULT hr;
if(FAILED(hr = CheckInputType(mtIn)))
{
return hr;
}
// format must be a VIDEOINFOHEADER
if((*mtOut->FormatType() != FORMAT_VideoInfo)
||(mtOut->cbFormat<sizeof(VIDEOINFOHEADER ))
||(mtOut->subtype!=MEDIASUBTYPE_RGB24))
{
return E_INVALIDARG;
}
BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
if ((pBmiOut->biPlanes!=1)
||(pBmiOut->biBitCount!=24)
||(pBmiOut->biCompression!=0)
||(pBmiOut->biWidth!=640)
||(pBmiOut->biHeight!=480))
{
return E_INVALIDARG;
}
return S_OK;
}
// CheckTransform
HRESULT CYUV2RGBfilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
CheckPointer(pAlloc,E_POINTER);
CheckPointer(pProperties,E_POINTER);
// Is the input pin connected
if(m_pInput->IsConnected() == FALSE)
{
return E_UNEXPECTED;
}
HRESULT hr = NOERROR;
pProperties->cBuffers = 1;
pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize()*2; //output is double of the input samples
ASSERT(pProperties->cbBuffer);
// If we don't have fixed sized samples we must guess some size
if(!m_pInput->CurrentMediaType().bFixedSizeSamples)
{
if(pProperties->cbBuffer < 100000)
{
// nothing more than a guess!!
pProperties->cbBuffer = 100000;
}
}
// 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);
if(FAILED(hr))
{
return hr;
}
ASSERT(Actual.cBuffers == 1);
if(pProperties->cBuffers > Actual.cBuffers ||
pProperties->cbBuffer > Actual.cbBuffer)
{
return E_FAIL;
}
return NOERROR;
} // DecideBufferSize
//
// Transform
//
// Copy the input sample into the output sample
//
//
HRESULT CYUV2RGBfilter::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
CheckPointer(pIn,E_POINTER);
CheckPointer(pOut,E_POINTER);
// Copy the sample data
BYTE *pSourceBuffer, *pDestBuffer;
long lSourceSize = pIn->GetActualDataLength();
long lDestSize = (long)(lSourceSize*1.5);
pIn->GetPointer(&pSourceBuffer);
pOut->GetPointer(&pDestBuffer);
//change data
ConvertYUV2toRGB(pSourceBuffer,pDestBuffer,lSourceSize);
//memset(pDestBuffer,100,lDestSize);
REFERENCE_TIME TimeStart, TimeEnd;
if(NOERROR == pIn->GetTime(&TimeStart, &TimeEnd))
{
pOut->SetTime(&TimeStart, &TimeEnd);
}
LONGLONG MediaStart, MediaEnd;
if(pIn->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR)
{
pOut->SetMediaTime(&MediaStart,&MediaEnd);
}
// Copy the Sync point property
HRESULT hr = pIn->IsSyncPoint();
if(hr == S_OK)
{
pOut->SetSyncPoint(TRUE);
}
else if(hr == S_FALSE)
{
pOut->SetSyncPoint(FALSE);
}
else
{ // an unexpected error has occured...
return E_UNEXPECTED;
}
//
AM_MEDIA_TYPE* pMediaType;
pIn->GetMediaType(&pMediaType);
ChangeFormat(pMediaType);
// Copy the media type
pOut->SetMediaType(pMediaType);
// Copy the preroll property
hr = pIn->IsPreroll();
if(hr == S_OK)
{
pOut->SetPreroll(TRUE);
}
else if(hr == S_FALSE)
{
pOut->SetPreroll(FALSE);
}
else
{ // an unexpected error has occured...
return E_UNEXPECTED;
}
// Copy the discontinuity property
hr = pIn->IsDiscontinuity();
if(hr == S_OK)
{
pOut->SetDiscontinuity(TRUE);
}
else if(hr == S_FALSE)
{
pOut->SetDiscontinuity(FALSE);
}
else
{ // an unexpected error has occured...
return E_UNEXPECTED;
}
// Copy the actual data length
//KASSERT((long)lDestSize <= pOut->GetSize());
pOut->SetActualDataLength(lDestSize);
return S_OK;
} // Transform
经过这些步骤就能得到符合功能要求的transform filter
同时经过以上步骤也能对filter开发有个大体的了解