标签:
杂谈 |
分类: DirectShow |
摘要:本篇文档主要讲述了Directshow开发的一些基本概念和技巧
1视频播放(Video Rendering)
dshow的视频提交过滤器可以在窗口模式和无窗口模式下工作。在窗口模式下,过滤器创建一个自己的窗口,在里面播放视频。在无窗口模式下,过滤器直接将视频在应用程序提供的窗口上显示,过滤器本身不创建窗口。
窗口模式
在窗口模式下,视频提交过滤器创建一个窗口,然后将视频祯帖到窗口上,你可以将这个窗口帖到你的应用程序的窗口。
为了在你的应用程序中显示视频,你可以将视频窗口设置成应用程序的子窗口。你可以通过
IVideoWindow *pVidWin = NULL;
pGraph->QueryInterface(IID_IVideoWindow, (void **)&g_pVidWin);
pVidWin->put_Owner((OAHWND)hwnd);
pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
RECT grc;
GetClientRect(hwnd, &grc);
pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);
结束时一定要清理现场
pControl->Stop();
pVidWin->put_Visible(OAFALSE);
pVidWin->put_Owner(NULL);
无窗口模式
当采用无窗口的模式时,就没有必要暴露IVideoWindow接口了。
为了能够使用VMR的缺省行为,在构建Graph图之前必须要调整VMR。
1 创建一个过虑器图表管理器,
2创建一个VMR,加入到graph中,
3 调用VMR的IVMRFilterConfig::SetRenderingMode方法设置VMRMode_Windowless标志。
4调用IVMRWindowlessControl::SetVideoClippingWindow 给视频指定一个显示窗口。
然后调用IGraphBuilder::RenderFile或者其他的方法来创建其他的Graph。
下面的代码显示了如何创建一个VMR,将其添加到Graph,如何设置无窗口模式
HRESULT InitWindowlessVMR(
{
}
你也可以调用下面的函数
IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
if (SUCCEEDED(hr))
{
}
下面看看如何设置视频的位置
有两个矩形需要考虑,一个是源矩形,一个是目的矩形。源矩形决定开始播放视频的位置,目的矩形决定在窗口显示视频的区域。VMR将源矩形按照目的矩形的大小进行扩展。
IVMRWindowlessControl::SetVideoPosition可以设置两个矩形的大小,源矩形必须小于等于本地视频大小。你可以通过IVMRWindowlessControl::GetNativeVideoSize获取本地的视频区域大小。
// Find the native video size.
long lWidth, lHeight;
HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);
if (SUCCEEDED(hr))
{
}
处理窗口消息
因为VMR没有自己的窗口,所以当视频需要重画或者改变的时候你要通知它。
1 当你接到一个WM_PAINT消息,你就要调用IVMRWindowlessControl::RepaintVideo来重画视频
2 当你接到一个WM_DISPLAYCHANGE消息,你就要调用IVMRWindowlessControl::DisplayModeChanged.
3 当你接到一个WM_SIZE消息时,重新计算视频的位置,然后调用SetVideoPostion。
下面的代码演示了WM_PAINT消息的处理
void OnPaint(HWND hwnd)
{
}
尽管我们要自己处理onpaint消息,但是已经非常简单了。
2 如何处理事件通知(Event Notification)
Filter发送的这些事件,其中的一部分可以被Manager直接处理,不通知应用程序,但有一部分事件,Manager将事件放入到一个队列中,等待应用程序处理。这里我们主要讨论在应用程序中经常遇到的三种事件
EC_COMPLETE表明回放已经结束
EC_USERABORT表明用户中断了回放。用户关闭视频播放窗口时,视频Render会发生这个事件
EC_ERRORABORT表明出现了一个错误。
应用程序可以通知filter graph manager,在某个指定的事件发生时,向指定的窗口发生一个指定的消息。这样应用程序就可以在消息循环中对发生的事件产生反应。
#define WM_GRAPHNOTIFY
IMediaEventEx *g_pEvent = NULL;
g_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&g_pEvent);
g_pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
然后在WindowProc函数增加一个处理WM_GRAPHNOTIFY消息的函数
case WM_GRAPHNOTIFY:
HandleGraphEvent()函数具体定义如下
void HandleGraphEvent()
{
}
在释放IMediaEventEx指针前,要取消事件通知消息,代码如下
// Disable event notification before releasing the graph.
g_pEvent->SetNotifyWindow(NULL, 0, 0);
g_pEvent->Release();
g_pEvent = NULL;
3如何枚举系统的设备和过虑器
1 系统设备枚举器
系统设备枚举器提供了一个很好的方法根据种类来枚举系统中注册的过虑器。也许枚一种不同的硬件都会有自己的过虑器,或许所有的硬件设备共用同一个filter。这个对于采用WDM驱动程序的硬件很有用。
系统设备枚举器根据不同的种类创建了一个枚举器,例如,音频压缩,视频捕捉。不同种类的枚举器对于每一种设备返回一个独立的名称(moniker)。种类枚举器自动将相关的即插即用,演播设备包括进来。
按照下面的步骤使用设备枚举器
1 创建枚举器组件,CLSID为CLSID_SystemDeviceEnum
2 指定某一种类型设备,参数CLSID,通过ICreateDevEnum::CreateClassEnumerator获取某一种类的枚举器,这个函数返回一个IEnumMoniker接口指针,如果该种类的空或者不存在,这个方法就返回S_FALSE。因此,当你调用这个函数时一定要检查返回值是否为S_OK,而不要用SUCCEEDED宏。
3 然后IEnumMoniker::Next枚举每一个moniker。这个方法返回一个IMoniker接口指针。
4 要想知道设备的名称,可以通过下面的函数IMoniker::BindToStorage
5 然后利用IMoniker::BindToObject生成绑定道设备上的filter。调用IFilterGraph::AddFilter将filter添加到Graph图中。
// Create the System Device Enumerator.
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
if (FAILED(hr))
{
}
// Obtain a class enumerator for the video compressor category.
IEnumMoniker *pEnumCat = NULL;
hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0);
if (hr == S_OK)
{
}
pSysDevEnum->Release();
在上面我们用IMoniker::BindToObject生成绑定道设备上的filter,当然我们还可以用另外的一种方法来生成绑定到设备上的filter
利用IMoniker::GetDisplayName得到moniker的名字。然后你把moniker的名字做参数传递给IFilterGraph2::AddSourceFilterForMonike
LPOLESTR strName = NULL;
IBaseFilter pSrc = NULL;
hr = pMoniker->GetDisplayName(NULL, NULL, &strName);
if (SUCCEEDED(hr))
{
}
// If successful, remember to release pSrc.
2 Filter Mapper
Filter Mapper 暴露一个IFilerMapper2接口,要想搜索一个接口,你可以调用该接口的IFilterMapper2::EnumMatchingFilters方法,这个方法需要传递一些参数来定义搜索条件,同时该方法返回一个适合条件的filter的枚举器,这个枚举器提供一个IEnumMoniker接口,并且对于每个适合的filter都提供一个单独的moniker。
下面的例子演示了,枚举所有的支持DV,并且至少有一个输出pin的filter,这个filter支持任何媒体类型。
IFilterMapper2 *pMapper = NULL;
IEnumMoniker *pEnum = NULL;
hr =CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2,
if (FAILED(hr))
{
}
GUID arrayInTypes[2];
arrayInTypes[0] = MEDIATYPE_Video;
arrayInTypes[1] = MEDIASUBTYPE_dvsd;
hr = pMapper->EnumMatchingFilters(
// Enumerate the monikers.
IMoniker *pMoniker;
ULONG
cFetched;
//////////下面就是枚举filter了,就是系统枚举设备filter
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
}
// Clean up.
pMapper->Release();
pEnum->Release();
4如何枚举Graph图中的对象(filter,pin)
有些时候,应用程序需要枚举graph中的filter或者是枚举filter所支持的pin。因此directshow提供了枚举graph filter中的com组件方法。
1 枚举filter
Filter图表管理器支持IFilterGraph::EnumFilters方法,来枚举graph图中的所有的filter。他返回一个IEnumFilters接口,利用这个接口就可以遍历graph中的所有的filter。
下面的代码演示了,如何遍历graph中的filter,并且显示filter的名字。
HRESULT EnumFilters (IFilterGraph *pGraph)
{
#ifdef UNICODE
#else
#endif
}
2 枚举pin
Filter支持IBaseFilter::EnumPins方法,这个方法可以可以枚举filter所有的pin。它返回一个IEnumPins接口,IEnumPins::Next可以遍历pin的接口。
下面的代码演示了如何如何查找一个输出和输入pin。利用PIN_DIRECTION参数来制定pin的类型(输入还是输出)。
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)
{
}
利用这个方法可以很容易的就查找一个pin,然后调用IPin::ConnectedTo方法确定这个pin是否被连接,可以查找一个空闲的pin。
3 查找媒体类型
每个pin都支持一个IPin::EnumMediaTypes方法,可以来枚举pin支持的媒体类型。它返回一个IEnumMediaTypes接口,这个接口的方法IEnumMediaTypes::Next返回一个指向AM_MEDIA_TYPE类型的指针。可以参考上面的代码来遍历pin所支持的媒体类型。
5 Seeking Filter graph
主要讲述了如何在一个媒体数据流中定位,任意指定开始播放的位置。
1 检查是否支持seek
Directshow通过IMediaSeeking接口支持seeking。Filter graph管理器支持这个接口,但是实际seeking的功能是有graph中的filter来实现的。
有一些数据是不能seek的,例如,你不可能seek从照相机中采集的活动的视频流。如果一个数据流可以被seek,但是,seek的类型还分以下几种类型,可以给你的数据流选择一种
1 定位到数据流中的一个绝对位置
2 返回数据流的持续时间
3返回数据流中的当前播放位置
4回放。
IMediaSeeking接口定义了一套标志AM_SEEKING_SEEKING_CAPABILITIES,用来描述可能支持的seek功能。
typedef enum AM_SEEKING_SeekingCapabilities {
AM_SEEKING_CanSeekAbsolute = 0x1,
AM_SEEKING_CanSeekForwards = 0x2,
AM_SEEKING_CanSeekBackwards = 0x4,
AM_SEEKING_CanGetCurrentPos = 0x8,
AM_SEEKING_CanGetStopPos = 0x10,
AM_SEEKING_CanGetDuration = 0x20,
AM_SEEKING_CanPlayBackwards = 0x40,
AM_SEEKING_CanDoSegments = 0x80,
AM_SEEKING_Source = 0x100
} AM_SEEKING_SEEKING_CAPABILITIES;
可以通过IMediaSeeking::GetCapabilities查看数据流支持的seek能力都有哪些。应用程序可以采取 &测试每一项。例如,下面的代码检查了graph是否可以seek 一个任意的位置
HRESULT hr = pSeek->GetCapabilities(&dwCap);
if (AM_SEEKING_CanSeekAbsolute & dwCap)
{
}
2Setting and Retrieving the Position
#define ONE_SECOND 10000000
REFERENCE_TIME rtNow
hr = pSeek->SetPositions(
注:1秒是10,000,000参考时间单位。为了方便,这个例子将这个值定义为ONE_SECOND,如果你使用的dshow的基类,常量CUITS的值和这个值相等。
为了设置这个位置不能改变,可以指定一个AM_SEEKING_NoPositioning参数。此时,参考时间应该设置为NULL。下面的例子将位置向前seek 10秒,然后停止位置不变。
REFERENCE_TIME rtNow = 10 * ONE_SECOND;
hr = pSeek->SetPositions(
3Setting the Playback Rate
调用IMediaSeeking::SetRate方法可以改变回放的速率。通过将新的速率设置成原来速率的倍数就可以设置新的速率,例如,pSeek->SetRate(2.0)
将新的速率设置为原来速率的两倍。比率大于1说明回放的速度比原来的大,如果介于0和1之间,就比正常的速度慢。
4Time Formats For Seek Commands
IMediaSeeking::IsFormatSupported方法,如果filter支持该时间格式,该函数返回ok否则返回false或者一个错误码。如果filter支持某种指定的时间格式,可以调用IMediaSeeking::SetTimeFormat方法切换到其他的时间格式。如果SetTimeFormat方法成功,下面的seek命令就要使用新的时间格式。
hr = pSeek->IsFormatSupported(&TIME_FORMAT_FRAME);
if (hr == S_OK)
{
}
6 如何设置Graph时钟(Setting Graph Clock)
当你构建了一个graph后,graph管理器会自动地给你的graph选择一个参考时钟的。Graph中的所有filter都同步于时钟。特别的,Renderer filter还要根据参考时钟的时间来决定每一个sample的Presentation 时间。
IGraphBuilder *pGraph = 0;
IReferenceClock *pClock = 0;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
// Build the graph.
pGraph->RenderFile(L"C://Example.avi", 0);
// Create your clock.
hr = CreateMyPrivateClock(&pClock);
if (SUCCEEDED(hr))
{
}