在Windows平台上实现一个文件播放器有什么好的开发库和方案呢?方案有很多,比如基于FFmpeg,VLC的插件,mplayer,Directshow。用FFmpeg来实现文件格式解析、分离视频音频流、解码是很方便的,但是要实现一个播放器,还要实现视音频的显示和回放、视音频同步的处理,要做很多额外的开发工作,比较麻烦。而用VLC的插件不方便调试,扩充功能要改VLC的源代码,不灵活。而用Directshow来做播放器在Windows平台上是比较成熟和简单的方案,所以特别给大家介绍一下。

微软的Directshow是一个实现多媒体播放,视音频处理等功能的多媒体库,并且提供了Filter的机制使得多媒体任务的各个处理单元(读取数据、分离、解码、编码、回放)达到模块化,提高了复用性。开发者可以用别人提供的Filter,也可以实现自己的Filter来处理特定的任务。每个Filter至少有一个Pin,Pin分输入Pin和输出Pin,按照Filter的类型输入Pin和输出Pin的数目不等。每个Pin都有自己接受的媒体格式,比如视频渲染器Filter的输入Pin只接受RGB或YUV的格式。两个Filter的Pin连接前需要对Pin进行媒体类型协商(确定两方的Pin的媒体类型是否一致),如果协商成功,两个Pin就可以连接起来。一连串的Filter通过这种机制连接形成一个Graph,从而完成复杂的多媒体任务,比如播放文件,录制麦克风的音频,采集摄像头图像。下面演示了一个播放AVI文件的Graph的Filter连接图:

在上面的Graph图中,包括几种Filter:Source Filter,Splitter,Video Decoder,Audio Decoder,Video Renderer,Audio Renderer。而这些Filter大部分系统已经提供了,还有少数功能需要第三方的Filter来完成。因为多媒体文件的格式众多,所以播放文件的Graph需要加入支持不同格式的Filter,其中最主要的Filter是:分离器,解码器。解码器Filter目前有很多了,免费的而又出名的解码器Filter有FFDshow。FFDshow底层用到了FFmpeg,基于Directshow框架来实现,解码效率很高,但是比较庞大和臃肿,现在已基本不维护了。并且FFDshow只提供了编码器和解码器,还没有实现分离器。以前微软平台上的Directshow开发员会了支持尽量多的媒体格式,想尽办法从各处收集到各种分离器Filter,结果程序打包之后体积很大,并且兼容各种格式要插入不同的的Filter使程序变得复杂。很多第三方Filter不开源,加上测试不够,稳定性不好,容易产生很多问题。幸好,现在有个老外开发了一套Directshow插件,叫LAV Filters。不错,是一套,包括分离器,视频解码器和音频解码器。只要安装这套插件,播放大多数的多媒体格式基本上没问题了,省了开发员很多功夫。还有,这个LAV Filters套件还是开源的,作者对工程的更新速度很快,已经修复了很多Bug,现在变得很稳定了,连大名鼎鼎的开源播放器MPC也使用了LAV Filters,将它列为优先选用的插件。

说了那么多,大家一定很想使用LAV Filters来做开发吧?不用急,我下面就介绍如何在Directshow Graph中使用它。
首先,我们要知道这几个Filter的CLSID,下面是这几个Filter的CLSID定义:
  DEFINE_GUID(CLSID_LAVSplitter,

0x171252A0, 0x8820, 0x4AFE, 0x9D, 0xF8, 0x5C, 0x92, 0xB2, 0xD6, 0x6B, 0x04);

DEFINE_GUID(CLSID_LAVVideoDecoder, 
                0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F);

DEFINE_GUID(CLSID_LAVAudioDecoder,

0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91);

另外还有一个LAV Source Filter,CLSID是:

DEFINE_GUID(CLSID_LAVSource,

0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38);

这个LAV Source Filter的功能跟LAVSplitter差不多,都是分离器,不同的是它没有输入Pin,它集成了了Async Source Filter + LAVSplitter的功能。
下面我们就准备在Directshow的Graph中加入这些Filter。首先,我们需要实现一个类来封装播放文件的所有接口,下面是类的声明:

class CDXGraph
{
private:
IGraphBuilder * mGraph;
IMediaControl * mMediaControl;
IMediaEventEx * mEvent;
IBasicVideo * mBasicVideo;
IBasicAudio * mBasicAudio;
IVideoWindow * mVideoWindow;
IMediaSeeking * mSeeking;
IBaseFilter * m_pSourceFilter;
IBaseFilter * m_pDumpFilter; DWORD mObjectTableEntry;
UINT m_GraphMsg; public:
bool m_bEnableSound;
int m_nJPEGQuality;
SIZE m_PictureSize;
UINT m_nFramerate; public:
CDXGraph();
virtual ~CDXGraph(); public:
virtual bool Create(void);
virtual void Release(void);
virtual bool Attach(IGraphBuilder * inGraphBuilder); IGraphBuilder * GetGraph(void); // Not outstanding reference count
IMediaEventEx * GetEventHandle(void); UINT GetGraphState(); bool SetClipSourceRect(RECT rcSource);
bool SetDisplayWindow(HWND inWindow, LPRECT rcTarget);
bool SetNotifyWindow(HWND inWindow, long lMsg);
bool ResizeVideoWindow(long inLeft, long inTop, long inWidth, long inHeight); void HandleEvent(LONG * eventCode); bool Run(void); // Control filter graph
bool Stop(void);
bool Pause(void); bool IsRunning(void); // Filter graph status
bool IsStopped(void);
bool IsPaused(void); //bool SetFullScreen(BOOL inEnabled);
//bool GetFullScreen(void); // IMediaSeeking
bool GetCurrentPosition(LONGLONG * outPosition); //获得当前播放时间(单位为100纳秒, 等于10^(-4) ms )
bool GetStopPosition(LONGLONG * outPosition);
bool SetCurrentPosition(LONGLONG inPosition); //设置当前播放时间(单位为100纳秒, 等于10^(-4) ms )
bool SetStartStopPosition(LONGLONG inStart, LONGLONG inStop);
bool GetDuration(LONGLONG * outDuration); //获得文件时间长度(单位为100纳秒, 等于10^(-4) ms )
bool SetPlaybackRate(double inRate); //设置播放速度
bool GetPlaybackRate(double * outRate); // Attention: range from -10000 to 0, and 0 is FULL_VOLUME.
bool SetAudioVolume(long inVolume);//调节音量
long GetAudioVolume(void);
// Attention: range from -10000(left) to 10000(right), and 0 is both.
bool SetAudioBalance(long inBalance);
long GetAudioBalance(void); bool RenderFile(const TCHAR * inFile, DWORD & dwError);
bool SnapshotBitmap(const TCHAR * outFile);
bool GetTotalFrames(LONGLONG * outNum); private:
void AddToObjectTable(void) ;
void RemoveFromObjectTable(void); bool QueryInterfaces(void); HRESULT ConnectFilters(IPin * inOutputPin, IPin * inInputPin, const AM_MEDIA_TYPE * inMediaType = 0);
void DisconnectFilters(IPin * inOutputPin); HRESULT RenderFilter(IBaseFilter * pFilter);
HRESULT GetVideoProps(IPin * pVideoOutputPin);
};

  

 接着列出最核心的一个函数CDXGraph::RenderFile,它负责创建FilterGraph,将各个Filter添加到Graph和连接起来。下面是函数的实现:

bool CDXGraph::RenderFile(const TCHAR * inFile, DWORD & dwError)
{
if (mGraph == NULL)
{
dwError = ERROR_INVALID_POINTER;
return false;
} dwError = 0; HRESULT hr; bool bVideoPinConnected = false;
bool bAudioPinConnected = false;
bool bPrivateStreamPinConnected = false; #ifndef UNICODE
WCHAR wszFilePath[MAX_PATH] = {0};
MultiByteToWideChar(CP_ACP, 0, inFile, -1, wszFilePath, MAX_PATH);
#else
TCHAR wszFilePath[MAX_PATH] = {0};
lstrcpy(wszFilePath, inFile);
#endif CComPtr<IFileSourceFilter> pFileSource;
CComPtr<IBaseFilter> pSplitter;
CComPtr<IBaseFilter> pVideoDecoder;
CComPtr<IBaseFilter> pVideoRenderer; #if 0
hr = AddFilterByCLSID(mGraph, CLSID_MpegSourceFilter, L"Mpeg Splitter ", &pSplitter );
if(FAILED(hr))
{
OutputDebugString("Add Mpeg Splitter Filter Failed \n");
return FALSE;
}
#else
hr = AddFilterByCLSID(mGraph, CLSID_LAVSource, L"LAV Source Splitter ", &pSplitter );
if(FAILED(hr))
{
OutputDebugString("Add LAV Splitter Filter Failed \n");
return FALSE;
}
#endif
hr = pSplitter->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource);
if(FAILED(hr))
{
dwError = ERROR_GET_INTERFACE_FAIL;
return false;
} hr = pFileSource->Load(wszFilePath, NULL);
if(FAILED(hr))
{
dwError = ERROR_LOADFILE_FAIL;
return false;
} hr = AddFilterByCLSID(mGraph, CLSID_LAVVideoDecoder, L"LAV Video Decoder", &pVideoDecoder);
if(FAILED(hr))
{
OutputDebugString("Add LAV Video Filter Failed \n");
//return FALSE;
} hr = AddFilterByCLSID(mGraph, CLSID_VideoMixingRenderer9, L"VMR9 Renderer ", &pVideoRenderer);
if(FAILED(hr))
{
OutputDebugString("Add VMR9 Renderer Filter Failed \n");
return FALSE;
} hr = RenderFilter(pSplitter);
if(SUCCEEDED(hr))
bVideoPinConnected = true;
else
bVideoPinConnected = false; if(!bAudioPinConnected && !bVideoPinConnected)
{
OutputDebugString("RenderFilter Failed!\n"); OutputDebugString("第二次尝试用RenderFile自动连接\n"); if (FAILED(mGraph->RenderFile(wszFilePath, NULL)))
{
OutputDebugString("RenderFile Failed!\n\n"); dwError = ERROR_AUTO_RENDERFILE_FAIL;
return false;
} IBaseFilter * pVideoRenderer = NULL;
hr = FindVideoRenderer(mGraph, &pVideoRenderer);
if(SUCCEEDED(hr))
{
CComPtr<IPin> pVideoPin = GetInPin(pVideoRenderer, 0);
GetVideoProps(pVideoPin);
}
} if(!m_bEnableSound)
{
SetAudioVolume(-10000);
} OutputDebugString("RenderFile Succeeded!\n"); return true;
}

CDXGraph::RenderFile函数显示加入了这几个Filter:LAV Source,LAV Video Decoder,VMR9。其中,VMR9是视频渲染器,负责渲染图像和根据时间戳控制视频帧何时显示;LAVSource负责读取文件和从文件容器里分离出视频流和音频流,当我们调用AddFilterByCLSID函数(实际上调用了COM接口CoCreateInstance函数)创建这个Filter实例时,它是没有加载文件的,我们必须查询它的IFileSourceFilter接口指针,通过这个接口指针设置文件路径,把文件加载进去,如果文件加载成功,LAV Source会根据文件容器里媒体流的数目生成对应的OutputPin。接着,加入LAV Video Decoder,然后调用RenderFilter函数把LAV Source的每个OutputPin自动与下游的Filter进行连接,因为连接Filter的时候Graph Manager会优先选用已经添加到Graph中的Filter,那么LAV Source的Video Output Pin就会尝试与Video Decoder进行连接,而Audio Output Pin也一样,但是由于我们没有显示加入任何的Audio Decoder,Graph Manager会从系统安装的Filter中找到一个合适的解码器Filter插入进去,然后自动连接两个Filter的OutputPin与InputPin。如果所有Filter连接成功,那么播放文件Graph的Filter链路图就像下面这样子:

上面所讲的内容是Directshow开发很基本的操作,相信熟悉Directshow的读者看了觉得很熟悉和简单。

Mitov组件商使用的就是Directshow和Windows Api模式。

Windows 视频Directshow开发介绍的更多相关文章

  1. Microsoft Tech Summit 2018 课程简述:利用 Windows 新特性开发出更好的手绘视频应用

    概述 Microsoft Tech Summit 2018 微软技术暨生态大会将于10月24日至27日在上海世博中心举行,这也会是国内举办的最后一届 Tech Summit,2019 年开始会以 Mi ...

  2. Directshow开发播放器相关介绍

    原文地址:http://www.cnblogs.com/qiufa/archive/2006/12/19/596949.html DirectShow技术是DirectX推出的建立在DirectDra ...

  3. 转:Directshow开发的一些例子

    DirectShow Filter 开发典型例子分析 --字幕叠加 (FilterTitleOverlay)1 本文分析一下<DirectShow开发指南>中的一个典型的Transform ...

  4. windows phone 8 开发系列(三)程序清单说明与配置

    一 清单文件内容介绍 当我们先建了一个项目之后,我们可以看到vs自动会为我们创建了很多文件,正常人都会先一个个去翻看下每个文件都是干啥的,都主要写了些啥,在这些文件中,在Properies目录下面,我 ...

  5. XNA 4.0 环境搭建和 Hello World,Windows Phone 游戏开发

    XNA 4.0 环境搭建和 Hello World,Windows Phone 游戏开发 使用 Scene 类在 XNA 中创建不同的场景(八) 摘要: 平方已经开发了一些 Windows Phone ...

  6. Windows Phone 7 开发环境的搭建

    本节开始进行Windows Phone 开发环境的搭建,包括所需要的操作系统及硬件的介绍,开发工具的下载与安装,以及开发工具的介绍等.由于Jake Lin老师的视频中讲解的是早期的Windows Ph ...

  7. FFMPEG SDK 开发介绍(原创)

    来源:http://blog.sina.com.cn/s/blog_62a8419a01016exv.html 本文是作者在使用ffmpeg sdk开发过程中的实际经验,现在与大家分享,欢迎学习交流. ...

  8. 《Windows IoT 应用开发指南》

    物物互联的时代已经到来,智能家居.智慧校园.智慧交通.可穿戴.无人机.全息投影,各种各样的新名词.黑科技层出不穷.当我们为五年前能够通过手机控制家电而欣喜若狂的时候,可曾憧憬过当前使用增强现实设备完成 ...

  9. Windows 10 IoT Serials 1 - 针对Minnow Board MAX的Windows 10 IoT开发环境搭建

    目前,微软针对Windows IoT计划支持的硬件包括树莓派2,Minnow Board MAX 和Galileo (Gen 1和Gen 2).其中,Galileo (Gen 1和Gen 2)运行的是 ...

随机推荐

  1. Vue 框架-05-动态绑定 css 样式

    Vue 框架-05-动态绑定 css 样式 今天的小实例是关于 Vue 框架动态绑定 css 样式,这也是非常常用的一个部分 首先说一下 动态绑定,相对的大家都知道静态绑定,静态绑定的话,直接加 cl ...

  2. 用tableView实现的一种加载数据的布局

    用tableView实现的一种加载数据的布局 此博文是应朋友之邀解决他的业务逻辑问题 效果: 素材: 源码: ImageCell.h 与 ImageCell.m // // ImageCell.h / ...

  3. @private、@protected与@public三者之间的区别

    @private.@protected与@public三者之间的区别 类之间关系图 @private只能够使用在声明的类当中,其子类也不能够使用用@private声明的实例变量 @protected只 ...

  4. 用以替换系统NSLog的YouXianMingLog

    用以替换系统NSLog的YouXianMingLog 这是本人自己使用并改良的用以替换系统NSLog的类,非常好用,以下是使用示例,现在开源出来并提供源码,好用的话顶一下吧^_^ 效果: YouXia ...

  5. November 25th 2016 Week 48th Friday

    People will fall for its appearance while driving passionately. 观者倾心,驭者动魄. This is an advertisement ...

  6. November 13th 2016 Week 47th Sunday The 1st Day

    Adventure may hurt you, but monotony will kill you. 也许冒险会让你受伤,但一成不变会让你灭亡. Just change a bit, let the ...

  7. Alpha 冲刺报告(10/10)

    Alpha 冲刺报告(10/10) 队名:洛基小队 峻雄(组长) 已完成:阿尔法版的ppt 明日计划:总结阿尔法版的问题 剩余任务:角色属性脚本的完整版本 困难:缺乏编码经验,编码进度比较慢 ---- ...

  8. PHP设计模式系列 - 工厂模式

    工厂模式 提供获取某个对象实例的一个接口,同时使调用代码避免确定实例化基类的步骤. 工厂模式 实际上就是建立一个统一的类实例化的函数接口.统一调用,统一控制. 工厂模式是php项目开发中,最常用的设计 ...

  9. mvc, web mvc, spring web mvc 区别

    1. MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用于组织代码用一种业务逻辑和数据显示分离的 ...

  10. 基于easyui开发Web版Activiti流程定制器详解(三)——页面结构(上)

    上一篇介绍了定制器相关的文件,这篇我们来看看整个定制器的界面部分,了解了页面结构有助于更好的理解定制器的实现,那么现在开始吧! 首先,我们来看看整体的结构: 整体结构比较简单,主要包括三个部分: 1. ...