转自:http://blog.csdn.net/chenyujing1234
例子代码:(编译工具:VS2005)
http://www.rayfile.com/zh-cn/files/46611607-78a2-11e1-ac18-0015c55db73d/
参考文章:http://blog.csdn.net/rageliu/article/details/621157
虽然网上已有很多关于DirectShow写source filter的资料,不过很多刚开始学的朋友总说讲的不是很清楚(可能其中作者省略了许多他认为简
单的过程),读者总希望看到象第一步怎么做,第二步怎么做....这样的demo。其实写你的第一个filter是有一定难度的,只要过了这关以后
就容易多了。
由于最近需要自己写一个push推模式的source filter,加上刚激活了Blog,不好意思Blog上没有一篇文章,所以将写这个filter的过程写下来
,为了照顾刚开始学的朋友,我采用第一步第二步....这样的方式尽可能的讲解详细,相信你按照这个步骤一定没问题的,对于vc中DirectSho
w开发环境的配置,这里不做讲解。下面开始:
(vc 6.0 + DirectShow 9.0)
我也记得刚学时候的迷茫,所以会尽量详细每个过程,所以很多是sdk的例子我没改动它,没讲的是我提供的源代码里面我加有比较详细的注释
,可以配合我提供的源代码一起看。
第一步:建立工程
File->New->Project选择Win32 Dynamic-Link Library,(由于是个demo,名字我用的Push_Test_01)->Next后选择A simple DLL project(这里
为了避免自己写DllMain的麻烦,所以没选An empty DLL project)->可以Finish了
到这里工程建立结束。
第二步:相关设置和需要加入的文件等操作
首先将Debug方式改为Release。接着Project->Seetings->Link里的Output file
name从Release/Push_Test_01.dll改为Release/Push_Test_01.ax。
在工程目录下建立一个文本文件,修改名字为Push_Test_01.def。将其加入工程:Project->Add to project->Files 选择Push_Test_01.def后
加入。
对Push_Test_01.def进行修改,FileView->Source Files 双击Push_Test_01.def后输入:
- LIBRARY Push_Test_01.ax
- EXPORTS
- DllMain PRIVATE
- DllGetClassObject PRIVATE
- DllCanUnloadNow PRIVATE
- DllRegisterServer PRIVATE
- DllUnregisterServer PRIVATE
确定project->Seetings->link下Object/library modules里面为:
strmbase.lib msvcrt.lib quartz.lib vfw32.lib winmm.lib kernel32.lib advapi32.lib version.lib largeint.lib user32.lib
gdi32.lib comctl32.lib ole32.lib olepro32.lib oleaut32.lib uuid.lib
添加头文件:
#include <streams.h>
#include <olectl.h>
#include <initguid.h>
生成全球唯一标识,这里这样
DEFINE_GUID(CLSID_PushTest,
0xfd501041, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d, 0xa1);
第三步:注册等函数的添加
首先修改入口函数,并添加注册和反注册函数,操作后的内容如下:
- //注册
- STDAPI DllRegisterServer()
- {
- return AMovieDllRegisterServer2(TRUE);
- }
- //反注册
- STDAPI DllUnregisterServer()
- {
- return AMovieDllRegisterServer2(FALSE);
- }
- //filter的入口函数
- extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
- BOOL APIENTRY DllMain(HANDLE hModule,
- DWORD dwReason,
- LPVOID lpReserved)
- {
- return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
- }
此时编译会有class CFactoryTemplate没实现等错误,下面我们来实现它。
添加下面的代码,每个地方我基本都加了大体意思的注释:
- // 媒体类型 Filter setup data
- const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
- {
- &MEDIATYPE_Video, // Major type 主类型
- &MEDIASUBTYPE_NULL // Minor type sub类型
- };
- // pin信息
- const AMOVIESETUP_PIN sudOutputPinBitmap =
- {
- L"Output", // Obsolete, not used. pin 名字
- FALSE, // Is this pin rendered? 输入pin有用,输出pin一般为FALSE
- TRUE, // Is it an output pin? TRUE表示是输出pin,不然是输入pin
- FALSE, // Can the filter create zero instances? 是否能不实例化
- FALSE, // Does the filter create multiple instances?是否能创建多个同这样类型的pin
- &CLSID_NULL, // Obsolete. 连接的filter类
- NULL, // Obsolete. 该pin要连接的pin的类
- 1, // Number of media types. 该pin支持的媒体类型
- &sudOpPinTypes // Pointer to media types.该pin的媒体类型的描述
- };
- const AMOVIESETUP_FILTER sudPushSourceBitmap =
- {
- &CLSID_PushSourceBitmap,// Filter CLSID 该filter的类标志
- g_wszPushBitmap, // String name 该filter的名字
- MERIT_DO_NOT_USE, // Filter merit 该filter的Merit值
- 1, // Number pins 该filter的pin的数目
- &sudOutputPinBitmap // Pin details 该filter的pin的描述
- };
- CFactoryTemplate g_Templates[3] =
- {
- {
- g_wszPushBitmap, // Name filter的名字
- &CLSID_PushSourceBitmap, // CLSID 对象的类标识
- CPushSourceBitmap::CreateInstance, // Method to create an instance of MyComponent 创建一个实例用的函数
- NULL, // Initialization function
- &sudPushSourceBitmap // Set-up information (for filters)filter的注册信息
- },
- {
- g_wszPushBitmapSet, // Name
- &CLSID_PushSourceBitmapSet, // CLSID
- CPushSourceBitmapSet::CreateInstance, // Method to create an instance of MyComponent
- NULL, // Initialization function
- &sudPushSourceBitmapSet // Set-up information (for filters)
- },
- {
- g_wszPushDesktop, // Name
- &CLSID_PushSourceDesktop, // CLSID
- CPushSourceDesktop::CreateInstance, // Method to create an instance of MyComponent
- NULL, // Initialization function
- &sudPushSourceDesktop // Set-up information (for filters)
- },
- };
- int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
通过上面的注释,我们看到里面定义有三个Filter,名字分别为:
- #define g_wszPushBitmap L"PushSource Bitmap Filter"
- #define g_wszPushBitmapSet L"PushSource BitmapSet Filter"
- #define g_wszPushDesktop L"PushSource Desktop Filter"
在第一个Filter 的注册信息 sudPushSourceBitmap 可以看出它有一个pin,且该pin被描述为sudOutputPinBitmap:
这个pin是个输出pin,且支持Video类型等等信息,不多说了。
这里主要对CPushSourceBitmap::CreateInstance//创建一个实例用的函数
说明一下!!CPushSourceBitmap就是我们的filter类!!在下面实现它。
第四步:filter类的实现(只讲CPushSourceBitmap,其它两个filter(CPushSourceBitmapSet、CPushSourceDesktop)类同)
添加新类CPushSourceBitmap,使其继承自CSource。这就是我们的filter类,在这个类里面没有过多的操作:
- // filter的主类,继承自CSource
- class CPushSourceBitmap : public CSource
- {
- private:
- // 只能通过CreateInstance()的调用创建实例
- CPushSourceBitmap(IUnknown *pUnk, HRESULT *phr);
- ~CPushSourceBitmap();
- CPushPinBitmap *m_pPin;
- public:
- // 唯一能创建该类实例的接口
- static CUnknown * WINAPI CreateInstance(IUnknown *pUnk, HRESULT *phr);
- };
这里有2点需要注意:
构造函数CPushSourceBitmap()是private的,不是一般的public!!!!!!!!!!!
CreateInstance()函数是static的,因为它不能通过对象来调用!!!!
2个函数的具体实现如下:
//构造函数,注意这里是private属性的,不是public,
//所以要创建它的实例,只能是通过CreateInstance()函数的方式
- CPushSourceBitmap::CPushSourceBitmap(IUnknown *pUnk, HRESULT *phr)
- : CSource(NAME("PushSourceBitmap"), pUnk, CLSID_PushSourceBitmap)
- {
- //为类中的m_pPin空间付值,这就自动给filter加入了一个pin,析构的
- //时候会自动释放
- m_pPin = new CPushPinBitmap(phr, this);
- if (phr)
- {
- if (m_pPin == NULL)
- *phr = E_OUTOFMEMORY;
- else
- *phr = S_OK;
- }
- }
//CreateInstance()该函数是static属性的,因为不能通过对象来调用
- CUnknown * WINAPI CPushSourceBitmap::CreateInstance(IUnknown *pUnk, HRESULT *phr)
- {
- // 这里调用了private属性的构造函数
- CPushSourceBitmap *pNewFilter = new CPushSourceBitmap(pUnk, phr );
- if (phr)
- {
- if (pNewFilter == NULL)
- *phr = E_OUTOFMEMORY;
- else
- *phr = S_OK;
- }
- return pNewFilter;
- }
这里的类CPushPinBitmap就是我们的pin类,在后面要实现!!其实主要的操作是在pin类CPushPinBitmap里面的。
第五步:pin类的实现(只讲CPushPinBitmap,其它两个Pin(CPushPinBitmapSet、CPushPinDesktop)类同)
添加类CPushPinBitmap,使其继承自CSourceStream。这里需要重载的函数会多一点!不过没关系!我会一个一
个的进行说明。
主要是这3个(原因请参考我的另一篇文章 <<Filter组件开发中的SDK基类分析 >>):
- // 由于我们的filter就一种媒体类型,所以重载了GetMediaType(CMediaType *pMediaType)
- // 如果有多种类型,就应该重载另外2个函数了,具体参考基类CSourceStream
- HRESULT GetMediaType(CMediaType *pMediaType);
- // 这个函数是用来设置Sample大小的,在pin连接成功后会被调用
- HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
- // 对Sample数据的填充
- HRESULT FillBuffer(IMediaSample *pSample);
CPushPinBitmap这个Pin例子简介:
主要的点是演示如何获得主memory中的DIB体,并插入到video流中,
为了让例子尽可能的简单,我们仅从文件中读一个单独的24 bpp bitmap,
并把它复制到我们发送的每一帧中.
- CPushPinBitmap::CPushPinBitmap(HRESULT *phr, CSource *pFilter)
- : CSourceStream(NAME("Push Source Bitmap"), phr, pFilter, L"Out"),
- m_FramesWritten(0),
- m_bZeroMemory(0),
- m_pBmi(0),
- m_cbBitmapInfo(0),
- m_hFile(INVALID_HANDLE_VALUE),
- m_pFile(NULL),
- m_pImage(NULL),
- m_iFrameNumber(0),
- m_rtFrameLength(FPS_5) // Display 5 bitmap frames per second
- {
- // 此例子主要的点是演示如何获得主memory中的DIB体,并插入到video流中.
- // 为了让例子尽可能的简单,我们仅从文件中读一个单独的24 bpp bitmap,
- // 并把它复制到我们发送的每一帧中.
- // 在 filter graph 中, 我们把filter连接到AVI Mux(它用我传递过来的video帧创建了AVI文件),
- // 这样,数据的结果就是一个静态的已纹理的video流.
- // Your filter will hopefully do something more interesting here.
- // The main point is to set up a buffer containing the DIB pixel bits.
- // This must be done before you start running.
- TCHAR szCurrentDir[MAX_PATH], szFileCurrent[MAX_PATH], szFileMedia[MAX_PATH];
- // 首先查找bitmap 在哪个目录里
- GetCurrentDirectory(MAX_PATH-1, szCurrentDir);
- wsprintf(szFileCurrent, TEXT("%s\\%s\0"), szCurrentDir, BITMAP_NAME);
- m_hFile = CreateFile(szFileCurrent, GENERIC_READ, 0, NULL, OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL, NULL);
- if (m_hFile == INVALID_HANDLE_VALUE)
- {
- // File was not in the application's current directory,
- // so look in the DirectX SDK media path instead.
- lstrcpyn(szFileMedia, DXUtil_GetDXSDKMediaPath(), MAX_PATH-1);
- lstrcat(szFileMedia, BITMAP_NAME);
- m_hFile = CreateFile(szFileMedia, GENERIC_READ, 0, NULL, OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL, NULL);
- if (m_hFile == INVALID_HANDLE_VALUE)
- {
- TCHAR szMsg[MAX_PATH + MAX_PATH + 100];
- wsprintf(szMsg, TEXT("Could not open bitmap source file in the application directory:\r\n\r\n\t[%s]\n\n")
- TEXT("or in the DirectX SDK Media folder:\r\n\r\n\t[%s]\n\n")
- TEXT("Please copy this file either to the application's folder\r\n")
- TEXT("or to the DirectX SDK Media folder, then recreate this filter.\r\n")
- TEXT("Otherwise, you will not be able to render the output pin.\0"),
- szFileCurrent, szFileMedia);
- OutputDebugString(szMsg);
- MessageBox(NULL, szMsg, TEXT("PushSource filter error"), MB_ICONERROR | MB_OK);
- *phr = HRESULT_FROM_WIN32(GetLastError());
- return;
- }
- }
- DWORD dwFileSize = GetFileSize(m_hFile, NULL);
- if (dwFileSize == INVALID_FILE_SIZE)
- {
- DbgLog((LOG_TRACE, 1, TEXT("Invalid file size")));
- *phr = HRESULT_FROM_WIN32(GetLastError());
- return;
- }
- m_pFile = new BYTE[dwFileSize];
- if(!m_pFile)
- {
- OutputDebugString(TEXT("Could not allocate m_pImage\n"));
- *phr = E_OUTOFMEMORY;
- return;
- }
- DWORD nBytesRead = 0;
- if(!ReadFile(m_hFile, m_pFile, dwFileSize, &nBytesRead, NULL))
- {
- *phr = HRESULT_FROM_WIN32(GetLastError());
- OutputDebugString(TEXT("ReadFile failed\n"));
- return;
- }
- // WARNING - This code does not verify that the file is a valid bitmap file.
- // In your own filter, you would check this or else generate the bitmaps
- // yourself in memory.
- int cbFileHeader = sizeof(BITMAPFILEHEADER);
- // 存储 BITMAPINFO 大小
- BITMAPFILEHEADER *pBm = (BITMAPFILEHEADER*)m_pFile;
- m_cbBitmapInfo = pBm->bfOffBits - cbFileHeader;
- // 存储指向BITMAPINFO的指针
- m_pBmi = (BITMAPINFO*)(m_pFile + cbFileHeader);
- // 存储指向像素bits开始的指针
- m_pImage = m_pFile + cbFileHeader + m_cbBitmapInfo;
- // Close and invalidate the file handle, since we have copied its bitmap data
- CloseHandle(m_hFile);
- m_hFile = INVALID_HANDLE_VALUE;
- }
GetMediaType 从头信息中的subtype算出GUID,并提供输出Pin上的首选媒体类型
- HRESULT CPushPinBitmap::GetMediaType(CMediaType *pMediaType)
- {
- CAutoLock cAutoLock(m_pFilter->pStateLock());
- CheckPointer(pMediaType, E_POINTER);
- // If the bitmap file was not loaded, just fail here.
- if (!m_pImage)
- return E_FAIL;
- // 为VIDEOINFOHEADER和颜色表分配足够的空间
- VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(SIZE_PREHEADER + m_cbBitmapInfo);
- if (pvi == 0)
- return(E_OUTOFMEMORY);
- ZeroMemory(pvi, pMediaType->cbFormat);
- pvi->AvgTimePerFrame = m_rtFrameLength;
- // 复制头信息
- memcpy(&(pvi->bmiHeader), m_pBmi, m_cbBitmapInfo);
- // 为FillBuffer 中的使用而设置image 的大小
- pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);
- // 清除source和target的区域
- SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered
- SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle
- pMediaType->SetType(&MEDIATYPE_Video);
- pMediaType->SetFormatType(&FORMAT_VideoInfo);
- pMediaType->SetTemporalCompression(FALSE);
- // 从头信息中的subtype算出GUID
- const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
- pMediaType->SetSubtype(&SubTypeGUID);
- pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);
- return S_OK;
- }
- HRESULT CPushPinBitmap::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest)
- {
- HRESULT hr;
- CAutoLock cAutoLock(m_pFilter->pStateLock());
- CheckPointer(pAlloc, E_POINTER);
- CheckPointer(pRequest, E_POINTER);
- // If the bitmap file was not loaded, just fail here.
- if (!m_pImage)
- return E_FAIL;
- VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*) m_mt.Format();
- // Ensure a minimum number of buffers
- if (pRequest->cBuffers == 0)
- {
- pRequest->cBuffers = 2;
- }
- // 在GetMediaType中给pvi->bmiHeader初始了数据
- pRequest->cbBuffer = pvi->bmiHeader.biSizeImage;
- ALLOCATOR_PROPERTIES Actual;
- hr = pAlloc->SetProperties(pRequest, &Actual);
- if (FAILED(hr))
- {
- return hr;
- }
- // Is this allocator unsuitable?
- if (Actual.cbBuffer < pRequest->cbBuffer)
- {
- return E_FAIL;
- }
- return S_OK;
- }
FillBuffer 把DIB bit复制到filter的输出buffer中.
- HRESULT CPushPinBitmap::FillBuffer(IMediaSample *pSample)
- {
- BYTE *pData;
- long cbData;
- CheckPointer(pSample, E_POINTER);
- // If the bitmap file was not loaded, just fail here.
- if (!m_pImage)
- return E_FAIL;
- CAutoLock cAutoLockShared(&m_cSharedState);
- // 用pData变量访问sample的数据buffer
- pSample->GetPointer(&pData);
- cbData = pSample->GetSize();
- // 检查我们是用静态的video
- ASSERT(m_mt.formattype == FORMAT_VideoInfo);
- VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)m_mt.pbFormat;
- // If we want to change the contents of our source buffer (m_pImage)
- // at some interval or based on some condition, this is where to do it.
- // Remember that the new data has the same format that we specified in GetMediaType.
- // For example:
- // if(m_iFrameNumber > SomeValue)
- // LoadNewBitsIntoBuffer(m_pImage)
- // 把DIB bit复制到filter的输出buffer中.
- // 因为单采样的大小可能比image大小大,所以限定大小.
- memcpy(pData, m_pImage, min(pVih->bmiHeader.biSizeImage, (DWORD) cbData));
- // Set the timestamps that will govern playback frame rate.
- // If this file is getting written out as an AVI,
- // then you'll also need to configure the AVI Mux filter to
- // set the Average Time Per Frame for the AVI Header.
- // The current time is the sample's start
- REFERENCE_TIME rtStart = m_iFrameNumber * m_rtFrameLength;
- REFERENCE_TIME rtStop = rtStart + m_rtFrameLength;
- pSample->SetTime(&rtStart, &rtStop);
- m_iFrameNumber++;
- // Set TRUE on every sample for uncompressed frames
- pSample->SetSyncPoint(TRUE);
- return S_OK;
- }
==============================================================================================================================
VS2005下的编译时报以下错误:
- error LNK1103: 调试信息损坏;请重新编译模块 strmbasd.lib
原因是因为我的strmbasd.lib是用VS2008编译的,而现在的环境是VS2005。版本不对应。
改为对应,问题就解决了
===============================================================================================================================
总结:
在《DircctShow开发指南》一书中有下面一段话:
DirectShow为局部存储器传输定义了两种机制:推模式(push model)和拉模式(pull model)。在推模式中,源过滤器生成数据并提交给下一级过滤器。下一级过滤器被动的接收数据,完成处理后再传送给再下一级过滤器。在拉模式中,源过滤器与一个分析过滤器相连。分析过滤器向源过滤器请求数据后,源过滤器才传送数据以响应请求。推模式使用的是IMemInputPin接口,拉模式使用IAsyncReader接口,推模式比拉模式要更常用。
大家可以比较我的两篇文章:
DX90SDK SDK源码分析(二) 推模式的例子
DX90SDK SDK源码分析(一) 拉模式的例子 http://blog.csdn.net/chenyujing1234/article/details/7378315
就可以发现上面这段话的内容了.