写这篇笔记,就当是对这个期间学习DirectShow的一个总结,假如它可以给你带来一些收获的话,那我会感到更高兴。还有我遇到的一些问题,希望和大家一起讨论。
一、基本概念
1.DirectShow概述
DirectShow的主要功能是流媒体的采集与回放。
它集成了DirectDraw,DirectSound,Direct3D的一些技术。
它是一个开放的架构,你自己可以写自己的Filter组件。
DirectShow的架构:(这个图太经典了)
2.Filter
Filter其实就是一个COM组件,但它有自己的特性。它由输入,处理,输出三部分组成,
输入,输出是叫PIN,输入的叫输入pin,输出的叫输出PIN。PIN也是一.种COM组件,它实现了IPin接口。
在Filter的实现里,PIN其实是Filter的一个数据成员。(个人想法)
(1)Filter分为三类:Source Filter(源过滤器),Transform Filter(转换过滤器) Render Filter(渲染过滤器)
(2)Source
Filter 的主要互责音、视频数据的采集,读取。比如File Source Filter(文件源过滤器),它是从磁盘里的音视文件读取数据的。
Transform Filter 互责音、视数据的压缩,解压,编码、解码、分析。
比如:Spliiter Filter(分离器),其功能是将音频流和视频流分离开来
Mux Filter(复合器),将音频流和视频流 全成单一数据流
还有一此Encoder,Decoder,Compressor等。
(3)Render Filter 互责音、视流的渲染,就是和输出,它将数据流送到输出设备,(显示器、磁盘、声卡)。
Filter有三种状态,运行,暂停,停止。
关于数据流:
3.Filter Graph(过滤器图)
Filter Graph是DirectShow一个基本的概念。我们开发一个DirectShow应用,其中最重要的步骤就是构造一个Filter Graph。下图就是一个Filter Graph
这是一个实现本地文件(avi文件)回放的Filter Graph,File Source互责读文件理的数据,之后将数据传输给AVI Splitter,AVI Spliiter 将接收的数据进行分析,分离成视频流和音频流。视频流经过AVI Ddcompressor的解码,送到Video Renderer输出到显示器。从AVI Spliter出来的音频流则送到DirectSound Device,由DirectSound Device送到音频输出设备。
4.Filter Graph Manager
Filter Graph Manager也是一个COM组件,它是DirectShow的控制中心,它控制Filter的运行。它有以下几个功能:
(1)协调各个Filter的状态。Filter有运行,暂停,停止三种状态,在Filter Graph中,各个Filter的状态必须协调一到,否则会引起冲突。Filter Graph Manager互责协调各个Filter的状态。
(2) 提供一个参考时间,用以同步音频流和视频流。
(3) 提供一套用于建立Filter Graph的方法
(4)与应用程序进行交互,它会将Filter Graph内部发生的事件,通过消息的方式通知应用程序。
5.Sample
Filter Graph运行的时候,数据从上游的Filter传到下游的Filter,数据有一个单位,这个单位叫Sample,就是数据将分成一个一个的Sample进行传输。
6.Media Type
媒体类型是DirectShow的一个基本的概念,它描述了两个过滤器之间传输数据(Sample)的格式。两个过滤器在连接之前,必须就所采用的媒体类型进行协商。
媒体类型,用AM_MEDIA_TYPE结构体来表示,它的结构可以看MSDN文档。其中有几个域是比较重要的
typedef struct _MediaType{
GUID majortype //主类型
GUID subtype
//辅助类型(进一步描述数据格式)
GUID formattype
//更详细描述数据格式
ULONG cbFormat
//描述formatype数据块的大小
BYTE* pbFormat
//描述formattype数据块的内存指针
//其它略
}
比较常用的描述媒体类型的GUID
majortype的有:
MEDIATYPE_Video(数字视频)
MEDIATYPE_Audio(数字音频)
MEDIATYPE_AnologVideo(模拟视频) //似乎没用过
MEDIATYPE_AnologAudio(模拟音频) //同上
MEDIATYPE_Stream(字节流) //Filter Source Filter从文件里读出来的数据
MEDIATYPE_Interleaved
//DV的数据流(包括音视频数据)
Subtype
//MEDIASUBTYPE_Y
Formattype
FORMAT_None
FORMAT_DvInfo (描述DV的数据类型格式)
FORMAT_VideoInfo (描述一般视频数据格式)
FORMAT_WaveFormatE (描述一般音频数据格式)
FORMAT_VideoInfo2 ////
媒体类型协商:两个过滤器在传输数据之前,必须就所采用的媒体类型进行协商。
7.Filter的连接过程
DirectShow提供了多方法来连接Filter,如IFilterGraph::ConectDirect,IGraphBuilder::Connect
Filter的连接过程实际是两个过滤器就媒体类型进行协商的过程。Filter连接的过程其实它们的PIN连接的过程,就是上游的输出PIN与上游的输入PIN连接。我们知道,每一个PIN都有它自己支持的媒体类型,而当两个PIN没有共同支持的媒体类型的时候,它们是不能连接的,所以连接之前要进行协商。
在Conect函数的实现里,可以看到大致过程是这样的:
连接是由输出PIN发出的
Connect Start
IF PIN已连接 THEN EXIT
IF Filter不是停止状态 THEN EXIT
//开始协商过程
CALL AgreeMediaType
Connect End
AgreeMediaType Start
在输出PIN或输入PIN媒体类型枚举器mediatypeEnums
调用函数TryMediaTypes,并把枚举器的指针传给它
AgreeMediaType End
TryMediaTypes Start
FOR EACH mediatype IN mediatypEnums
AttempConnection(media)
NEXT
TryMediaTypes End
AttemptConnection Start
CheckMediaType //检查媒体类型是否被支持(输出PIN自己)
IF 支持 THEN
在PIN上保存媒体类型
ELSE
FAILED
END IF
CALL ReceiveConnect // 询问输入PIN是否接受当前的媒体类型。
IF SUCCEED THEN
CALL CompleteConnect
ELSE
FAIL
END IF
AttemptConnect End
CompleteConnect的作用是完成连接后的一些后续工作,主要是决定数据传输的时候使用哪一个内存分配器,由谁来创建内存分配器(IMemAllocator)。
注:Filter传输的数据单元叫Sample,它是一个COM组件,封装了一个缓冲区。Sample由内存分配器来管理。内存分配器是实现了IMenAllocator接口的COM组件,互责管理Sample。
智能连接
:
构造
Filter Graph
的时候通过一些方法的调用,
DirectShow
会帮我们构造整个
Filter Graph
。用到的方法有
IGraphBuilder::RenderFile, IGraphBuilder:Render
ICaptureGraphBuilder2::RenderStream
8.Filter 的数据传送
(1)数据传送有两种模式:拉模式,推模式。
推模式:上游Filter调用下游输入PIN上的IMemInputPin::Receive,使用这种模式的话,输入PIN 必须实现IMemInputPin接口。比较典型的使用推模式的Filter是Capture Source Filter。
拉模式:下游的Filter调用 上游输出PIN的IAsycnRender接口的方法,这是,输出PIN必须实现IAsyncReader接口。比较典型的使用拉模式的是Fillter Source.
(2)传送过程中的内存管理:在传送的过程中,必须分配一定的缓冲。这个过程是通过内存分配器(IMemAllocator)来实现,它管理一系列的Sample。Sample是一个COM对象(实现IMediaSample接口),它封装并管理一个缓冲区。
在连接的过程中,Filter协商双方共同使用的内存管理器,并知道了内存管理器的位置。
这样,当上游的Filter要传输数据的时候,它将调用IMemAllocator::GetBuffer来得到一个闲的Sample然后将数据进去,传输给下游。
9.关于Filter Graph的状态转换:
10.质量控制:好像没什么用,没用到过
11.音、视同步:
(1) 参考时钟:
主要就是为Filter Graph设置一个参考时钟,凡实现了IReferenceClock接口的都可以作为参考时钟,FGM(Filter Graph Manager)有一套选定参考时钟的策略:一般有Live Source(Capture Source Filter)就选它作时钟,因为,这种Filter 包装的硬件上一般都有一个定时器。如果没有Live Source,那就选Audio Renderer,因为声卡上一般也有定时器。如果还没有的话,那就看其它Filter有没有实现IReferenceClock。确实没有的话,就选系统时钟。
(2) 时间戳(Time Stample)
送到Renderer渲染的Sample必须打上时间戳,就是一个渲染的Start Time 和End Time
好让Renderer 为它选择渲染时间。IMediaSample::SetTime
12.DirectShow对硬件的支持
DirectShow中Filter是工作在用户模式(user mode),而硬件设备,比如:视频采集卡,是工作是内核模式(kernel mode),那么,它们是怎样进行交互的呢?DirectShow为我们提供了包装Filter,包装Filter也是工作用户模式,不过它可以跟硬件驱动程序进行交互,再通过硬件驱动去控制硬件设备。
DirectShow里面有一个叫ksProxy的包装Filter,它是支持所有WDM设备,它提供了一个框架,然后由硬件厂商进行实现。
二.常用的接口
比较有用的接口和方法
IBaseFilter 这是所有Filter至少要实现的接口
IPin PIN至少要实现的接口
IFilterGraph 提供了一些建立Filter Graph的方法
IGraphBuilder (这个接口是由IFilterGraph继承来的,它提供了一些更方便的方法来建立Filter Graph,比如:RenderFile,Render,AddSourceFilter)
IMediaControl(控制Filter Graph的状态)
IVideoWindow(控制视频窗口)
//接口方法:put_MessageDrain
IMediaEventEx(事件处理有关的)
IMediaSeeking(用于媒体播放定位的)
上面这些接口,都是通过Filter Graph Manager暴露出来的,我们只创建一个Filter Graph Manaer 组件,就可以它们
ICaptureGraphBuilder2(用来建立Capture Filter Graph,这是一个非常有用的接口)
方法:
(1)SetFiltergraph //设置对应的IGraphBuilder*,也就是FilterGraph
(2)RenderStream
//用得最多的方法,对Capture Source Filter指定的输出PIN(Capture ,Preview,CC,VBI,)完成Filter Graph下游其它部分的构造。
它的原型如下:HRESULT RenderStream(
const GUID *pCategory,
const GUID *pType,
IUnknown *pSource,
IBaseFilter *pIntermediate,
IBaseFilter *pSink
);
它比较常见的用法如下:
为了对视频进行预览,我们只要如下调用:
RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,pCap,NULL,NULL);
为了对视频进行保存的:
RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,pMux);
pMux是一个Mux Splitter对输入的音、视频数据合并并进行编码,
(3) SetOutputFileName 这个方法是在对视频进行保存时调用的,可以设置保存文件路径,保存类型(如avi,asf),并会返回一个Mux Filter指针。
它的原型是:
HRESULT SetOutputFileName(
const GUID *pType,
LPCOLESTR lpwstrFile, //文件保存位置
IBaseFilter **ppf, //返回一个Mux Filter指针
IFileSinkFilter **pSink //这个指针用来更改文件保存路径,如果不需要可以置为NULL
);
pType 可能是一个subtype,当它是subtype时,只能下面两种取值:
MEDIASUBTYPE_Avi(保存为Avi格式),
MEDIASUBTYPE_Asf(保存为Asf),
因为这两种格式是windows可以识别的,所以它自动返回一个Mux Filter指针
如果要保存为其它格式,那么要将pType这个参数指定为一个Mux Filter或一个File Writer的GUID。
模拟电视卡可能用到的接口:
ICrossbar
//输入端子选择
IAMTuner
IAMTVTuner
//电视卡上TV Tuner的包装类,控制电视频道的接收与选择
常用方法:put_Channel
//设置接收频道
三、无题
1. DirectShow开发环境的配置(VC 6.0)
一般包含的头文件是 dshow.h
或streams.h 有时还会用到其它头文件,比如:qedit.h
用到的静态连接库文件:strmiids.lib strmbasd.lib(strmbase.lib Release版本) quartz.lib
winmm.lib uuid.lib
路径配置:VC
tools/option/
有配置的时候,最后把所有的库文件都包含上去,这样连接的时候就不会出现莫名其妙的错误了。
2. DirectShow应用开发的一般步骤
(1) 创建一个Filter Graph Manager组件
(2) 构造一个完整的Filter Graph
(3) 从 Filter Graph Manager组件得到各种接口,比如:IMediaControl,对程序进行控制
具体如下:
(1) 创建Filter Graph Manager组件的代码如下:
hr=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_FilterGraph, (void**)&pGraph)
相关接口的查询:
IMediaControl *pControl=NULL;
Hr=pGraph->QueryInterface(IID_IMediaControl,(void**)&pControl):
IVideoWindow* pVW=NULL;
Hr=pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVW);
(2) 之后,创建Filter Graph
这是整个程序最关键的部分
创建Filter Graph 有下面一些方法
IFilterGraph::AddFilter
IFilterGraph::ConnectDirect
IGraphBuilder::AddSourceFilter
IGraphBuilder::Connect IGraphBuilder::RenderFile
IGraphBuilder::Render
ICaptureGraphBuilder2::RenderStream
一般采用智能连接比较方便,当智能连接不行的时候再自己手动连接
(3) 视频窗口的设置:如果要对视频进行预览的话,那就要设置视频窗口。视频窗口是由Renderer Filter 自动创建出来,我们编程的时候,只需要对它时进行设置就行了
IVideoWindow接口用来专门控制视频窗口,用以下常见的方法:
//put_Owner
为视频窗口指定父窗口,通过这个方法,我们可以将视频窗口嵌到我们自己的窗口,比如一个静态文本控件,
SetWindowPosition 设置视频窗口的位置,
//put_MessageDrain
这个是比较有用的。对视频窗口而言,我们是不能改写它的窗口处理过程,因为它是由DirectShow自动创建的。我们可以通过这个方法将视频窗口收到的消息挂接到一个我们自己的窗口,这样就实现了对视频窗口的消息处理了。
//put_Visible 将视频窗口显示出来
//SetNotifyWindow 当Filter Graph中发生一些事件时,会向应用程序发送一些消息,这个方法是指定处理这此消息的的窗口
//put_WindowStyle 设置Video Window的样式,比如:
long style=0;
get_WindowStyle(&style);
style&=~WS_CAPTION;
put_WindowStyle(style)
这样就可以去掉Video Window的标题栏了
///put_FullScreenMode 设置全屏模式
(4)启动Filter Graph
pGraph->Run();
3. 常用的Filter Graph 的构造
构造Filter Graph,可以GraphEdit这个工具来测试
(1) 本地文件播放
最方便的方法:IGraphBuilder::RenderFile(filename);
只要选择播放的文件,IGraphBuilder就智能地帮你构造出整个Filter Graph
(2) 视频输入设备采集视频的预览
ICaptureGraphBuilder2 * pGB=NULL;
Hr=CoCreateInstance (CLSID_CaptureGraphBuilder2,NULL,CLSCTX_INPROC,IID_ICaptureGraphBuilder2,(void**)&pGB);
//对于DV面言,它可能有两种视频输出PIN,一种是Interleaved Pin, 一种是Video Pin
//前面一种包含音视频流,后面一种只有视频流,所以我们预览的时候,可以先试一下
Interleaved Pin,如果失败的话,再试一下Video Pin
Hr=pGB->RenderStream(PIN_CATEGORY_PREVIEW,MEDIATYPE_Interleaved,
//pCap,NULL,NULL);
if(FAILED(hr))
{
hr=pGB->RenderStream(PIN_GATEGORY_PREVIEW,MEDIATYPE_Video,pCap,NULL,NULL);
}
if(FAILED(hr))
{
return hr;
}
(3) 采集视频的录制(输出为本地文件)
pGB->SetOutputFileName(&MEDIASUBTYPE_Asf, filename, &pMux,&pSink);
MEDIASUBTYPE_Asf 指定为保存为Asf格式,
MEDIASUBTYPE_Avi 保存为AVI格式
如是要保存为其它格式,要有相应格式的Mux Spliiter 或 Filter Writer,然后传入它的GUID
filename 是保存路径
hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Interleaved,pCap,NULL,pMux);
if(FAILED(hr))
{
hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,pMux);
}
(4) 模拟电视卡VBI信息的渲染
电视卡的VBI信息,用以下三种:CC(Closed Caption),WST(World Standard Teletext), 忘记了
CC( Closed Caption)
电视卡的VBI信号可能是从VBI PIN输出的,也可能是从CC PIN输出的,所以我们要进行判断。
Hr=pGB->RenderStream(&PIN_CATEGORY_VBI,&MEDIATYPE_Video,pCap,NULL,NULL);
If(FAILED(hr))
{
pGB->RenderStream(&PIN_CATEGORY_CC,&MEDIATYPE_Video,pCap,NULL,NULL);
}
if(FAILED(hr))
{
return hr;
}
RenderStream会帮我们创建整个Filter Graph
RenderStream不支持WST信号的渲染,我们必须手动创建Filter Graph
四.杂记
(1)电视卡Crossbar, TV Tuner
Crossbar是用于输入端子的选择。使用的时候,我们只要用ICrossbar::Route 导通相应的端子就可以了。有的电视卡有两个Crossbar。
TV Tuner控制电视卡接收与频道选择,可以通过接口IAMTVTuner进行操作
(2)Stream Buffer Engine
这个可以实现实时流媒体的缓冲。它有两个Filters,Stream Buffer Source,Stream Buffer Sink
Stream Buffer Source 互责流媒体的采集,并写入磁盘,以供给 Stream Buffer Sink读取。
对于流媒体,Stream Buffer Source只支持DV和MPEG-2两种格式,所以,在输入Source时,有时候要进行格式转换。Stream Buffer Source 优先采用的格式是MPEG-2。
Stream Buffer Sink 互责读取Source 写入的数据,然后进行回放。
读取的时候,它会先找到Stream Buffer Source写入的一个 Stub文件,里面包含了指向其它文件的指针。
使用Stream Buffer Engine时,我们要构造两个Filter Graph。
(3) WM Asf Writer
将视频保存为Asf文件的时候,要用到WM Asf Writer,它有一个音频输入端,一个视频输入端,它要求只两个端都要接上,否则构造出来的Filter Graph无法启动。这个要求感觉有点勉强,不知有没有解决方法。
(4) ISampleGrabber
在创建CLSID_SampleGrabber组件的时候,我们不能直接就得到里面的ISampleGrabber
,而是要先得到IBaseFilter接口,然后再用QueryInterface得到ISampleGrabber.感觉很奇怪。
根据IUnknown原则,无论我们先得到那个接口都是可以的啊。