做的这个directshow的filter属于transform filter。在其间,参考了,directshow的帮助文档,一本外文的介绍directshow的书(这本书不错,里面的代码,虽然感觉有copy directshow帮助文档的成分,但是,讲的挺不错),还有vc知识库中的两篇文章了,这两篇文章也写得挺好的。如果当初我没有看,我想会走不少弯路。
要写directshow的transform filter 已经准备了好久了。曾经用过directshow,不过里面的也就是用现成的filter组成一个filter graph,然后run,就可以了。把自己写filter看作挺难的事情。现在发现,其实写transform filter不难,当然,也只是像我这样简单的。但是,处理的速度确实是问题。
首先,directshow filter是符合com组件规范的,也就是,其是一个com组件,要符合com的规范,需要实现一些函数的,不过,已经有不少的基类了,只需要继承就可以了,这样,对com组件所涉及的知识就少了很多。在编写directshow filter时,只要找准了要继承的基类,然后,实现里面的虚函数等等,写上自己的控制代码,就可以了。
下面先简单的给出一些源代码和说明,具体的解释,以后会做。
这个简单的transform filter由三个文件组成:
1。ToGrayFilter.def:由于filter是个基于dll的com组件,所以一般的filter要实现几个入口函数。要导出dll中的函数有两种方法:一种是在定义函数时使用导出关键字__declspec(dllexport),也就是在.h文件中定义函数如下:
extern “C“ __declspec(dllexport) BOOL DllRegisterServer;等等;第二种方法是使用模块定义文件,这也是我在这里用的方法。
//ToGrayFilter.def-----------------------------------------------------------------------
//
LIBRARY ToGrayFilter.ax
EXPORTS
DllMain PRIVATE //dll的入口函数,directshow中实现的是dllEntryPoint
DllGetClassObject PRIVATE //用于获得类工厂指针
DllCanUnloadNow PRIVATE //系统空闲时会调用这个函数,确定是否可以卸载DLL
DllRegisterServer PRIVATE //将com组件注册到注册表中
DllUnregisterServer PRIVATE //删除注册表中的com组件的注册信息
上面的函数是作为一个典型的自注册com组件dll所必需的5个导出函数。
2。ToGrayFilter.h
在这个文件中声明了一个c++类,这个类是从directshow中的一个用于方便用户编写filter的基类中继承的。
//ToGrayFilter.h-------------------------------------------------------------------------------------------------------
//
#ifndef TOGRAYFILTER_H_
#define TOGRAYFILTER_H_
//
// {5F2265B1-A841-4eb7-871F-5556436042AC}
DEFINE_GUID(CLSID_ToGrayFilter,
0x5f2265b1, 0xa841, 0x4eb7, 0x87, 0x1f, 0x55, 0x56, 0x43, 0x60, 0x42, 0xac);
class CToGrayFilter:public CTransformFilter
{
public:
CToGrayFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr); //constructor
~CToGrayFilter(); //destructor
public:
// Static object-creation method (for the class factory) //必须有的
//
static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr);
public:
//implement the base filter 's method,下面5个函数是CTranseformFilter的虚函数,必须实现了的
//
HRESULT CheckInputType(const CMediaType *pmtIn);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut);
HRESULT DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProp);
HRESULT Transform(IMediaSample *pSource,IMediaSample *pDest);
//this method is also in the base filter class,but in the base class it does noting,
//implement here just want to get m_VihIn and m_VihOut
//
HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);
private:
//my own process method,这是我自己的处理函数:),在Transform这个函数中调用的
//
HRESULT ToGray(BYTE *pbInput,BYTE *pbOutput);
//a help method ,just copy from helper document,这个函数,最终没有在这个filter中用,因为不知道为何,
//感觉有些问题,按理说是不应该这样的。最后是自己直接操作了
void GetVideoInfoParameters(
const VIDEOINFOHEADER *pvih, // Pointer to the format header.
BYTE * const pbData, // Pointer to the first address in the buffer.
bool bYuv, // Is this a YUV format? (true = YUV, false = RGB)
DWORD *pdwWidth, // Returns the width in pixels.
DWORD *pdwHeight, // Returns the height in pixels.
LONG *plStrideInBytes, // Add this to a row to get the new row down.
BYTE **ppbTop // Returns a pointer to the first byte in the
// top row of pixels.
);
VIDEOINFOHEADER m_VihIn; // Holds the current video format (input),可以把每一帧图像当作bmp位图
VIDEOINFOHEADER m_VihOut; // Holds the current video format (output)
//the imformation about every picture,put here just for speed
//
DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
int m_Width;//the width of the bitmap,to use it ,just handy
int m_Height;//the height of the bitmap
};
#endif
3。ToGrayFilter.cpp
// ToGrayFilter.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
//the include file for directshow filter
//
#include "streams.h" //用到了filter的基类,就要包含这个头文件的
#include <initguid.h> //
#include <tchar.h>
#include <stdio.h>
#include "ToGrayFilter.h"
#pragma warning(disable:4715)
//--------------------------------------------------------------------
CToGrayFilter:: CToGrayFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *phr):
CTransformFilter(pName, pUnk, CLSID_ToGrayFilter)
{
}
CToGrayFilter::~CToGrayFilter()
{
}
//------------------------------------------------------------------------------
//
//this method check this filter's input pin could receive which kind of media type
//
HRESULT CToGrayFilter::CheckInputType(const CMediaType *pmtIn)
{
if ((pmtIn->majortype != MEDIATYPE_Video) ||
(pmtIn->subtype != MEDIASUBTYPE_RGB24) ||
(pmtIn->formattype != FORMAT_VideoInfo) ||
(pmtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(pmtIn->pbFormat);
// Everything is good.
return S_OK;
}
//the downstream filter check this filter's output pin ,then the method will be used
//now because the media type has not been changed ,so only return the media type of
//this filter's input pin
//
HRESULT CToGrayFilter::GetMediaType(int iPosition, CMediaType *pMediaType)
{
// The output pin calls this method only if the input pin is connected.
ASSERT(m_pInput->IsConnected());
// There is only one output type that we want, which is the input type.
if (iPosition < 0)
{
return E_INVALIDARG;
}
else if (iPosition == 0)
{
//this maybe OK now
return m_pInput->ConnectionMediaType(pMediaType);
}
return VFW_S_NO_MORE_ITEMS;
}
//this method checks if a proposed output type is compatible with the current input type.
//The method is also called if the input pin reconnects after the output pin connects.
//
HRESULT CToGrayFilter::CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut)
{
// Check the major type.
//
if (mtOut->majortype != MEDIATYPE_Video)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
// Check the subtype and format type.
//
// Make sure the subtypes match
//
if (mtIn->subtype != mtOut->subtype)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
if ((mtOut->formattype != FORMAT_VideoInfo) ||
(mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
// Compare the bitmap information against the input type.
//
ASSERT(mtIn->formattype == FORMAT_VideoInfo);
BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);
if ((pBmiOut->biWidth <= pBmiIn->biWidth) &&
(pBmiOut->biHeight == abs(pBmiIn->biHeight)))
{
return S_OK;
}
return VFW_E_TYPE_NOT_ACCEPTED;
}
//this method is used during the output pin connection process.
//the output pin is responsible for negotiating the allocation of data stream buffers
//during the pin connection process,
//even if this allocation is actually done by the input pin of the downstream filter.
//
HRESULT CToGrayFilter::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProp)
{
// Make sure the input pin is connected.
//
if (!m_pInput->IsConnected())
{
return E_UNEXPECTED;
}
// Our strategy here is to use the upstream allocator as the guideline,
// but also defer to the downstream filter's request
// when it's compatible with us.
// First, find the upstream allocator...
ALLOCATOR_PROPERTIES InputProps;
IMemAllocator *pAllocInput = 0;
HRESULT hr = m_pInput->GetAllocator(&pAllocInput);
if (FAILED(hr))
{
return hr;
}
// ...now get the properties.
hr = pAllocInput->GetProperties(&InputProps);
pAllocInput->Release();
if (FAILED(hr))
{
return hr;
}
// Buffer alignment should be non-zero [zero alignment makes no sense!].
if (pProp->cbAlign == 0)
{
pProp->cbAlign = 1;
}
// Number of buffers must be non-zero.
if (pProp->cbBuffer == 0)
{
pProp->cBuffers = 1;
}
// For buffer size, find the maximum of the upstream size and
// the downstream filter's request.
pProp->cbBuffer = max(InputProps.cbBuffer, pProp->cbBuffer);
// Now set the properties on the allocator that was given to us.
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProp, &Actual);
if (FAILED(hr))
{
return hr;
}
}
HRESULT CToGrayFilter::Transform(IMediaSample *pSource,IMediaSample *pDest)
{
// Get pointers to the underlying buffers.
//
BYTE *pBufferIn, *pBufferOut;
HRESULT hr;
hr = pSource->GetPointer(&pBufferIn);
if (FAILED(hr))
{
return hr;
}
hr = pDest->GetPointer(&pBufferOut);
if (FAILED(hr))
{
return hr;
}
// Process the data.
//
ToGray(pBufferIn,pBufferOut);
}
//------------------------------------------------------------------------------
//this method is also in the base filter class,but in the base class it does noting,
//implement here just want to get m_VihIn and m_VihOut
//
HRESULT CToGrayFilter::SetMediaType(PIN_DIRECTION direction,
const CMediaType *pmt)
{
if (direction == PINDIR_INPUT)
{
ASSERT(pmt->formattype == FORMAT_VideoInfo);
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmt->pbFormat;
// WARNING! In general you cannot just copy a VIDEOINFOHEADER
// struct, because the BITMAPINFOHEADER member may be followed by
// random amounts of palette entries or color masks. (See VIDEOINFO
// structure in the DShow SDK docs.) Here it's OK because we just
// want the information that's in the VIDEOINFOHEADER struct itself.
CopyMemory(&m_VihIn, pVih, sizeof(VIDEOINFOHEADER));
}
else // Output pin
{
ASSERT(direction == PINDIR_OUTPUT);
ASSERT(pmt->formattype == FORMAT_VideoInfo);
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmt->pbFormat;
CopyMemory(&m_VihOut, pVih, sizeof(VIDEOINFOHEADER));
}
//------------------------------------------------
//
// DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
//int m_Width;//the width of the bitmap,to use it ,just handy
//int m_Height;//the height of the bitmap
m_Width=m_VihIn.bmiHeader.biWidth;
m_Height=m_VihIn.bmiHeader.biHeight;
m_bytePerLine=(24*m_Width+31)/32*4;
return S_OK;
}
//----------------------------------------------------------------------------------
//my own process method ,to change the data to gray
//
HRESULT CToGrayFilter::ToGray(BYTE *pbInput,BYTE *pbOutput)
{
DWORD dwWidth, dwHeight; // Width and height in pixels (input)
DWORD dwWidthOut, dwHeightOut; // Width and height in pixels (output)
LONG lStrideIn, lStrideOut; // Stride in bytes
BYTE *pbSource, *pbTarget; // First byte first row, source & target
//下面这一段都没有用了,因为感觉这个函数比较古怪,最后是用自己的处理bmp位图的方法得到数据的
//GetVideoInfoParameters(&m_VihIn, pbInput,false, &dwWidth, &dwHeight,
// &lStrideIn, &pbSource);
// GetVideoInfoParameters(&m_VihOut, pbOutput,false, &dwWidthOut, &dwHeightOut,
// &lStrideOut, &pbTarget);
// Formats should match (except maybe stride).
// ASSERT(dwWidth == dwWidthOut);
//ASSERT(abs(dwHeight) == abs(dwHeightOut));
//here to process the data
//
/*for (DWORD y = 0; y < dwHeight; y++)
{
WORD *pwTarget = (WORD*)pbTarget;
WORD *pwSource = (WORD*)pbSource;
//下面是对RGB32的处理,所以,对于RGB24如果这样做,会有问题的
//RGBQUAD *pPixelTarget = (RGBQUAD*)pbTarget;
//RGBQUAD *pPixelSource = (RGBQUAD*)pbSource;
// for (DWORD x = 0; x < dwWidth; x++)
// {
// BYTE grayColor;
// grayColor=
// (pPixelSource[x].rgbBlue+pPixelSource[x].rgbGreen+pPixelSource[x].rgbRed)/3;
// // pPixelTarget[x] is the x'th pixel in the row.
// pPixelTarget[x].rgbBlue = pPixelSource[x].rgbBlue;
// pPixelTarget[x].rgbGreen = pPixelSource[x].rgbGreen;
// pPixelTarget[x].rgbRed = pPixelSource[x].rgbRed;
// pPixelTarget[x].rgbReserved = 0;
// }
}*/
//want to get the imformation for myself
//
//也可以在这里,当每一帧图像来了的时候,都重新计算偏移等等,但是没有必要了,而且会影响速度
//DWORD m_bytePerLine;//the real numbers of bits in one line,just for handy
//int m_Width;//the width of the bitmap,to use it ,just handy
//int m_Height;//the height of the bitmap
//m_Width=m_VihIn.bmiHeader.biWidth;
//m_Height=m_VihIn.bmiHeader.biHeight;
//m_bytePerLine=(24*m_Width+31)/32*4;
BYTE rColor,gColor,bColor,changeColor;
for (int i=0;i<m_Height;i++)
{
for (int j=0;j<m_Width;j++)
{
rColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3);
gColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3+1);
bColor=*(pbInput+(m_Height-i-1)*m_bytePerLine+j*3+2);
changeColor=(rColor+gColor+bColor)/3;
*(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3)=changeColor;
*(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3+1)=changeColor;
*(pbOutput+(m_Height-i-1)*m_bytePerLine+j*3+2)=changeColor;
}
}
return S_OK;
}
//下面这个函数,是从帮助文档中copy的函数,不过感觉没有什么用,实际上在我写的这个filter中也没有用 上
//----------------------------------------------------------------------------
//a helper function ,not write by me,just copy from the help docoment
//
void CToGrayFilter::GetVideoInfoParameters(
const VIDEOINFOHEADER *pvih, // Pointer to the format header.
BYTE * const pbData, // Pointer to the first address in the buffer.
bool bYuv, // Is this a YUV format? (true = YUV, false = RGB)
DWORD *pdwWidth, // Returns the width in pixels.
DWORD *pdwHeight, // Returns the height in pixels.
LONG *plStrideInBytes, // Add this to a row to get the new row down.
BYTE **ppbTop // Returns a pointer to the first byte in the
// top row of pixels.
)
{
LONG lStride;
// For 'normal' formats, biWidth is in pixels.
// Expand to bytes and round up to a multiple of 4.
if ((pvih->bmiHeader.biBitCount != 0) &&
(0 == (7 & pvih->bmiHeader.biBitCount)))
{
lStride = (pvih->bmiHeader.biWidth * (pvih->bmiHeader.biBitCount / 8) + 3) & ~3;
}
else // Otherwise, biWidth is in bytes.
{
lStride = pvih->bmiHeader.biWidth;
}
// If rcTarget is empty, use the whole image.
if (IsRectEmpty(&pvih->rcTarget))
{
*pdwWidth = (DWORD)pvih->bmiHeader.biWidth;
*pdwHeight = (DWORD)(abs(pvih->bmiHeader.biHeight));
if (pvih->bmiHeader.biHeight < 0 || bYuv) // Top-down bitmap.
{
*plStrideInBytes = lStride; // Stride goes "down".
*ppbTop = pbData; // Top row is first.
}
else // Bottom-up bitmap.
{
*plStrideInBytes = -lStride; // Stride goes "up".
// Bottom row is first.
*ppbTop = pbData + lStride * (*pdwHeight - 1);
}
}
else // rcTarget is NOT empty. Use a sub-rectangle in the image.
{
*pdwWidth = (DWORD)(pvih->rcTarget.right - pvih->rcTarget.left);
*pdwHeight = (DWORD)(pvih->rcTarget.bottom - pvih->rcTarget.top);
if (pvih->bmiHeader.biHeight < 0 || bYuv) // Top-down bitmap.
{
// Same stride as above, but first pixel is modified down
// and over by the target rectangle.
*plStrideInBytes = lStride;
*ppbTop = pbData +
lStride * pvih->rcTarget.top +
(pvih->bmiHeader.biBitCount * pvih->rcTarget.left) / 8;
}
else // Bottom-up bitmap.
{
*plStrideInBytes = -lStride;
*ppbTop = pbData +
lStride * (pvih->bmiHeader.biHeight - pvih->rcTarget.top - 1) +
(pvih->bmiHeader.biBitCount * pvih->rcTarget.left) / 8;
}
}
}
//-----------------------------------------------------------------------------------------------
//
CUnknown* WINAPI CToGrayFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
{
CToGrayFilter *pFilter = new CToGrayFilter(NAME("To Gray Filter"), pUnk, pHr);
if (pFilter== NULL)
{
*pHr = E_OUTOFMEMORY;
}
return pFilter;
}
//the following three method maybe the same for most of the filters
//
extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE ,DWORD,LPVOID );
BOOL APIENTRY DllMain(HANDLE hModule,DWORD dwRes,LPVOID pv)
{
return DllEntryPoint((HINSTANCE)hModule,dwRes,pv);
}
HRESULT WINAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
HRESULT WINAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
//---------------------------------------------------
//
AMOVIESETUP_FILTER FilterInfo =
{
&CLSID_ToGrayFilter, // CLSID
L"To Gray Filter", // Name
MERIT_DO_NOT_USE, // Merit
0, // Number of AMOVIESETUP_PIN structs
NULL // Pin registration information
};
CFactoryTemplate g_Templates[]={
{
L"To Gray Filter",
&CLSID_ToGrayFilter,
CToGrayFilter::CreateInstance,
0,
&FilterInfo,
}
};
int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]);
上面就是我的程序的所有源代码。这只是一个示例的程序而已,里面有好多东西,其实将来写从CTransformFilter继承的transform filter是完全可以重复使用的,也就是里面的很多东西都差不多都会是这样。代码简单,真正自己的实现只是一个函数而已,里面只是得到像素的值,然后进行转换。也就是说,transformfilter有一个架子,不同的只是对像素的操作而已。
现在匆匆收场了,其实还有好多没有提到的地方,但是现在心情突然不好了,以后再补充吧。