您的位置:首页 > 编程语言

Windows Embedded Compact 7中的多媒体编程(上)

2013-06-13 10:07 429 查看
随着计算机技术、网络技术迅猛发展,在嵌入式电脑上,多媒体功能变得越来越普及。倒如现在很多智能手机、PDA以及嵌入式电脑部具有听MP3、观看VIDEO视频以及使用摄像头拍照、录像等功能,这些功能极大地增强了用户体验。微软提供Direct Show技术标准,使得可以非常方便地开发多媒体应用程序。本章就来重点介绍Direct Show技术,并以实例来展现Direct Show技术应用。
本章主要介绍DirectShow的相关知识,以及如何利用DirectShow来进行多媒体编程。本章还提供了在Windows Embedded Compact 7环境下使用DirectShow编程的两个实例。

10.1 Direct Show介绍
DirectShow有时简称为DS或DShow,它是微软公司对之前Windows视频技术的一次更新,是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于组建对象模型(Component Object Model,COM)的流媒体处理开发包。作为DirectX大家族中的一员,DirectShow为Windows平台上处理各种格式的媒体文件播放、音频和视频采集等高性能的多媒体应用提供了完整的解决方案。DirectShow 9.0之前的版本与DirectX开发包一起发布,之后成为Windows SDK的一部分。
DirectShow为大部分微软公司程序设计语言提供了一个媒体的通用接口,而且是一个基于Filter并能在用户或开发者的命令下播放或记录媒体文件的可扩展框架。DirectShow的应用很广泛。通过DirectShow,软件开发者能够对ASF、MPEG、DV、MP3、WAVE等格式的媒体文件执行各种不同的处理。例如,对于本地应用,开发者可以利用DirectShow实现不同格式媒体文件的解码播放以及不同媒体格式间的相互转换,可以从本地机器的采集设备捕获音视频数据并保存为文件,可以接受并观看模拟电视等。此外,DirectShow可用于视频点播、视频监控以及视频会议等网络应用。广而言之,DirectShow还可以用于除了音视频等多媒体数据以外的其他流式数据的处理。
在Windows Embedded Compact 7中,我们同样可以使用DirectShow来捕获、回放和转换多媒体流数据。接下来我们将分别介绍DirectShow技术框架、过滤器(Filter)、过滤器图表管理器(Filter Graph Manager)、PIN以及DirectShow接口定义。
介绍下DirectX家族。DirectX是一个多煤休API,它提供标准接口来与图形卡、声卡、输入设备等进行交互。如果没有这组标准API,用户就需要为图形卡和声卡的每个组台和每种类型的键盘、鼠标和游戏杆编写不同的代码。DirectX从具体的硬件中抽象出来,并且将一组通用指令转换成硬件的具体命令。
DirectX的家族成员很多,而且各有各的本领,类似于DirectDraw和Direct3D负责二维图形图像,三维动面加速、DirectMusic和DirectSound负责交互式音乐,环境音效处理一样,
DirectShow为Windows平台上处理各种格式的媒体文件播放,音视频采集等高性能要求的多媒体应用,提供了完整的解决方案。
DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,与
DirectX开发包一起发布。DirectShow是Windows平台上的流媒体框架,提供了高质量的多媒体流采集和回放功能。它支持多种多样的媒体文件格式,包括ASF、MPEO、AVI. MP3和WAV文件,目时支持使用WDM驱动或早期的VFW驱动来进行多媒体流的采集。DirectShow整合了其他的DirectX技术,能自动侦测井使用可利用的音视频硬件加速,也能支持没有硬件加速的系统。
Microsoft通过DirectShow为多媒体程序开发员提供了标准的、统一的、高效的API接口。
DirectShow技术是建立在DirectX的DirectDraw和DirectSound的基础之上的,它通过
DirectShow对显卡进行控制以显示视师,通过DirectSound对声卡进行控制以播放声音。
DirectX为了最大限度提高效率而允许用户直接访问硬件,如允许用户直接读写显存,因此,DirectShow也同样具有快速的优势。
下面就对DirectShow的关键技术点做简要介绍,以便读者对DirectShow技术有了初步了解,也便于理解后面讲述的媒体播放器、摄像头捕捉示例代码。DirectShow是一门非常系统的技术,关于更为详细的技术细节,请读者参考相关专业DirectShow书籍或者MSDN。

10.1.1 DirectShow技术框架
DirectShow的技术框架如图10-1所示。看了此框架图就会发现DirectShow的工作原理也非常简单、清晰。此图可以简单理解成3部分,输入→逻辑处理→输出,这是一个典型的计算机处理流程。输入部分:它可以是本地视频、音频文件、Internet网络、视频和音频采集卡:输出部分:它可以是本地文件、显示器(显卡)和声卡;逻辑处理部分:也就是DirectShow的核心部分,从图上看包括:源过滤器、传输过滤器、渲染过滤器以及管理这3个过滤器的过滤器图管理器对象。

图10-1 DirectShow 技术框架图

在应用程序中,为了完成对多媒体数据的处理,需要将若干过滤器连接起来,一个的输出作为另一个的输入,这样连接在一起的一组过滤器称为过滤器图(Filter Graph)。过滤器图也决定着每一步该使用哪一个过滤嚣及这些过滤器之间是如何连接的。这样,多媒体数据流就在过滤器流水线上,从源过滤器经由中间过滤器移动到播放过滤器,从而最终完成播放。在这个过程中完成了对数据的读取、解码、将数据输出到相应的设备、播放等操作。
10.1.2过滤器( Filter)
DirectShow是基于模块化的开发框架,每个功能操作如捕获、回放和转换都采用COM组件方式,每个COM组件就称为Filter。Filter是完成DirectShow处理过程的基本单元。DirectShow提供了一系列标准的Filters用于应用开发,开发者也可以开发自己的Filter来扩展DirectShow的功能,但必须是以COM形式建立的。DirectX为用户提供了DirectShow基类库(DirectShow Base Class Library),用户自定义的过滤器都可以从基类库提供的基类和接口派生出来。
过滤器主要分为以下几种类型:
  (1)源过滤器(source filter):源过滤器引入数据到过滤器图表中,数据来源可以是文件、网络、照相机等。不同的源过滤器处理不同类型的数据源。
  (2)变换过滤器(transform filter):变换过滤器的工作是获取输入流,处理数据,并生成输出流。变换过滤器对数据的处理包括编解码、格式转换、压缩解压缩等。
  (3)渲染过滤器(renderer filter):渲染过滤器在过滤器图表里处于最后一级,它们接收数据并把数据提交给外设。
  (4)分割过滤器(splitter filter):分割过滤器把输入流分割成多个输出。例如,AVI分割过滤器把一个AVI格式的字节流分割成视频流和音频流。
  (5)混合过滤器(mux filter):混合过滤器把多个输入组合成一个单独的数据流。例如,AVI混合过滤器把视频流和音频流合成一个AVI格式的字节流。
  过滤器的这些分类并不是绝对的,例如一个ASF读过滤器(ASF Reader Filter)既是一个源过滤器又是一个分割过滤器。
在DirectShow中,一组相连的过滤器称为一个过滤器图表(Filter Graph),从而完成比单个过滤器更复杂的多媒体任务。过滤器图表用来连接过滤器以控制媒体流,它也可以将数据返回给应用程序,并搜索所支持的过滤器。过滤器有三种可能的状态:运行、停止和暂停。暂停是一种中间状态,停止状态到运行状态必定经过暂停状态。暂停可以理解为数据就绪状态,是为了快速切换到运行状态而设计的。在暂停状态下,数据线程是启动的,但被渲染过滤器阻塞了。通常情况下,过滤器图表中所有过滤器的状态是一致的。
过滤器接收输入和产生输出,信息通过过滤器管脚( PIN)在过滤器之间传递。一个管脚(PIN)是一个过滤器端口,它可以是输入端口也可以是输出端口。
过滤器Filter具有三个状态:运行、停止、暂停。当一个Filter运行时,它就处理媒体数据流:当停止时,Filter就不再处理数据,暂停状态用于临时暂停过滤器运行。
10.1.3 Filter Graph Manager
DirectShow专门提供了一个高级接口——过滤器图表管理器(Filter Graph Manager,FGM)——用来控制过滤器图表中的过滤器。同时,它也能为一个特定的媒体文件选配过滤器,渲染播放该文件。过滤器图表管理器是COM 形式的,它的功能有:协调过滤器间的状态转变;建立参考时钟;把事件(event)传送给应用程序;为应用程序提供建立过滤器图表的方法等。
  这些功能可通过过滤器图表管理器接口IGraphBuilder、IMediaControl、IMediaEvent、IMediaEventEx、IVideoWindow、IBasicAudio、IBasicVideo、IMediaSeeking、IMediaPosition、IVideoFrameStep等实现。
在开发基于DirectShow技术的应用程序时,都必须创建多个过滤器(Filter)并进行恰当的连接,于是数据流就可以从源过滤器输入,经过一系列过滤器(Filter)传递,最后到达RenderFilter输出,被用户所使用。这些过滤器的集合就叫做过滤器图(Filter Graph)。图10-2演示如何使用Filter来播放AVI视频文件。

图10-2播放带声音的视频文件过滤器图形

下面简要介绍采用Filter播放视频文件的流程。本流程将会在本章第2节的媒体播放器中
被具体代码实现。
(l)首先从一个文件中读取AVI数据,形成字节流。(这个工作由源Filter完成)。
(2)检查AVI数据流的头格式,然后通过AVI分割Filter,将视频流和音频流分开。
(3)解码视频流,根据压缩格式的不同,选取不同的decoder filters。
(4)通过Renderer Filter重画视频图像。
(5)音频流送到声卡进行播放,一般采用缺省的DirectSound Device Filter。
Filter Graph Manager也是一个COM对象,用来控制Filter Graph中所有的Filter,主要有以下的功能:
协调Filters之间的状态改变。Filters的状态改变必须以一种特殊顺序发生。因此,应用程序并不将状态改变的命令直接发给Filter,而是发送给Filter Graph Manager一个简单命令,由Manager将命令分发给Graph中的每一个Filter。Seeking也是按同样的方式工作,先由应用程序将Seek命令发送到Filter Graph Manager,然后由其分发给每个Filters。
建立参考时钟。Graph中的Filter都采用同一个时钟,称为参考时钟(Reference Clock),参考时钟可以确保所有的数据流同步。视频桢或者音频Sample应该被提交的时间称为Presentation Time,它是相对于参考时钟来确定的。Filter Graph Manager应该选择一个参考时钟,可以选择声卡上的时钟,也可以选择系统时钟。
传递事件到应用程序。Filter Graph Manager采用事件队列机制将Graph中发生的事件通知给应用程序,这个机制类似于Windows的消息循环。
提供应用程序建立Filter Graph的方法。Filter Graph Manager给应用程序提供了将Filter添加进Graph的方法、连接Filter以及断开Filter连接的方法。
Filter Graph Manager没有的一个功能就是把数据从一个Filter移动到另一个Filter,这是由Filters自己通过它们的Pin连接完成的。处理过程总是在不同的线程进行。
10.1.4 Pin
过滤器可以和一个或多个过滤器相连,连接的单向接口也是COM形式的,称为引脚,就是Pin。过滤器利用引脚在各个过滤器间传输数据。每个引脚都是从Ipin这个COM对象派生出来的。每个引脚都是过滤器的私有对象,过滤器可以动态的创建引脚,销毁引脚,自由控制引脚的生存时间。引脚可以分为输入引脚(Input pin)和输出引脚(Output pin)两种类型,两个相连的引脚必须是不同种类的,即输入引脚只能和输出引脚相连,且连接的方向总是从输出引脚指向输入引脚。
Pin就是两个过滤器相连的接口。每个Pin都是从IPin这个COM对象派生出来的。每个Pin都是过滤器私有对象,过滤器可以动态地创建和销毁Pin.自由地控制Pin的生存时间。Pin可以分为两类:输入Pin和输出Pin。两个相连的Pin必须是不同种类的,即输入Pin只能同输出Pin相连。数据就从相连的Pin中流动,从上一级过滤器到下一级过滤器。两个过滤器的Pin相连的时候,有一个协商的过程,两者必须统一数据流的类型、缓存的大小和数据传送的机制等。如果协商没有统一,这两个过滤器就无法连接。
10.1.5 DirectShow接口定义介绍
上面简要介绍了DirectShow技术框架,下面就来介绍DirectShow SDK提供的相关COM接口,通过这些接口可以非常方便地实现DirectShow应用编程,DirectShow常用接口如表10-1所示。
表 10-1DirectShow常用接口
接口
描述
IAMCollection
过滤器图表对象集合,例如过滤器或引脚。
IAMStats
允许应用程序从图表管理器中检索性能数据。过滤器可以使用此接口记录性能数据。
IBasicAudio
允许应用程序控制音频流的音量和平衡。
IBaseFilter
提供用于控制过滤器的方法。应用程序可以使用此接口枚举引脚和查询过滤器信息。
IBasicVideo
允许应用程序设置视频属性,例如目标矩形和源矩形
IBasicVideo2
从 IBasicVideo 接口派生,为应用程序提供了一个附加方法,通过它可以检索视频流的首选纵横比。
IConfigAsfWriter2
提供用于获取和设置 WM ASF Writer过滤器写文件要使用的高级流格式(Advanced Streaming Format,ASF)配置文件的方法和用于支持 Windows Media Format 9 Series SDK 中的新功能(例如双向编码和对反交错视频的支持)的方法。
IDeferredCommand
允许应用程序取消或修改该应用程序先前使用 IQueueCommand 接口排入队列的图形-控制命令。
IFilterInfo
管理过滤器的信息并提供访问过滤器和表示过滤器上的引脚的 IPinInfo 接口。
IFileSinkFilter
在将媒体流写入文件的过滤器上实现。
IFileSourceFilter
在从文件读媒体流的过滤器上实现。
IGraphBuilder
为应用程序提供创建过滤器图表的方法。
IMediaControl
提供方法来控制经过过滤器图表的数据流。它包含运行、暂停和停止图形的方法。
IMediaEvent
包含用来检索事件通知和用于重写过滤器图表管理器的默认事件处理的方法。
IMediaEventEx
从 IMediaEvent 派生并添加方法来启用一个应用程序窗口,以便在事件发生时接收消息。
IMediaPosition
用于寻找数据流的位置。
IMediaSeeking
提供搜索数据流位置和设置播放速率的方法。
IMediaTypeInfo
包含用于检索引脚连接的媒体类型的方法。
IPinInfo
包含用于检索引脚信息和连接引脚的方法。
IQueueCommand
允许应用程序预先将图形-控制命令排入队列。
IRegFilterInfo
提供对 Windows 注册表中的过滤器的访问,以及向过滤器图表中添加已注册的过滤器。
IVideoFrameStep
用于步进播放视频流,可使DirectShow应用程序,包括DVD播放器一次只播放一帧视频。
IVideoWindow
包含用于设置窗口所有者、窗口的位置和尺寸及其他窗口属性的方法。
IWmProfileManager
用于创建配置文件、加载现有的配置文件和保存配置文件。
10.2媒体播放器示例
Windows Embedded Compact7支持使用DirectShow技术来捕获、播放和转换多媒体内容。嵌入式设备通常包含音频和视频硬件解码器。由于嵌入式设备中的硬件专属于嵌入式平台,开发者需要实现DirectShow过滤器来让DirectShow在特定的平台运行。为了尽快实现一个DirectShow过滤器,一般推荐在WINCE7下使用DirectShow基类来完成。本节主要介绍利用DirectShow在WINCE平台上如何实现一个媒体播放器。一个播放过滤器图表包括一个源过滤器,一个多路分配过滤器,一个活多个解码器滤波器以及一个或多个渲染过滤器。对于某些格式,WINCE7还包括一个新的缓冲过滤器,即Buffering Stream Filter。该过滤器位于源过滤器和多路分配器之间,管理IMediaSample缓冲池。如果使用Buffering Stream Filter,下行的过滤器只需要很少的缓冲区管理工作。
图3提供了一个包含Buffering Stream Filter的过滤器图表。值得一提的是,在图10-3中,有的过滤器由操作系统中提供,有的过滤器是平台特有的,还有过滤器既可由操作系统提供,也可以为平台特有。

图10-3 DirectShow 实现媒体播放器的过滤器图表

当然,我们在实现视频和音频解码器、渲染器时可以有不同的选择。例如,可以利用一个软件模块将视频解码器和渲染器合并,另一个软件模块将音频解码器和渲染器合并。还有一种方式是创建单个模块,该模块包括视频解码器和渲染器、音频解码器和渲染器。取决于不同的实现方式,图10-3所示的过滤器图表可修改为图10-4所示的配置。

图10-4 媒体播放器的过滤器图表的其他配置

如果某些媒体格式不需要缓冲流过滤器,图3和图4中的Buffering Stream Filter可以去除,此时源过滤器和多路分配过滤器直接相连。
WINCE7所支持的编解码格式如表2所示。

表10-2 WINCE7支持的DirectShow编解码器

在实现DirectShow过滤器时,我们必须实现一些接口和方法。为了减少需要开发的代码量,DirectShow基类提供了许多通用代码,所以我们推荐在过滤器开发过程中尽可能地使用基类。一个过滤器的主要功能部分包括输入引脚,输出引脚以及过滤器本身的逻辑。但是需要注意的是,特殊的过滤器可能没有输入引脚或输出引脚。例如,如图2所示,源过滤器没有输入引脚,渲染过滤器没有输出引脚。一般而言,我们继承一些相关的基类来开发一个类所对应的输入引脚、输出引脚以及过滤器逻辑。

图10-5DirectShow解码器过滤器类图

在第1节中简要介绍了Direct Show的基本技术框架,下面就以一个实际的媒体播放器为例进行说明,使读者对应用DirectShow编程有更深入地了解。本倒是通过DirectShow的IFilterGpaph->Render函数来自动渲染视频、音频文件。这里能够播放哪些媒体文件,跟系统注册相关的Filter有很大关系。
下面就来讲述如何利用DirectShow技术来搭建一个媒体播放器。
(l)建立新项目。
使用VS2008选择智能设备模板,利用MFC智能设备应用程序向导创建一个基于对话框的应用程序EricMeidaPlayer,编译环境设置为yinchengOS SDK。
(2)新建CEricMediaControl类,用于封装媒体文件播放等功能。
CEricMediaControl类是一个通用c++类,无基类。其类图如图10-6所示。

图10-6 媒体播放类图

CEricMediaControl类基于DirectShow技术并且封装了媒体播放技术,提供打开媒体文件、播放、暂停、终止、全屏、设置当前播放位置等常用方法。基于CEricMediaControl类可以非常方便地制作媒体播放程序,读者也可以在此类的基础上加以修改升级,以实现更强的功能。
下面就来具体实现CEricMediaControl类。
1)为CEricMediaControl类添加如下私有变量,用于定义播放媒体文件播放所需的
DSHOW接口,代码如下所示。
private:
//DSHOW 接口
IGraphBuilder *m_pGB ;
IMediaControl *m_pMC ;
IMediaEventEx *m_pME ;
IVideoWindow *m_pVW ;
IBasicAudio *m_pBA ;
IBasicVideo *m_pBV ;
IMediaSeeking *m_pMS ;

//显示视频的窗口句柄
HWND m_hOwnerWnd;
备注:这里需要引用dshow.h头文件。
#include<show.h>
2)定义媒体播放事件对应的WINDOWS消息常量WM_GRAPHNOTIFY,定义如下:
//定义DSHOW事件通知消息
#define WM_GRAPHNOTIFY WM_USER+ 101
3)实现CEricMediaControl类的构造函数和析构函数。在构造函数里,将DSHOW相关
接口初始化为NULL,并初始化COM环境;在析构函数里,释放DSHOW接口并释放COM
环境。CEricMediaControl类构造函数和析构函数的实现如下所示。
//构造函数
CEricMediaControl::CEricMediaControl(void)
{
//将DSHOW接口置空
m_pGB = NULL;
m_pMC = NULL;
m_pME = NULL;
m_pVW = NULL;
m_pBA = NULL;
m_pBV = NULL;
m_pMS = NULL;
//初始化 COM 环境
CoInitialize(NULL);
}

//析构函数
CEricMediaControl::~CEricMediaControl(void)
{
//释放DSHOW接口
UnInitDShow();
//释放COM 环境
CoUninitialize();
}
4)为CEricMediaControl类添加InitDShow和UninitDShow两个私有方法。InitDShow方法用于初始化DShow接口并渲染媒体文件;UnlnitDShow方法用于释放DShow接口。读者应特别注意InitDShow方法,该方法直接描述了如何利用DShow接口逐步播放视频文件。
这个两个方法的定义如下:
private:
//初始化DSHOW接口
BOOL InitDShow(LPCTSTR strFileName /*视频文件名*/
,HWND hOwnerWnd /*显示视频的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
);
//释放DSHOW接口
BOOL UnInitDShow();
InitDShow和UnlnitDShow方法的实现如下所示。
/*
*函数介绍: 初始化DShow接口,并渲染好视频文件
*入口参数: strFileName: 视频文件名
hOwnerWnd: 显示视频的窗口句柄
hNotifyWnd: 接收DSHOW事件消息的串口句柄
*出口参数:(无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::InitDShow(LPCTSTR strFileName /*视频文件名*/
,HWND hOwnerWnd /*显示视频的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
)
{
HRESULT hResult;
//第1步:创建IGraphBuilder接口
hResult = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGB);
if (hResult != S_OK)
{
return FALSE;
}
//第2步:利用IGraphBuilder渲染视频文件
hResult = m_pGB->RenderFile(strFileName,NULL);
if (hResult != S_OK )
{
//
if (hResult == VFW_S_PARTIAL_RENDER)
{
TRACE(L"Some of the streams in this movie are in an unsupported format.\n");
}
else if (hResult == VFW_S_AUDIO_NOT_RENDERED)
{
TRACE(L"Partial success; the audio was not rendered.\n");
}
else if (hResult == VFW_S_DUPLICATE_NAME)
{
TRACE(L"Success; the Filter Graph Manager modified the filter name to avoid duplication..\n");
}
else if (hResult == VFW_S_VIDEO_NOT_RENDERED)
{
TRACE(L"Partial success; some of the streams in this movie are in an unsupported format.\n");
}
else
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}
}
//第3步:得到媒体播放控制接口
hResult = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}
//第4步:得到媒体播放位置搜索接口
hResult = m_pGB->QueryInterface(IID_IMediaSeeking,(void**)&m_pMS);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}

//设置查找定位的时间单位,这里设置:100纳秒(十亿分之一秒)
GUID guid_timeFormat = TIME_FORMAT_MEDIA_TIME;
m_pMS->SetTimeFormat(&guid_timeFormat);

//第5步:得到Filter Graph媒体事件接口
hResult = m_pGB->QueryInterface(IID_IMediaEventEx,(void**)&m_pME);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}
//设置媒体事件通知消息窗口
m_pME->SetNotifyWindow((OAHWND)hNotifyWnd, WM_GRAPHNOTIFY, 0);

//第6步:得到视频播放窗口接口
hResult = m_pGB->QueryInterface(IID_IVideoWindow, (void **)&m_pVW);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}
//设置视频播放窗口句柄
m_pVW->put_Owner((OAHWND)hOwnerWnd);
//设置视频窗口格式
m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

//第7步:得到基础视频流接口
hResult = m_pGB->QueryInterface(IID_IBasicVideo, (void **)&m_pBV);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}

//第8步:得到基础音频流接口
hResult = m_pGB->QueryInterface(IID_IBasicAudio, (void **)&m_pBA);
if (hResult != S_OK)
{
//释放DSHOW接口
UnInitDShow();
return FALSE;
}

return TRUE;
}

/*
*函数介绍: 卸载DShow系列接口
*入口参数: (无)
*出口参数:(无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::UnInitDShow()
{
//1,释放媒体播放控制接口
if (m_pMC != NULL)
{
//停止视频播放
m_pMC->Stop();
m_pMC->Release();
m_pMC = NULL;
}

//2,释放媒体事件接口
if (m_pME != NULL)
{
//消息通知窗口置空
m_pME->SetNotifyWindow(NULL, 0, 0);
m_pME->Release();
m_pME = NULL;
}

//3,释放视频播放窗口接口
if (m_pVW != NULL)
{
//隐藏视频窗口
m_pVW->put_Visible(OAFALSE);
//设置视频播放窗口句柄为空
m_pVW->put_Owner(NULL);
m_pVW->Release();
m_pVW = NULL;
}

//4,释放基础音频流接口
if (m_pBA != NULL)
{
m_pBA->Release();
m_pBA = NULL;
}

//5,释放基础视频流接口
if (m_pBV != NULL)
{
m_pBV->Release();
m_pBV = NULL;
}

//6,释放媒体搜索接口
if (m_pMS != NULL)
{
m_pMS->Release();
m_pMS = NULL;
}

//7,最后释放FilterGpaph接口
if (m_pGB != NULL)
{
m_pGB->Release();
m_pGB = NULL;
}

return TRUE;
}
5)为CEricMediaControl类添加4个共有方法,供外部调用。这4个方法分别为OpenFile(打开视频文件)、VfideoRun(视频播放)、VideoPause(视频暂停)、VideoStop(视频停止)。打开视频文件方法,调用前面讲述的InitDShow方法,来初始化视频播放文件。视频播放、暂停、停止等方法通过IMediaControl接口调用相关方法来实现媒体流的控制。
这4个方法具体定义如下:
public:
//打开视频文件
BOOL OpenFile(LPCTSTR strFileName /*视频文件名*/
,HWND hOwnerWnd /*显示视频的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
);
//播放视频
BOOL VideoRun();
//暂停视频
BOOL VideoPause();
//停止视频
BOOL VideoStop();

这4个方法的实现如下所示。
/*
*函数介绍:打开视频文件
*入口参数:strFileName:视频文件名
hOwnerWnd:显示视频的窗口句柄
hNotifyWnd:接收DSHOW事件消息的串口句柄
*出口参数:(无)
*返回值:TRUE,成功打开视频文件;FALSE:打开视频文件失败
*/
BOOL CEricMediaControl::OpenFile(LPCTSTR strFileName /*视频文件名*/
,HWND hOwnerWnd/*显示视频的窗口句柄*/
,HWND hNotifyWnd/*接收DSHOW事件消息的串口句柄*/


//存储显示视频窗口句柄
m_hOwnerWnd = hOwnerWnd;
//重置DSHOW接口
UninitDShow();
//打开视频文件,并对DSHOW接口做初始化工作
return InitDShow( strFileName, hOwnerWnd,hNotifyWnd);

/*
*函数介绍:括放视频
*入口参数;(无)
*出口参数:(无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::VideoRun()
//检测IMediaControl控制接口有效性
if(m_pMC== NULL)
{
Return FALSE;
}
//播放视频
HRESULT hResult = m_pMC->Run();
if(hResult !=S_OK)
{
Return FALSE;
}
Return TRUE;
}
/*

*函数介绍;暂停视频
*入口参数:(无)
*出口参数:(无)
*返回值: TRUE:成功:FALSE:失败
*/
BOOL CEricMediaControl::VideoPause()
//检测IMediaControl控制接口有效性
if (m_pMC==NULL)
{
return FALSE;
}
//暂停视频
HRESULT hResult = m_pMC->Pause();
If(hResult i= S_OK)
{
Return FALSE;
}
Return TRUE;
}
/*
*函数介绍:停止视频
*入口参数:(无)
*出口参数:(无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::VideoStop()
{
HRESULT hResult;
//检测IMediaControl控制接口有效性
if(m_pMC ==NULL)
{
Return FALSE;
}
//停止视频
hReSult = m_pMC->Stop();
if (hResult != S_OK)
{
Return FALSE;
}
//将当前播放位置置0
LONGLONG pos:0;
hResult : m_pMS->SetPositions(&pos, AM_SEEKIFIG_AbsolutePositionin9
NULL, AM SEEKING NoPositioning);
6)为类CEricMediaControl类添加7个共有方法,用于设置媒体播放属性以及得到媒体播放属性。这7个方法分别为FitVideoWindow(设置视频显示比例)、FuIIScreen(全屏显示)、GetFuIIScreenStatus(得到是否是全屏显示)、GetMediaEvent(得到DShow播放事件)、SetPositions(设置播放进度)、GetCurrentPos(得到当前播放进度)、GelDuration(得到媒体文件时间长度)。具体定义如下:
public:
//设置视频显示比例
BOOL FitVideoWindow(FLOAT fScale);
//全屏显示
BOOL FullScreen();
//得到是否是全屏显示
BOOL GetFullScreenStatus();
//得到DShow播放事件
BOOL GetMediaEvent(long *lEventCode);
//设置播放进度,单位: 秒
BOOL SetPositions(DWORD dwPos /*设置当前播放进度*/);
//得到视频播放当前的位置,单位:秒
BOOL GetCurrentPos(DWORD &dwPos /*out 当前播放进度*/);
//得到视频文件时间长度,单位:秒
BOOL GetDuration(DWORD &dwLength);
这7个函数的实现如下所示。
/*
*函数介绍: 设置视频显示比例
*入口参数: fScale : 显示比例, <= 1.0
*出口参数: (无)
*返回值:TRUE:成功;FALSE:失败,此处有点问题
*/
BOOL CEricMediaControl::FitVideoWindow(FLOAT fScale)
{
LONG lHeight, lWidth;
int iSeek = 0;
double dblScaleX,dblScaleY;
HRESULT hr = S_OK;
LONG lDeflateX = 0;
LONG lDeflateY = 0;
CRect clientRect;
CRect dstRect;

//
if (m_pBV == NULL)
{
return FALSE;
}

//放缩比例必须小于等于1
if (fScale > 1.0)
{
return FALSE;
}

//得到原始视频尺寸
hr = m_pBV->GetVideoSize(&lWidth, &lHeight);
if (hr != S_OK)
{
return FALSE;
}

//设置拉伸后的尺寸
lWidth = lWidth * fScale;
lHeight = lHeight * fScale;

//得到视频播放窗口的尺寸
GetClientRect(m_hOwnerWnd, &clientRect);

lDeflateX = (clientRect.Width() - clientRect.Width() * fScale) / 2;
lDeflateY = (clientRect.Height() - clientRect.Height() * fScale) / 2;

//重新设置客户区域
clientRect.DeflateRect(lDeflateX,lDeflateY);

if ( (lWidth <= clientRect.Width())
&& (lHeight <= clientRect.Height()))
{
dstRect.left = (clientRect.right - clientRect.left - lWidth) /2;
dstRect.right = dstRect.left + lWidth;
dstRect.top = (clientRect.bottom - clientRect.top - lHeight) /2;
dstRect.bottom = dstRect.top + lHeight;
}
else
{
dblScaleX =double(clientRect.Width()) / double(lWidth) ;
dblScaleY = double(clientRect.Height()) / double(lHeight) ;

if (dblScaleX <= dblScaleY)
{
dstRect.left = clientRect.left;
dstRect.right = clientRect.right;

iSeek =(clientRect.Height() - clientRect.Width()*(double(lHeight) / double(lWidth)))/2;
dstRect.top = clientRect.top + iSeek;
dstRect.bottom = dstRect.top + clientRect.Width()*(double(lHeight) / double(lWidth));
}
else
{
dstRect.top = clientRect.top;
dstRect.bottom = clientRect.bottom;

iSeek =(clientRect.Width() - clientRect.Height()*(double(lWidth) / double(lHeight)))/2;
dstRect.left = clientRect.left + iSeek;
dstRect.right = dstRect.left + clientRect.Height()*(double(lWidth) / double(lHeight));
}
}

//设置视频播放位置
m_pVW->SetWindowPosition(dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height());

return TRUE;
}

/*
*函数介绍: 全屏显示
*入口参数: (无)
*出口参数: (无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::FullScreen()
{
LONG lMode = 0;
static HWND hDrain=0;
if (m_pBV == NULL)
{
return FALSE;
}
//得到全屏状态
m_pVW->get_FullScreenMode(&lMode);

if (lMode == OAFALSE)
{
// Save current message drain
m_pVW->get_MessageDrain((OAHWND *) &hDrain);

// Set message drain to application main window
m_pVW->put_MessageDrain((OAHWND)m_hOwnerWnd );

//设置全屏幕
lMode = OATRUE;
m_pVW->put_FullScreenMode(lMode);
}
else
{
//切换到非全屏模式
lMode = OAFALSE;
m_pVW->put_FullScreenMode(lMode);

// Undo change of message drain
m_pVW->put_MessageDrain((OAHWND) hDrain);
// Reset video window
FitVideoWindow(1);
m_pVW->SetWindowForeground(-1);
}
return TRUE;
}

/*
*函数介绍: 得到是否全屏幕播放
*入口参数: (无)
*出口参数: (无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::GetFullScreenStatus()
{
LONG lMode = 0;
if (m_pBV == NULL)
{
return FALSE;
}

m_pVW->get_FullScreenMode(&lMode);

if (lMode == OAFALSE)
{
return FALSE;
}
else
{
return TRUE;
}
}

/*
*函数介绍: 得到媒体事件
*入口参数: (无)
*出口参数: lEventCode
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::GetMediaEvent(long *lEventCode)
{
LONG evCode, evParam1, evParam2;
HRESULT hr=S_OK;

if (m_pME == NULL)
{
return FALSE;
}

hr = m_pME->GetEvent(&evCode, &evParam1, &evParam2, 0);
if (SUCCEEDED(hr))
{
*lEventCode = evCode;
// Spin through the events
hr = m_pME->FreeEventParams(evCode, evParam1, evParam2);

return TRUE;
}
return FALSE;
}

/*
*函数介绍: 设置播放进度
*入口参数: dwPos :播放进度,单位秒
*出口参数: (无)
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::SetPositions(DWORD dwPos /*设置当前播放进度*/)
{
//设置绝对位置,转化成纳秒为单位
LONGLONG llPos = dwPos * 10000 * 1000;
if (m_pMS == NULL)
{
return FALSE;
}
//设置视频播放当前位置
HRESULT hr = m_pMS->SetPositions(&llPos,AM_SEEKING_AbsolutePositioning ,
NULL, AM_SEEKING_NoPositioning);
if (hr == S_OK)
{
return TRUE;
}
else
{
return FALSE;
}
}

/*
*函数介绍: 得到视频文件播放长度,单位秒
*入口参数: dwLength :视频文件时间长度
*出口参数: dwLength :视频文件时间长度
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::GetDuration(DWORD &dwLength)
{
dwLength = 0;
if (m_pMS == NULL)
{
return FALSE;
}

//得到视频总时间长度
LONGLONG llDuration;
HRESULT hResult = m_pMS->GetDuration(&llDuration);
if (hResult != S_OK)
{
return FALSE;
}

//转换成以秒为单位
llDuration = llDuration / 10000 ;
llDuration = llDuration / 1000;
dwLength = (DWORD)llDuration;
return TRUE;
}

/*
*函数介绍: 得到媒体当前播放进度位置,单位秒
*入口参数: dwPos :当前播放进度,单位秒
*出口参数: dwPos :当前播放进度,单位秒
*返回值:TRUE:成功;FALSE:失败
*/
BOOL CEricMediaControl::GetCurrentPos(DWORD &dwPos)
{
dwPos = 0;
LONGLONG llPos = 0;
if (m_pMS== NULL)
{
return FALSE;
}

//得到当前视频播放位置
HRESULT hResult = m_pMS->GetCurrentPosition(&llPos);
if (hResult != S_OK)
{
return FALSE;
}

//转换成以秒为单位
llPos = llPos / 10000 ;
llPos = llPos / 1000;
dwPos = (DWORD)llPos;
return TRUE;
}
至此,CEricMediaContorl类就编写完成了。下面将基于CEricMediaContorl类演示如何使用它实现播放视频、音频文件的功能。若此时编译工程,将会发现有一堆连接错误提示。那是因为没有链接strmiids.lib静态库。
选择“EricMediaPlayer属性页”一“链接嚣” 一“输入”一“附加依赖项”,输入strmiids.lib,即可无误地编译本工程。
(3)设计媒体播放器主窗口。
1) include“CEricMedia Control.h”
2)为CEricMeidaPlayerDlg类添加mVideoControl成员变量,类型为之前创建的CEricMediaControl类。定义如下。
privrate:
CEricMediaControl m_VideoControl;//媒体播放对象
备注:由于引用了CEricMediaControl类,因此需要在EricMediaPJayerDlg.h文件中添加对CEricMediaControl类定义文件的引用。

3)实现“打开”按钮单击事件。单击“打开”按钮,将可以选择要播放的媒体文件。“打开”按钮单击事件的实现如下所示。
//打开视频文件
void CEricMeidaPlayerDlg::OnBnClickedBtnOpen()
{
DWORD dwMax = 0;
//得到媒体播放窗口
CWnd *pVideoWnd = GetDlgItem(IDC_WND_VIDEO);
//得到进度条窗口
CProgressCtrl *pPrgWnd = (CProgressCtrl*)GetDlgItem(IDC_PRG_VIDEO);
//定义媒体播放文件名
CString strFileName;

TCHAR szFilters[]= _T("windows media video Files (*.wmv)|*.wmv|video Files (*.avi)|*.avi|All Files (*.*)|*.*||");
CFileDialog fileDlg (TRUE, _T("Open video files"), _T("*.wmv"),
OFN_FILEMUSTEXIST , szFilters, this);

//打开文件选择对话框
if( fileDlg.DoModal () !=IDOK )
{
return;
}

//得到要播放的视频文件名
strFileName = fileDlg.GetPathName();

//打开视频文件
if (m_VideoControl.OpenFile(strFileName,pVideoWnd->m_hWnd,m_hWnd))
{
//设置视频尺寸
m_VideoControl.FitVideoWindow(1.0);
//得到视频文件时间长度
m_VideoControl.GetDuration(dwMax);
//设置进度条范围
pPrgWnd->SetRange(0,dwMax);
pPrgWnd->SetPos(0);
}
else
{
AfxMessageBox(L"Can't play the video,because the system can't find some codec program!");
}
}
4)实现“播放”、“暂停”、“停止”和“全屏”按钮单击事件,具体实现代码如下所示。
//播放视频
void CEricMeidaPlayerDlg::OnBnClickedBtnPlay()
{
m_VideoControl.VideoRun();
}

//暂停视频
void CEricMeidaPlayerDlg::OnBnClickedBtnPause()
{
m_VideoControl.VideoPause();
}

//停止视频
void CEricMeidaPlayerDlg::OnBnClickedBtnStop()
{
m_VideoControl.VideoStop();
}

//全屏
void CEricMeidaPlayerDlg::OnBnClickedBtnFull()
{
m_VideoControl.FullScreen();
}
5)实现视频窗口(IDC_WND_VIDEO)单击事件,用于从全屏状态回到正常状态,如下所示。
//视频窗口单击事件
//用于从全屏状态回到正常状态
void CEricMeidaPlayerDlg::OnStnClickedWndVideo()
{
if (m_VideoControl.GetFullScreenStatus())
{
//切换到正常状态
m_VideoControl.FullScreen();
}
}
6)实现媒体播放事件通知消息(WM ORAPHNOTIFY)函数。
首先为CEricMediaPlayerDlg类添加自定义消息处理函数定义,定义如下:
//媒体插放事件消息处理函数
afx_msg LRESULT OnNotifyMedia(WPARAM wParam, LPARAM lParam);
注意,该定义应添加在DECLARE_MESSAOE_MAP()语句之前。
OnNotifyMedia函数的实现如下所示。
//媒体播放事件通知
LRESULT CEricMeidaPlayerDlg::OnNotifyMedia(WPARAM WParam, LPARAM LParam)
{
long lEventCode;
if (m_VideoControl.GetMediaEvent(&lEventCode))
{
//收到播放结束事件
if (lEventCode == EC_COMPLETE)
{
if (m_VideoControl.VideoStop())
{
//
}
}
}
return (LRESULT)0;
}
最后,需要将OnNotifyMedia消息处理函数和消息WM GRAPHNOTIFY关联起来,即在
BEGIN_MESSAGE_MAP宏中添加如下代码实现关联:
ON_MESSAGE(WM_GRAPHNOTIFY, OnNotifyMedia)
7)添加1个定时器,用于更新媒体播放进度。
在BOOL CEricMeidaPlayerDlg::OnInitDialog()函数中添加如下代码,用于启动1个定时器。
//启动定时器,用于更新媒体播放进度
SetTimer(1,1000,NULL);
定时器消息处理函数的实现如下所示。
//WM_TIMER,定时器处理函数
void CEricMeidaPlayerDlg::OnTimer(UINT_PTR nIDEvent)
{
//得到进度条窗口
CProgressCtrl *pPrgWnd = (CProgressCtrl*)GetDlgItem(IDC_PRG_VIDEO);

DWORD dwPos = 0;
//得到媒体当前播放进度
m_VideoControl.GetCurrentPos(dwPos);
//设置进度条位置
pPrgWnd->SetPos(dwPos);

CDialog::OnTimer(nIDEvent);
}
至此,媒体播放器示例就编写完成了。
打开Windows Embedded Compact 7虚拟机,将该程序复制到共享目录,
执行之,如图10-7

图10-7选择程序并运行
运行效果如下图10-8

图10-8程序效果图
单击“打开”按钮,选择一个电影,我们这里选择鸿门宴的剪辑,然后单击“播放”按钮就可以观看视频了。

图10-9播放电影图
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows 微软技术