二. DirectShow对DVD的支持
DirectShow对DVD播放提供了强力的支持。(DirectShow为支持DVD播放作了大量的工作,除了不提供MPEG2 Video Decoder Filter外。换句话说,必须提供第三方的MPEG2 Decoder,否则我们写的播放程序还是跑不起来的!)编写DVD播放程序,我们无须研究DVD的规格说明书;了解上面的这些基础知识就已经足够了。因为微软提供了一个叫DVD Navigator的Filter,帮我们完成了繁琐的DVD导航的任务;提供一个专门用于建立播放DVD的Filter Graph的COM组件(CLSID_DvdGraphBuilder)。我们所要做的,主要就是使用DVD Navigator的两个接口:IDvdInfo2,获得光盘的属性和导航状态;以及IDvdControl2,设置属性和执行播放操作。典型的播放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::SelectRelativeButton等,效果是高亮度显示被选中的菜单;后者如IDvdControl2::ActivateAtPosition,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播放程序会陷入“万事具备,只欠东风”的窘境!
|