在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. Hive是读时模式

    Hive处理的数据是大数据,在保存表数据时不对数据进行校验,而是在读数据时校验,不符合格式的数据设置为NULL: 读时模式的优点是,加载数据库快. 传统的数据库如mysql.oracle是写时模式,不 ...

  2. Oracle Sequence Cache 参数说明

    转自 http://blog.csdn.net/tianlesoftware/article/details/5995051 之前整理的一篇文章: ORACLE SEQUENCE 介绍 http:// ...

  3. Automate the Sizing of your SGA in Oracle 10g

    How much memory does each of the individual components of the SGA need? Oracle now has methods to de ...

  4. python匿名函数 高阶函数 内置函数 文件操作

    1.匿名函数 匿名就是没有名字 def func(x,y,z=1): return x+y+z 匿名 lambda x,y,z=1:x+y+z #与函数有相同的作用域,但是匿名意味着引用计数为0,使用 ...

  5. 百度开源的分布式 id 生成器

    UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器.UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于d ...

  6. MongoDB 安装和使用问题总结

    1. 一直安装不了[一直next下去但最后没有发现生成文件夹] 去掉 Installing MongoDB Compass 前面的打勾 2. 需要开两个cmd运行mongodb 开第一个,输入以下运行 ...

  7. 使用CAReplicatorLayer [2]

    使用CAReplicatorLayer [2] 工具类 // // Math.h // MathEquation // // Created by YouXianMing on 15/11/20. / ...

  8. word 排版用到双直线、波浪线、虚线 、直线、隔行线等技巧

    在办公或毕业设计时,有时排版需要插入双直线.波浪线.虚线 .直线.隔行线等而烦恼, 今天小白与大家分享技巧如下: 感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮.本文欢迎各位转载,但 ...

  9. 铁乐学Python_day12_作业

    1.写函数,返回一个扑克牌列表,里面有52项,每一项是一个元组 例如:[('红心',2),('草花',2), -('黑桃','A')] def poker(): suit = ['红心', '梅花', ...

  10. Windows Server 2008搭建域控制器

    前言 1.为什么要建域 工作组的分散管理模式不适合大型的网络环境下工作,域模式就是针对大型的网络管理需求设计的,就是共享用户账号,计算机账号和安全策略的计算机集合.域中集中存储用户账号的计算机就是域控 ...