本文转自:http://bbs.njupt.edu.cn/cgi-bin/bbsanc?path=/groups/computer.faq/MultiMedia/D52F2B929/D99113C57/X70CFBCB7
发信人: yellowdawn (追逐天边的云), 信区: MultiMedia
标 题: DirectShow应用——视频捕捉WDM Vs VFW
发信站: 紫金飞鸿 (2005年01月26日18:32:09 星期三), 站内信件
作者:陆其明
关键字 DirectShow Live Capture WDM VFW
说起视频捕捉问题,我们先要来看一下视频捕捉卡。根据使用的驱动程序的不同来分类
,目前市场上大致有两种捕捉卡:VFW (Video for Windows)卡和WDM (Windows Driver
Model)卡。前者是一种趋于废弃的驱动模型,而后者是前者的替代模型;WDM还支持更多
新的特性,比如直接支持电视接收、视频会议、1394接口的设备、桌面摄像机、多条视
频流(Line-21或Closed-Caption等)同时输出等等。采用VFW的一般都是些以前生产的
卡;市面上新出现的,一般都是采用了WDM驱动程序。另外,视频捕捉卡的接口,可以是
以PCI或AGP的方式插入PC机箱,也可以直接以USB接口的方式外挂;还有就是通过1394接
口与PC机相连的数码摄像机等等。
使用DirectShow来处理一般的视频捕捉问题,是相对比较简单的。这当然得益于DirectS
how这一整套先进的应用架构。捕捉卡通常也是以一个(Capture) Filter的形式出现的。
处理视频捕捉,我们同样是使用Filter Graph,同样是操作Filter;控制起来,就似于
操作媒体文件的播放。当然,这主要是从应用程序控制层面上来说的;视频捕捉的应用
场合比较多,视频捕捉本身的一些处理还是有它的特殊性的,而且牵涉面比较广。本文
侧重于阐述一个建立视频捕捉程序的一般过程,以及WDM与VFW的兼容性问题。
当视频捕捉卡正确安装到系统中后,使用GraphEdit插入Filter,我们可以在“Video
Capture Sources”目录下看到代表捕捉卡的那个Filter。一般一个Capture Filter至少
有一个Capture Output Pin;典型的情况下,还有一个Preview Pin或者Video Port
Pin(一般Preview Pin和VP Pin不会共存)。有些视频捕捉卡,能够同时捕捉Video和Au
dio,那么它的Filter自然还应该有Audio部分的输出Pin。视频捕捉卡都注册在CLSID_Vi
deoInputDeviceCategory目录之下;要知道系统中安装了哪些捕捉卡,需要利用系统枚
举,如下:
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pEnum = NULL;
// Create the System Device Enumerator.
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast<void**>(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the video capture category.
hr = pDevEnum->CreateClassEnumerator(
CLSID_VideoInputDeviceCategory, &pEnum, 0);
}
Capture Filter的创建也不是象其他Filter一样使用CoCreateInstance,而是在枚举的
过程中BindToObject。在这一点上,对WDM卡和VFW卡的处理是一致的。
将Capture Filter加入Filter Graph之后,剩下的Filter怎么连接?DirectShow给我们
提供了一个简单的解决方法:使用ICaptureGraphBuilder2接口。如下创建:
IGraphBuilder *pGraph = 0;
ICaptureGraphBuilder2 *pBuild = 0;
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 0,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
(void**)&pGraph);
if (SUCCEEDED(hr))
{
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Initialize the Capture Graph Builder.
pBuild->SetFiltergraph(pGraph);
}
}
接下来,就是使用ICaptureGraphBuilder2::RenderStream来继续各个Output Pin的连接
。值得注意的是,这里有一个Pin Category的概念,作为RenderStream的第一个参数,
比如Preview Pin的目录为PIN_CATEGORY_PREVIEW,Capture Pin的目录为PIN_CATEGORY_
CAPTURE等等。下面是Preview Pin的连接示例:
ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder
// Initialize pBuild (not shown).
IBaseFilter *pCap; // Video capture filter.
/* Initialize pCap and add it to the filter graph (not shown). */
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
pCap, NULL, NULL);
调用RenderStream实现Preview链路,不管Capture Filter是否有Preview Pin或者只有V
P Pin,Capture Graph Builder都能自动正确地处理。(如果只有VP Pin,则自动连接V
P Pin;如果Capture Filter只有一个Capture Output Pin,则自动插入一个Smart Tee
Filter然后再连接。)
要实现视频捕捉到文件,最简单的方法也是使用ICaptureGraphBuilder2::RenderStream
。如下(假设生成的是AVI文件):
IBaseFilter *pMux;
hr = pBuild->SetOutputFileName(
&MEDIASUBTYPE_Avi, // Specifies AVI for the target file.
L"C:\\Example.avi", // File name.
&pMux, // Receives a pointer to the mux.
NULL);
hr = pBuild->RenderStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
NULL, // Intermediate filter (optional).
pMux); // Mux or file sink filter.
// Release the mux filter.
pMux->Release();
下面是典型的两个经过RenderStream以后构建的Capture Filter Graph的示意图:
使用Capture Graph Builder构建Filter链路的好处,还在于它能自动加入Crossbar
Filter(用于选择捕捉卡的输入端子,一般有三种:AV、S-Video、TV),如果是电视卡
的话还有TV Tuner Filter等等;使用ICaptureGraphBuilder2::FindInterface就可以找
到相应的控制接口等等。
跟WDM卡相比,VFW卡实现的功能要简单得多。上述的Filter Graph创建过程,两种卡的
处理是相似的;而对于视频捕捉的设置,则有较大的差异。WDM Capture Filter的执行
文件为kswdmcap.ax,它实际上是kernel-mode下KsProxy的一个插件;而DirectShow使用
了一个标识为CLSID_VfwCapture的Filter来支持VFW卡。WDM卡,设置Capture输出的图像
格式、图像的对比度、亮度、色度、饱和度等,都是通过IAMStreamConfig、IAMVideoPr
ocAmp等接口来实现(当然,在GraphEdit中可以通过Filter的Property Page来设置);
而VFW卡,一般要将驱动程序内的设置对话框显示给用户。VFW驱动程序一般实现三个设
置对话框:Video Source(设置图像源属性)、Video Format(设置图像输出格式)和V
ideo Display(设置图像显示属性)。下面是显示Video Source对话框的示例:
pControl->Stop(); // Stop the graph.
// Query the capture filter for the IAMVfwCaptureDialogs interface.
IAMVfwCaptureDialogs *pVfw = 0;
hr = pCap->QueryInterface(IID_IAMVfwCaptureDialogs, (void**)&pVfw);
if (SUCCEEDED(hr))
{
// Check if the device supports this dialog box.
if (S_OK == pVfw->HasDialog(VfwCaptureDialog_Source))
{
// Show the dialog box.
hr = pVfw->ShowDialog(VfwCaptureDialog_Source, hwndParent);
}
}
pControl->Run();
以上讲述了视频捕捉程序创建的一般过程。视频捕捉还有其他问题,比如AV同步、设备
的热插拔、DV Camcorder的控制、Analog TV以及Digital TV的支持,还有捕捉后的音视
频压缩、音视频合成,或者硬件压缩卡的支持等等。要想编写出专业级的视频捕捉程序
,这些问题是不可回避的!
=============================================================================
yellowdawn (追逐天边的云) 于 (2005年01月26日18:37:14 星期三) 提到 :
作者:陆其明 刊载于《中文信息——程序春秋》2003.10期
关键字 DirectShow Audio Capture
现在的所谓多媒体电脑一般都会有声卡(软声卡或硬声卡),有声卡就能进行音频的捕
捉。大家一定熟悉Windows自带的附件“录音机”程序,可以通过麦克风进行录音,最终
生成一个Wave文件。读完本文之后,你就会发现,自己使用DirectShow写一个音频捕捉
的应用程序,原来也是这么的容易!
大家知道,DirectShow对硬件的支持是通过特定的包装Filter来实现的。声卡使用的是A
udio Capture Filter,Filter内部使用以waveIn开头的一套API实现(如waveInOpen等
)。运行GraphEdit,插入Filter时,在“Audio Capture Sources”目录下,我们就能
看到所有代表本地机器上的声卡的各个Filter(有的机器装了几张声卡,这里就会有几
个Filter)。在Filter Graph中加入这个Filter,我们发现这个Filter有很多Input pin
,如Line In、CD Audio、Microphone、Stereo Mix等等;有一个Capture output pin。
需要说明的是,在Filter Graph中,这些Input pin并没有真正的数据流入,它们只是声
卡的各个输入端子的象征性表示;所以这些Input pin永远也不用连接。下面我们来看一
下如何创建一个音频捕捉程序。首先,当然是加入一个Audio Capture Filter。大家知
道,DirectShow加入一个硬件Filter,都是要靠“枚举”;声卡Filter也不例外。代表
声卡的Filter都注册在CLSID_AudioInputDeviceCategory目录下,使用系统设备枚举器
枚举这个目录,就能发现我们想要创建的声卡对象。(如何枚举这里就不再赘述了。)
当成功加入声卡Filter后,接下去的问题就是要将这个Filter与其他Filter相连。比如
,我们想捕捉生成一个Wave文件,那么我们还需加入一个Wave Dest Filter和一个File
Writer Filter,然后依次将它们相连。需要说明的是,Wave Dest Filter是微软Direct
X SDK带的一个例子,在samples\Multimedia\DirectShow\Filters\WavDest目录下,我
们必须首先编译这个例子并且注册这个Filter;这个Filter的功能是,当我们结束捕捉
时,往Wave文件中写入一个文件头信息。下图是在GraphEdit中的Filter连接图:
下面是一段创建音频捕捉程序的框架代码,可供参考:
void BuildAudioCaptureGraph(void) // Warning! No error checking here.
{
IBaseFilter *pSrc = NULL, *pWaveDest = NULL, *pWriter = NULL;
IFileSinkFilter *pSink= NULL;
IGraphBuilder *pGraph;
// Create the Filter Graph Manager.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
// Add the audio capture filter.
FindAudioCapture(&pSrc); // Assume that this function enumerates
// audio capture devices and picks one.
pGraph->AddFilter(pSrc, L"Capture");
// Add the WavDest and the File Writer.
AddFilterByClsid(pGraph, L"WavDest", CLSID_WavDest, &pWavDest);
AddFilterByClsid(pGraph, L"File Writer", CLSID_FileWriter, &pWriter);
// Set the file name.
pWriter->QueryInterface(IID_IFileSinkFilter, (void**)&pSink);
pSink->SetFileName(L"C:\\MyWackyWav.wav", NULL);
// Hook everything up.
ConnectTwoFilters(pGraph, pSrc, pWavDest);
ConnectTwoFilters(pGraph, pWavDest, pWriter);
}
当然,在进行音频捕捉的同时,我们还可以实时监听音频源的输入。如下示意图:
我们在Audio Capture Filter后面接了一个Infinite Pin Tee,这个Filter能够将一个I
nput pin输入的数据,复制成多份,分别通过各个Output pin发送出去。(这个Filter
也是微软DirectX SDK带的一个例子,在samples\Multimedia\DirectShow\Filters\
InfTee目录下。)我们看到Tee Filter的一支连到了DirectSound Renderer,可以将声
音放在声卡上输出。 创建音频捕捉的应用程序很简单吧!下面,我们还要来讨论一下音
频捕捉前可能用到的一些参数设置。在声卡Filter的每个Input pin上,我们都可以得到
IAMAudioInputMixer这个接口。通过这个接口,我们可以设置各个输入端子的音频属性
,如进行音频合成时是否允许某个输入端子的音频参与混合、音频输入的音量,还有Tre
ble、Bass等等。另外,在Filter上也可以得到IAMAudioInputMixer接口,这时调用接口
方法就可以统一控制各个输入端子的属性。音频捕捉,还可以设置的是音频的采样频率
以及声音的具体格式(8Bits或16Bits,单声道或双声道)。我们可以通过Capture
output pin的IAMStreamConfig来完成。下面的代码可供参考:
HRESULT hr = pCapturePin->QueryInterface(IID_IAMStreamConfig, (void
**)&pCfg);
// Read current media type/format
AM_MEDIA_TYPE *pmt={0};
hr = pCfg->GetFormat(&pmt);
if (SUCCEEDED(hr))
{
// Fill in values for the new format
WAVEFORMATEX *pWF = (WAVEFORMATEX *) pmt->pbFormat;
pWF->nChannels = (WORD) nChannels;
pWF->nSamplesPerSec = nFrequency;
pWF->nAvgBytesPerSec = lBytesPerSecond;
pWF->wBitsPerSample = (WORD) (nBytesPerSample * 8);
pWF->nBlockAlign = (WORD) (nBytesPerSample * nChannels);
// Set the new formattype for the output pin
hr = pCfg->SetFormat(pmt);
DeleteMediaType(pmt);
}
// Release interfaces
pCfg->Release();
最后,还要提到的一点,也是音频捕捉比较特殊的地方:我们可以通过Capture
output pin上的IAMBufferNegotiation接口,改变音频捕捉缓冲的大小,以减少声音播
放的延迟。默认情况下,Audio Capture Filter使用0.5秒钟的缓冲。对于一些特殊的应
用,这么大的缓冲是没有必要的,带来的延迟也比较大。一般,缓冲设置成能够容纳80
毫秒的数据已经很可靠;甚至30-40毫秒也已经足够了。但是也不能太小,否则会影响到
音频捕捉的效率,使音质受到损害。下面的代码设置音频捕捉的缓冲大小,可供参考:
pCapturePin->QueryInterface(IID_IAMBufferNegotiation, (void **)&pNeg);
// Set the buffer size based on selected settings
ALLOCATOR_PROPERTIES prop={0};
prop.cbBuffer = lBufferSize;
prop.cBuffers = 6;
prop.cbAlign = nBytesPerSample * nChannels;
hr = pNeg->SuggestAllocatorProperties(&prop);
pNeg->Release();
以上,我们讲述了音频捕捉程序的创建过程,以及一些捕捉参数的设置方法。相信大家
对于如何写音频捕捉程序已经有了自己的认识。音频捕捉直接得到的是PCM数据,根据需
要,我们还可以对其进行压缩,比如用Mp3格式(微软提供了一个免费的Mp3 Encoder)
、AC3格式等等;压缩后数据量更少,可以符合很多场合的应用。
=============================================================================
yellowdawn (追逐天边的云) 于 (2005年01月26日18:41:02 星期三) 提到 :
作者:陆其明
关键字 DirectShow TV Capture
现在市面上的很多视频捕捉卡都带有电视接收功能(以下简称TV),比如ATI TV Wonder
等。能够在电脑上接收电视(注意:本文提及的电视均指模拟电视),并且把电视节目
保存到文件,这是件多么美妙的事情!所以,我们有必要来探讨一下这个问题。关于如
何编写视频捕捉程序,笔者以前的一篇文章(《DirectShow应用——视频捕捉WDM Vs
VFW》),已经作了较为详尽的介绍。本文的侧重点,在于介绍如何使用视频捕捉卡的TV
功能。
大家先来看一下,带有TV功能的Capture Filter Graph,实物参考图如下:
其中,橙色的Filter为Capture Filter,它前面有两个Crossbar用以切换输入端子(一
张典型的捕捉卡有三个输入端子:AV端子、S-Video端子和TV端子),ATI TV Tuner即为
本文要重点介绍的带有TV接收功能的Filter。TV Tuner的属性页如下:
通过这个属性页界面,我们就可以选择频道来观看电视了!
大家可能觉得,直接将TV Tuner的属性页暴露给用户,这种做法太不专业了!是的,下
面我们就来看一下如何用程序来实现TV的上述控制功能。
先来看一下Filter Graph的创建。大多数情况下,我们使用IGraphBuilder接口来实现。
但对于视频捕捉应用,推荐使用ICaptureGraphBuilder2接口,它将大大简化我们的工作
。我们可以通过接口方法ICaptureGraphBuilder2::SetFiltergraph设置我们已经创建的
Filter Graph,然后,让ICaptureGraphBuilder2来完成其他繁琐的工作,比如加入Cros
sbar Filter、加入TV Tuner Filter、连接Capture Filter的各个Output pin等。而这
些工作,都将随着ICaptureGraphBuilder2::RenderStream的调用自动完成,参考如下:
// Video:
pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
pCaptureFilter, NULL, NULL);
// Audio:
pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Audio,
pCaptureFilter, NULL, NULL);
Filter Graph创建完成后,我们接着需要通过Crossbar Filter的IAMCrossbar接口,来
选择TV作为我们的输入端子(具体做法,参见SDK例子AmCap中实现的CCrossbar类)。再
然后,我们就可以通过TV Tuner的一个接口去实现TV的控制功能,这个接口就是IAMTVTu
ner了。获得这个接口的方法不是找到这个Filter,然后调用QueryInterface,而是要用
到ICaptureGraphBuilder2的另一个方法:FindInterface。参考如下:
IAMTVTuner *pTuner = NULL;
hr = pBuild->FindInterface(
&LOOK_UPSTREAM_ONLY, // Look upstream from pCap.
NULL, // No particular media type.
pCap, // Pointer to the capture filter.
IID_IAMTVTuner, (void**)&pTuner);
if (SUCCEEDED(hr))
{
// Use pTuner ...
pTuner->Release();
}
关于模拟电视的一些常识,以及IAMTVTuner的各个接口方法的使用细节,请参考DirectX
的帮助文档。笔者这里只介绍常用的几个接口方法,如下:
IAMTuner::put_CountryCode,设置电视接收的国家代码(中国是86);
IAMTuner::put_Mode,设置接收器是电视模式还是调频电台等模式;
IAMTVTuner::put_InputType,设置电视是天线输入还是有线电视输入;
IAMTVTuner::get_TVFormat,得到电视的制式是PAL、NTSC、SECAM;
IAMTVTuner::AutoTune,进行自动频道搜索。
好了,大概介绍完了。使用这个Filter Graph,我们可以观看电视,也可以将电视捕捉
下来生成文件保存。很容易吧?!其实这些都是DirectShow的功劳!
=============================================================================
yellowdawn (追逐天边的云) 于 (2005年01月26日18:43:19 星期三) 提到 :
作者:陆其明 刊载于《中文信息——程序春秋》2003.10期
关键字 DirectShow DVD
一. DVD基础知识
首先,我们来增加一点感性认识,看一下DVD光盘的文件系统(采用MicroUDF标准)。我
们可以看到,典型情况下光盘上有两个文件夹:VIDEO_TS和AUDIO_TS(通常是空的,这
里不作介绍)。VIDEO_TS下面一般包含三种类型的文件:.VOB文件、.IFO文件和.BUP文
件。这些文件都是作什么用的呢?其实,.VOB文件是用来保存所有MPEG2格式的音视频数
据的,这些数据包括影片内容、供菜单(Menu)和按钮(Button)以及多种语言字幕用
的子图片(Sub-picture)流;.IFO文件则是控制.VOB文件播放用的,这个文件中可以找
到有关何时以及如何播放.VOB文件数据的控制信息;而.BUP文件则是.IFO文件内容的一
个备份(因为.IFO文件对于保证DVD光盘的正确播放起着至关重要的作用)。根据这三种
文件的特性,我们也就可以理解它们在光盘介质上的排列顺序了:IFO-VOB-VOB...-BUP
。
我们再来看一下各个具体的文件。VIDEO_TS.IFO文件,保存DVD光盘的视频管理器 (Vid
eo Manager,简称VMG)信息,主要是光盘的一些全局信息,比如光盘指定的播放区域、
如何显示菜单等。VIDEO_TS.VOB文件,保存显示菜单的数据。类似VTS_xx_y.VOB的文件
,保存各个视频节目。(注:这里"xx"是节目编号,从01到99,"y"是从0到9的编号;由
于MicroUDF文件系统中一个文件最大只能1 GB,因此大多数影片不得不保存在多个VOB文
件中。)VTS_xx_y.IFO文件,保存对应编号的VOB文件的音视频格式信息。
大致了解了文件系统,我们再来看一下DVD其他方面的基础知识。大家知道,一张DVD光
盘主要包括三种媒体数据:视频(Video)、音频(Audio)和子图片。视频一般采用MPE
G2压缩算法,视频流支持最多9个视角(Angle),支持Line 21 Closed Caption(模拟
电视中的概念,主要是些文字信息);音频最多支持8条不同的流(即8种不同语言的配
音),支持最多6声道的声音格式(具体格式可以是AC3、MPEG、LPCM、DTS、SDDS等),
支持卡拉OK;子图片流最多可以支持32个(即提供多种语言的字幕)。DVD内容最主要的
逻辑分类叫做“标题”(Title)(一个标题通常代表了一部电影,或者一段视频节目,
一张DVD光盘最多可以有99个标题),每个标题又可以 最多分成999个“章节”(Chapte
r)(章节是便于用户随机访问的节点,每播放完一个章节的内容会自动播放下一个章节
,或者跳回菜单)。
用户与DVD的交互,最主要是通过菜单来完成的。菜单有两种:视频管理器菜单(Video
Manager Menu,简称VMGM,也叫做Top Menu或Title Menu)和视频标题集菜单(Video
Title Set Menu,简称VTSM,也叫做Root Menu,尽管它不是真正意义上的“根”菜单)
。VMGM允许用户进入主要的标题或者标题集(包含有一组标题)。如果进入标题集,就
显示VTSM子菜单。VTSM菜单可以包含进入当前标题集中各个标题的按钮,还有音频、视
角、字幕、章节等选择子菜单。VTSM菜单是可选的,可以不实现;当用户开始播放一张D
VD,一般首先看到的就是VMGM菜单,除非这张光盘在制作时被设置成了自动播放第一个
标题(此时DVD也可以根本不制作VMGM菜单)。
DVD还有父母锁功能。DVD中的视频内容可以打上父母管理级别(Parental Management
Level,简称PML);级别可以从1到8,其中1是无限制级别,8是最高限制级别。这样,
可以禁止孩子在未经父母同意的情况下观看电影。注意:父母锁功能需要播放器的
支持。
DVD导航(Navigation)还有一个非常重要的抽象概念,就是域(Domain)。通俗地说,
也就是DVD在特定播放状态下的操作许可。比如“选择按钮”命令必须在菜单状态下才有
效,“快进”命令在停止或者菜单状态下是无效的。播放器必须使用域的概念,阻止用
户向DVD驱动器发送无效的命令。下表列出了五个主要的域以及对应的操作说明:
Domain(域)
DVD导航器的工作内容
First Play(开始播放DVD时第一个播放的一段视频内容) 读取光盘上初始部分
Video Manager Menu(显示VMGM菜单时) 读取光盘的主菜单,菜单相关的指令都是有效
的
Video Title Set Menu(显示VTSM菜单时) 读取VTSM菜单,或者设置音频、字幕、视角
等的子菜单,菜单相关的指令都是有效的
Title(播放标题内容时) 菜单相关的指令都是无效的
Stop(停止时) 导航器不在工作,此时可以执行播放指令
另外,DVD的制作者还可以利用“用户操作控制”功能(User Operation Controls,简
称UOPs)来限制用户的操作。这需要在光盘上记录这些限制记号。比如,大多数光盘都
不允许用户在DVD播放处于First Play域时执行快进或者显示菜单命令。
二. DirectShow对DVD的支持
DirectShow对DVD播放提供了强力的支持。(DirectShow为支持DVD播放作了大量的工作
,除了不提供MPEG2 Video Decoder Filter外。换句话说,必须提供第三方的MPEG2
Decoder,否则我们写的播放程序还是跑不起来的!)编写DVD播放程序,我们无须研究D
VD的规格说明书;了解上面的这些基础知识就已经足够了。因为微软提供了一个叫DVD
Navigator的Filter,帮我们完成了繁琐的DVD导航的任务;提供一个专门用于建立播放D
VD的Filter Graph的COM组件(CLSID_DvdGraphBuilder)。我们所要做的,主要就是使
用DVD Navigator的两个接口:IDvdInfo2,获得光盘的属性和导航状态;以及IDvdContr
ol2,设置属性和执行播放操作。典型的播放DVD的Filter Graph如下:
典型的Filter Graph创建过程如下:
// Create an instance of the DVD Graph Builder object.
HRESULT hr;
hr = CoCreateInstance(CLSID_DvdGraphBuilder,
NULL,
CLSCTX_INPROC_SERVER,
IID_IDvdGraphBuilder,
reinterpret_cast<void**>(&m_pIDvdGB));
// Build the DVD filter graph.
AM_DVD_RENDERSTATUS buildStatus;
hr = m_pIDvdGB->RenderDvdVideoVolume(pszwDiscPath, m_dwRenderFlags,
&buildStatus);
// Get the pointers to the DVD Navigator interfaces.
hr = m_pIDvdGB->GetDvdInterface(IID_IDvdInfo2,
reinterpret_cast<void**>(&m_pIDvdI2));
hr = m_pIDvdGB->GetDvdInterface(IID_IDvdControl2,
reinterpret_cast<void**>(&m_pIDvdC2));
...
// Get a pointer to the filter graph manager.
hr = m_pDvdGB->GetFiltergraph(&m_pGraph);
...
// Use the graph pointer to get a pointer to IMediaControl,
// for controlling the filter graph as a whole.
hr = m_pGraph->QueryInterface(IID_IMediaControl,
reinterpret_cast<void**>(&m_pIMC));
...
// Get a pointer to IMediaEventEx,
// used for handling DVD and other filter graph events.
hr = m_pGraph->QueryInterface(IID_IMediaEventEx,
reinterpret_cast<void**>(&m_pME));
...
// Use the graph builder pointer again to get the IVideoWindow interface,
// to set the window style and message-handling behavior of the video
renderer filter.
hr = m_pIDvdGB->GetDvdInterface(IID_IVideoWindow,
reinterpret_cast<void**>(&m_pIVW));
hr = m_pDvdGB->GetDvdInterface(IID_IAMLine21Decoder,
reinterpret_cast<void**>(&pL21Dec));
关于IDvdInfo2和IDvdControl2的各个接口方法的详细说明和用法,请参见DirectShow
SDK文档,以及SDK提供的例子代码DVDSample。下面,仅就编写DVD播放程序需要注意的
地方,进行一些简单的罗列:
1. 为了得到DVD Navigator在创建时发出的事件,一般在RenderDvdVideoVolume 调用之
前获得ImediaEventEx接口。
2. 菜单命令实际上有两种,一种是选中(Select),一种是激活(Activate)。前者如
IDvdControl2::SelectAtPosition,IDvdControl2::SelectButton,IDvdControl2::Sel
ectRelativeButton等,效果是高亮度显示被选中的菜单;后者如IDvdControl2::Activa
teAtPosition,IDvdControl2::ActivateButton等,效果是产生相应的动作。也有选中
加激活的命令,如IDvdControl2::SelectAndActivateButton。
3. DVD最多支持8条音频流、32条子图片流,但在同一时刻,都只能各自选中某一条。
4. DVD Navigator本身并不强调父母锁功能,而只是把光盘上的PML信息发送给应用程序
。因此,父母锁功能需要在应用程序上完成。
5. 通过IDvdInfo2::GetState调用,可以得到IDvdState对象,用以实现“书签”的功能
。具体实现参见DVDSample的CDvdCore::SaveBookmark和CDvdCore::RestoreBookmark两
个函数。
6. 注意DVD播放时候的Filter Graph状态,需要同时考虑DVD Navigator的状态。
7. 支持卡拉OK的DVD,要求Audio Decoder实现AM_KSPROPSETID_DvdKaraoke属性集(即
实现IKsPropertySet接口)。
8. IDvdControl2关于播放的接口方法,一般都有异步和同步两种调用方式。如果是异步
方式,调用这些接口方法后,会立即返回,而并不等到实际的操作完成。有时候,这样
操作会引起DVD Navigator的状态混乱。对于这些接口方法,微软推荐了有5种调用方法
,下面列出常用的3种:
(1)异步方式(以PlayTitle调用为例,下同)
HRESULT hr = pDVDControl2->PlayTitle( uTitle,
DVD_CMD_FLAG_None, // = 0
NULL);
(2)阻塞方式
HRESULT hr = pDVDControl2->PlayTitle( uTitle,
EC_DVD_CMD_FLAG_Block,
NULL);
(3)使用同步对象
IDvdCmd* pObj;
HRESULT hr = pDVDControl2->PlayTitle(uTitle, 0, &pObj);
if(SUCCEEDED(hr))
{
pObj->WaitToEnd();
pObj->Release();
}
三. 总结
总之,DirectShow使我们从DVD的专业知识中解放出来;有了DirectShow的支持,编写一
个DVD播放程序还是比较轻松的!再次提醒读者朋友们,必须要提供第三方的Video
Decoder和Audio Decoder,否则,我们的DVD播放程序会陷入“万事具备,只欠东风”的
窘境!
=============================================================================
yellowdawn (追逐天边的云) 于 (2005年01月26日18:48:27 星期三) 提到 :
作者:陆其明
关键字 DirectShow DV Capture
DV数码摄像机(以下简称DV机),随着人民生活水平的不断提高,正逐步进入百姓家庭
中。如何将拍在磁带上的视频内容,转成媒体文件,乃至刻成光盘,是个值得探讨的问
题,也是一件很有意思的问题。还好,因为我们选择了DirectShow,这一切看起来是那
么的简单!
DV机通过1394接口与电脑相连(如果你的电脑没有1394接口的话,需要装一张1394转接
卡);支持即插即用。使用DirectShow的一个工具软件GraphEdit,我们可以在“Video
Capture Sources”目录下看到一个名为“Microsoft DV Camera and VCR”的Filter,
即代表我们的DV机。微软提供的DV相关的Filter还有DV Splitter、DV Video Decoder、
DV Video Encoder、DV Muxer等。DV数据,视频采用帧内压缩,音频仍然为PCM格式,采
用交叉的方式来存储;PAL制的图像大小为720x576,单帧为144000字节,NTSC制的图像
大小为720x480,单帧为120000字节;所以DV数据保存为文件,文件还是很大的。
一般DV数据可以直接保存到AVI文件中。有两种格式,一种格式(Type-1)仍然将音视频
按DV原有的交叉格式保存,另一种格式(Type-2)将DV数据的音视频分开后再保存(这
种格式视频部分其实仍然带有音频的冗余数据)。两种格式各有优缺点:Type-1可以节
省CPU使用时间,适合实时捕捉的场合;而Type-2保持了对VFW的向后兼容性。
接下去,我们来看一下如何来创建我们的DV应用程序。第一步,如何通过程序去控制DV
机?DV机可以有两种工作模式,一种是摄像模式,一种是回放模式。我们这里说的“控
制”,主要是指如何控制DV机对磁带的一系列操作(如播放、暂停、快进、快退、停止
等)。答案很简单,主要是通过DV Filter上的IAMExtTransport接口来实现的。(需要
提醒一下的是,创建DV Filter,必须通过系统枚举器枚举视频捕捉设备来完成;如何枚
举系统设备,请参考DirectX帮助文档,这里不再赘述。)下面是获得这个接口的参考代
码:
HRESULT hr;
IBaseFilter *pDVCam; // Pointer to the capture filter.
IAMExtTransport *pTransport;
// Create an instance of the capture filter (not shown).
hr = pDVCam->QueryInterface(IID_IAMExtTransport, (void **)&pTransport);
获得这个接口后,我们就可以通过其接口方法put_Mode来实现对DV机播放的控制了。相
应地,通过get_Mode也可以得到当前DV机的状态,如下:
LONG State;
hr = MyDevCap.pTransport->get_Mode(&State);
if (SUCCEEDED(hr))
{
switch (State)
{
case ED_MODE_PLAY: // playing
break;
case ED_MODE_STOP: // stopped
break;
case ED_MODE_FREEZE: // paused
break;
}
}
除了IAMExtTransport接口外,还有其他两个控制接口:IAMExtDevice接口,可以获得DV
机当前的工作模式,参考如下:
IAMExtDevice *pDevice;
hr = pDVCam->QueryInterface(IID_IAMExtDevice, (void **)&pDevice);
LONG lDeviceType = 0;
pDevice->GetCapability(ED_DEVCAP_DEVICE_TYPE, &lDeviceType, 0);
if (lDeviceType == ED_DEVTYPE_VCR)
{
// Device is a VCR. Enable all VCR functions.
}
else
{
// Device is a camera.
// Enable record and record-pause; disable other functions.
}
IAMTimecodeReader接口,可以获得每一个DV帧的时间戳。
第二步,我们就来看一下DV的各种实际应用场合。如下图:
我们可以将DV Filter输出的DV数据直接以Type-1的方式保存为AVI文件。如上图所示,M
SDV即为DV Filter,后面紧跟着接了一个Smart Tee Filter,将一路DV流分成了两路;
这样,我们在写文件的同时又能够预览,一举两得。以上的Filter Graph结构,是在视
频捕捉场合最常用的。当然,我们也可以将DV数据解码后再保存,如下图:
其中,Infinite Pin Tee Filter的功能类似于Smart Tee,是SDK中提供的一个带有源码
的Filter;两者的区别主要在于它们对输出数据的时间戳处理上不同。上面我们讲了两
种从DV机上抓取数据的应用,那么,能不能将我们电脑上的媒体文件写到DV机的磁带上
呢?答案是肯定的。请看如下参考图:
这里假设我们的AVI文件包含的视频格式是DV。(如果不是DV格式,我们需要用DV
Video Encoder将视频压缩成DV格式后,再往下连接。)我们还用到了DV Mux Filter,
将音频和视频流交互打包成标准的DV帧,然后送到DV Filter进行录像。
好了,讲到这,大家对DV的应用编程应该有所了解了吧?!更多的细节,还要参考Direc
tX的帮助文档,以及SDK中一个叫DVApp的例子,研究一下吧,可以加深理解哦!
================================= 合集结束 ==================================