转载请标明是引用于 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类!!在下面实现它。
添加新类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里面的。
添加类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
就可以发现上面这段话的内容了.