您的位置:首页 > 其它

dshow-001 介绍

2017-02-21 16:21 78 查看
1 dshow是基于windows平台的一种流媒体机制

可以从基于wdm的数字/模拟设备中捕获,也可以直接从windows的视频中捕获

自动检测用户的音视频设备,自动硬件加速

2 dshow是基于com的技术,c++设计,没有管理api

dshow可以轻松完成媒体回放/格式转换/捕获任务

3 使用dshow时对com技术要了解多少

如果是app开发者:需要了解基本的com知识,包括com对象的初始化 访问接口 在其他接口管理引用计数

如果是filter开发者:需要了解更多

4 dshow没有管理api,微软也没有计划出一个管理api

dshow和directx的关系:dshow里的音视频捕获和回放是用directx中的模块实现的,我们不需要直接用directx的api来实现一个相同功能的dshow

dshow以前的名字叫Microsoft ActiveMovie

5 新一代的dmos是否会取代dshow

dmos是Microsoft DirectX Media Objects

dshow中可以使用dmos.如果是编解码和特效,使用dmos;如果是其他目的,还是使用dshow比较合适.

6 如何判断指定的系统中是否有dshow环境

创建一个filter graph manager对象,如果成功就是有,失败表示没有

7 如何在不显示属性页的情况下修改filter的设置

一般情况下,filter都会提供接口来设置属性

8 构建一个dshow app

主要头文件是dshow.h 如果有需要也会包含其他头文件

库文件主要有两个静态库:strmiids.lib主要提供类标志和接口标志 quartz.lib提供AMGetErrorText函数,如果不调用这个函数就不用包含这个

如果要写一个自定义的filter,最好使用base class这个工程.

运行库问题:xp sp2以后的都包含了dshow运行库 sp1之前的要自己处理.重要的是微软没有计划对这些库进行进一步开发

9 dshow app 编程

什么是filter:在dshow里面一个构建块就是一个filter

一个filter就是一个软件组成块,主要负责多媒体流上执行的操作.负责接收输入和处理输出

一系列filter合起来称为filter graph,控制这些filter的更高一级的组件称为filter graph manager,由她来负责数据流(启动和停止)

对于app一般有3个必要的执行块:app创建一个filter graph manager; 通过manager创建一个graph,这个graph里的所有filter都会依赖于这个app;app通过manager控制graph和filter的数据流,也会响应manager的事件

10 概览

一般的流程是创建一个管理对象(filter graph manager),通过管理对象来创建一个filter graph,启动graph.

对于编程来说,IGraphBuilder接口表示管理对象,她包含了两个子接口:IMediaControl和IMediaEvent,分别用于”控制流的启动和停止”,”接收管理对象返回的事件”.

11 dshow的大致介绍

问题:多媒体处理中的几大挑战

数据量大,需要快速处理

音视频同步,开始结束和播放过程中都需要有相同步调

数据来源繁多,本地文件/网络/电视广播/采集设备

数据格式繁多

在高级层面,程序员不知道终端用户有什么硬件设备

dshow的解决方案:

为了音视频的吞吐量达到预期,使用了direct3d和directsound

时间戳来保证同步

不同数据源 不同格式 不同硬件设备, dshow用了模块化的体系接口,利用filter来混合匹配不同的软件组件

12 filter的深入了解

dshow使用的模块化结构.com对象每一个执行阶段称为一个filter.

播放一个avi文件用到了以下几个filter:

file source filter: 将文件的原始数据读为字节流

avi splitter filter: 分析avi头,将字节流转换为单独的视频帧和音频样本

decoder filter: 将视频帧解码为非压缩格式

video renderer filter: 绘制视频帧

sound filter: 播放音频样本

在dshow中一系列filter称为filter graph

filter有3中状态:running stopped paused

filter的分类:

source filter: graph中提供数据的filter,数据源可能是文件/网络/摄像头/各种硬件等等

transform filter: 有一个input流,一个数据处理过程,一个output stream, 例如:编解码

renderer fitler: graph的最后一个filter,获取数据并展示给终端用户.例如:视频显示画面,音频丢到声卡,写文件的filter丢到磁盘

splitter filter: 一个input流,多个output流,常见的有解析一个流,同时输出多个流,看avi就是这种情况

mux filter: 多个input流,一个output流,合并流,和上面的splitter filter的目标正好相反

对一个filter的分类并不是绝对的,例如一个filter可以包含了source filter和renderer filter的功能.

每一个filter都会包含一个IBaseFilter接口,所有的pin都会包含IPin接口.

13 filter graph manager的深入了解

前面说到了,一个filter的集合称为一个filter graph, filter graph manager对象就是用来管理graph中的filter的,她是一个com对象

manager对象都能干些啥:

协调各个filter之间的的状态变化:graph中的filter的状态变化有严格的顺序(不可能出现render还在进行中,而source已经停止了的情况),状态的变化是app通知的,manager作为app和filter的中间层,app只会把命令传给manager,manager再来协调各个filter的状态变化.eg: 播放时的seek.

建立一个参考时钟(reference clock):在同一个graph中的filter都是使用同一个时钟,这个时钟称为参考时钟.参考时钟用于确保所有的流的同步.音视频帧上的时间被称为presentation time stamp(就是常说的pts),用于显示时的同步;还有一个称为dts(decoder time stamp 用于解码时的同步),pts只是相对于参考时钟的一个相对数.manager的参考时钟要么取系统时钟,要么取声卡时钟

将一些graph事件通知给app:manager有一个event队列,通过这个evnet队列可以将graph中出现的的event通知给app.(有点类似windows的消息循环)

提供方法让app来建立graph:app可以通过manager来想一个graph添加filter;连接两个filter或是取消filter之间的连接.

manager的方法并不负责数据的流动,数据的流动是filter自己负责的(确切的说是通过filter的pin来完成的).manager的处理都是在单独的线程中进行的,filter和manager运行在同一线程

14 媒体类型

由于dshow使用的模块化设计,所以需要一个东西来描述在graph中数据每时每刻的格式,这个东西就是媒体类型 media type

dshow中需要这么一个描述数字媒体类型,只有当两个filter协商的类型是一致时,这两个filter才能connect,不然就不行.

dshow中的类型结构是 AM_MEDIA_TYPE, 下面是结构体中重要的成员

Major type: 主类型, 一个guid, 分类是:音频 视频 上游的流 midi数据等

Subtype: 子类型, 一个guid, 进一步指定类型, eg:主类型是视频, 子类型是bgr24 或是yuv

Format block: 进一步描述数据的信息(eg:子类型是nv12,但并没有描述帧率,长宽等),成员名是pbFormat, 她是一个void*类型的指针,针对不通过的数据类型,这个指针指向不同的数据结构.加上另一个成员cbFormat就能取出结构体数据,在每次使用pbFormat指向的结构体之前,最好先检查cbFormat这个参数.

如果我们在filter中要查看rgb24视频的信息:

HRESULT CheckMediaType(AM_MEDIA_TYPE* pmt)
{
if (nullptr != pmt &&
MEDIATYPE_Video == pmt->majortype &&
MEDIASUBTYPE_RGB24 == pmt->subtype &&
FORMAT_VideoInfo == pmt->formattype &&
pmt->cbFormat >= sizeof (VIDEOINFOHEADER) &&
nullptr != pmt->pbFormat)
{
VIDEOINFOHEADER* pvih = (VIDEOINFOHEADER*)pmt->pbFormat;
...

return S_OK;
}

return E_POINTER;
}
AM_MEDIA_TYPE还包含了一些其他参数,但是对于filter来说,其他参数都是用不上.


15 媒体样本和分配

filter通过pin来流通数据,从filter的output pin流想另一个filter的input pin. 在input pin上调用IMemInputPin::Receive接口来获取output pin传来的数据.

依赖不同的filter,为媒体数据申请内存有多种方式:

在heap上,

在directdraw上用共享gdi内存

或是其他分配方式

申请内存(分配内存)的对象称为allocator 分配器. 是通过IMemAllocator接口暴露的一个com对象

当filter的pin connect之后,必须有一个pin提供一个allocator. dshow提供了一系列方法让pin来提供allocator.

当流开始时, allocator会创建一个缓冲池(buffer pool),在流中,上游流将数据写入,下游流来读取,期间上游流不会提供任何buffer的原始指针给下游流,相反,是利用com对象(称为media sample媒体样本)来管理这些buffer,这写media sample是通过allocator创建的.

media sample通过IMediaSample接口暴露, 一个media sample包含下面几个元素:

一个指向基础缓冲区的指针

一个时间戳: 表示pts

各种各样的flags: eg:从上个sample到这个sample,是否有中断

一个媒体类型(可选):filter是否要改变meida type. 一般sample没有media type 表示 从上一个sample到这个sample, media type没有改变

当一个filter使用buffer, 那么她就掌管sample的引用计数.allocator利用这个引用计数决定什么时候可以重用这个buffer.当filter释放这个sample之后,allocator就能重新利用这些缓冲了.

16 在graph中,硬件设备是怎么玩的

filter封装

dshow的filter是用户态的软件组件. 如果内核态的硬件设备(eg:音视频采集卡)要加入到这个graph来耍,那么这些设备就要表现的像一个用户态的filter(eg: 音频采集filter; vfw采集filter; tv tuner filter; tv audio filter等等). dshow提供了函数来让这些设备表现(或者说封装)成一个用户态的filter. 下一个问题:dshow是如何做到的? :dshow提供了一个ksproxy filter, 所有的dwn流设备都可以用这个filter表示.硬件厂商可以通过扩展ksproxy来支持自定义功能, 说白了就是提供一个ksproxy插件.(ksproxy.ax是windows上专门处理音视频的filter)

这些封装的的filter提供的com接口就是这些设备提供的功能. app就是通过这些接口来和filter传递信息的. filter将com接口的调用传递给设备驱动(这到了内核态了),然后将结果返回给app. tv tuner,tv audio, ksproxy filter支持使用IKsPropertySet接口来设置自定义属性,而vfw capture filter, audio capture filter不能通过这种方式扩展

对于app来说,这些封装filter让app可以控制设备就像控制其他filter一样(换句话说:app控制封装filter和控制其他filter一样,没有额外要求).

vfw

vfw采集filter支持早期vfw采集卡,如果系统中存在vfw采集卡,dshow会自动检测到,并加入到graph中

声卡

新一代的声卡,就像麦克风上的输入插孔.一般这些声卡都可以控制每一个输入音频的音量大小/高音/低音,在dshow中声卡的输入,混音都被封装在audio capture filter中

dwm流设备

新一代的硬件解码和采集都遵循wdm规格,wdm比vfw功能更加强大:枚举采集格式;编程控制视频参数(色彩和亮度)等等

为了支持dwm, dshow提供了一个ksproxy filter(ksproxy.ax 瑞士军刀),这个filter做了很多事.

内核流

wdm设备支持内核流

内核流不会将数据从内核态切换到用户态(切换的这个代价太大), 内核流不会对cpu造成负担,而且数据从一个设备到另一个设备不会使用系统的主内存

从app上看,数据是从一个filter移到另一个filter,实际上数据可能从来没有切换到用户态,(视频采集数据,在内核态直接丢到显卡),在某些场景下eg:将数据写文件时,就要把数据从内核态切换到用户态,不管怎么样数据没有必要拷贝到内存.

一般app开发者不需要考虑内核流.

17 graph构建组件

dshow提供了以下方式来构建graph:

filter graph manager: 这个对象控制着graph,支持IGraphBuilder IMediaContrl IMediaEventEx, app都会利用这个对象

capture graph manager: 这个对象提供了额外的方式来构建graph. 她原本设计就是为了执行视频捕获,支持ICaptureGraphBuilder2

filter mapper和system device enumerator,这些对象用于注册到用户的系统

dvd graph builder,这个对象主要是为了dvd回放和导航,支持IDvdGraphBuilder接口

智能连接 inteligent connect,用于智能创建全部或一部分graph.

18 深入了解graph的构建

如果要创建一个filter graph ,利用IDD_IGraphBuilder 参数

filter graph支持下面几种构建graph的方法:

IFilterGraph::ConnectDirect : 尝试直接让两个pin连接

IGraphBuilder::Connect : 尝试让两个pin连接, 一般会直接连接,如果不能直接连接,会智能在中间添加缺失的filter

IGraphBuilder::Render : 从output pin开始构建剩下的graph

IGraphBuilder::RenderFile : 构建一个完整的文件回放graph

IFilterGraph::AddFilter : 向graph中添加一个filter

上面这些方法提供了构建graph的3中基本途径:

manager构建整个graph

如果需要播放一个有格式的文件(avi,mp3,wav,mpeg),使用RenderFile

工作流程是这样的:RenderFile会从注册表查找可以解析文件的source filter, graph之后的部分也是从注册表查找,查找过程如下:

filter的类别标志了filter的基本功能(视频filter处理视频,编码filter处理编码等)

媒体类型描述了filter可以接收哪种数据,输出哪种数据

还有一个优先级参数,如果有多个filter都满足上面两点,那谁的优先级高选谁的

manager是通过filter mapper对象来搜索注册表的,filter mapper也是一个com对象,作用就是在注册表查找相关的filter

当搜索到合适的filter之后,manager会将filter加入到graph,每个filter添加时,都会先进行pin连接, 这是一个协商过程,如果pin连接失败,就会换另一个filter来尝试.这个搜索添加的过程是一个迭代的,直到所有的stream都rendered

manager构建部分graph

如果要做的事并不仅仅是播放一个文件, 那app必须执行一部分graph的工作.

eg: 视频采集app必须选择capture source filter加入到graph

eg: 写数据到avi文件, 必须添加avi mux 和file writer filter到graph

一般都是让manager完成整个graph.

我们可以通过上面的Render方法来实现从一个pin中预览

app构建整个graph

在某些场景,app必须构建graph的每一个filter,这时可以使用上面的AddFilter方法,然后遍历filter上的pin,再将pin连接起来,连接可以使用Connect和ConnectDirect

19 深入了解智能连接

智能连接是manager构建graph的一种机制, 她包含了一系列相关的算法来选择filter并将她们添加到graph

这部分适合添加filter遇到问题的读者,或是想让自己的filter可以作为智能连接的一部分的filter开发者

后续若有需要再补齐

20 graph中的数据流向

app开发者不需要了解细节

filter开发者需要理解

后续若有需要再补齐

21 dshow中的事件通知

filter通过event通知告诉manager现在有一个event通知,这个event可能是流结束,也可能是render中出现错误.

manager会自己处理一部分,剩下的会丢给app来处理

dshow中的事件通知和windows的消息循环类似,app可以接收event,然后处理.app也可以取消dshow对某个event的默认处理,这样dshow再接到这个event,就直接丢给app去处理.

22 app如何接收event通知

首先manager暴露了3个接口来支持event通知

IMediaEventSink: filter通过她来post一个event

IMediaEvent: app通过她来接收event

ImediaEventEx: 继承扩展IMediaEvent

整个流程是这样的:

filter通过IMediaEventSink::Notify方法post一个event到manager

app通过IMedaiEvent::GetEvent方法获取一个event,这个方法是阻塞的,知道event return或超时

event包含了哪些信息:

一个event code:指明event type

两个附加信息的参数,根据event code的不同,这两个参数可能是不一样的东西,指针,返回码,引用时间等等

需要注意的是:在app使用GetEvent之后,要使用IMediaEvent::FreeEventParams来释放参数

long evCode;
LONG_PTR param1, param2;
HRESULT hr;
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
switch (evCode)
{
...
}
hr = pEvent->FreeEventParams(evCode, param1, param2);
}

app取消manager对默写event的处理:IMediaEvent::CancleDefaultHandling
app启用manager对默写event的处理:IMediaEvent::RestoreDefaultHandling
当然,如果manager没有对指定event的默认处理,那么上面两个函数都是无效的


23 app接收event

除了上面说的GetEvent接收event,还可以使用windows的消息循环来实现:

当manager接收到一个event时,发送一个消息WM_xxx

app用windows的消息循环来处理

后续若有需要再补齐

24 参考时钟 reference clocks

前面说过了:dshow的出现目的之一是为了解决同步问题,解决方式是graph里统一的参考时钟

任何暴露IReferenceClock接口的对象都可以被当作是参考时钟,播放声音时使用的是硬件时间,回放一个文件时使用的是系统时间.

在dshow中, 一个参考时钟的单位是100ns, 获取当前时钟使用IReferenceClock::GetTime方法, 一个单独的GetTime是没有什么实际意义的,两个GetTime的增量才是有用的.

虽然参考时钟的精度有时是不同的(一般是100ns,实际上会小一点),但GetTime保证是递增的.

这里有一个小问题:GetTime是递增,如果此时参考时钟使用的是硬件时钟,正好遇到了硬件时钟回跳(重新对时导致时间比前面的时间小),这时GetTime返回的一直是之前最后一次报告的时钟,直到硬件时钟增大到比她大时.

默认的参考时钟
如果app选择了一个时钟,就用这个
如果graph包含了一个支持IReferenceClock的live source filter,就用这个
如果不包含live source filter, 如果有任意一个filter支持IReferenceClock,就用这个filter.(如果有播放声音,会自动选取声卡的时钟)
如果上面几个都不满足,那使用系统时钟,基于系统时间

如何设置时钟
app使用IMediaFilter::SetSyncSource方法来设置
使用IMediaFilter::SetSyncSource,参数为NULL, 意思是不使用参考时钟. 某些情况下是为了尽可能快的处理样本sample
使用IFilterGraph::SetDefaultSyncSource来重置默认时钟
当参考时钟改变后,manager会用IMediaFilter::SetSyncSource通知所有filter, app从不会对filter直接调这个接口.


25 时钟时间

dshow里有两种相关的时钟时间: 参考时间和流时间

参考时间 reference time, 就是参考时钟表示的绝对时间

流时间 stream time, graph最后一次启动的时间

当graph状态是running: 流时间等于参考时间减开始时间

当graph状态是paused: 流时间就是暂停时的那个流时间

当graph状态是stopped: 流时间是未定义

在一个seek操作之后: 流时间是0

当一个media sample 有一个时间戳, 这个时间戳的意思就是sample被rendered时的流时间,也被称为pts(presentation time stamp)

当app调用IMediaCtrol::Run时, manager会对每个filter调用ImediaFilter::Run.

26 time stamp

dshow里的时间戳

时间戳是什么:media sample的开始和结束时间,用流时间计量

timestamp有时是指pts. 不是所有格式都用相同方式来使用时间戳:eg:在mpeg中不是所有的sample都有时间戳.

当render filter收到一个timestamp, 她调度render是基于timestamp的.如果sample没有timestamp或是timestamp迟到了,sample会立马render;其他情况下要等待直到sample的start time.等待的接口是IReferenceClock::AdviseTime方法

一般打时间戳都是在source filter或是parse filter里,下面是指导方针:

文件回放: 第一个sample的时间戳是0. 后续的时间戳决定于sample长度,回放速度,格式. filter解析文件,就是要计算正确的时间戳

音视频捕获: 每一个sample都会有时间戳,且这个时间戳等于捕获时的流时间, 相应的有以下注意事项

从预览pin出来的视频是没有时间戳的,为什么会这样? 由于graph的潜在因素,如果视频帧打上时间戳,到达render时绝对会迟到,如果有质量控制,会导致render丢帧.不管怎样 media time是准的.

音频捕获filter用的是自己的buffer,而不是用的音频驱动的buffer.音频驱动在固定间隔时间将数据丢到capture filter的buffer中,这个间隔取决于驱动,但不会大于10ms. audio sample 上的timestamp表示驱动将数据丢到audio capture filter缓冲的时间. 这个时间有可能有些许不准,尤其是app用了一个非常小的buffer

mux filter: 依赖于输出格式,mux filter可能会生成timestamp,也可能不会.eg: avi文件,用了一个固定的帧率,而没有使用timestamp, 而在播放的时候,就会生成timestamp(就像上面的文件回放一样)

给sample打timestamp,调用IMediaSample::SetTime方法

媒体时间 media times

这是一个可选的,filter可以给sample指定一个media time.在video stream中media time表示帧数;在audio stream中media time表示采样数

renderer和mux 可能会根据media time来决定丢帧,不管怎么样,filter没有要求设置media time,为一个sample设置media time使用IMediaSample::SetMediaTime方法

27 了解live sources

实时数据源,也称为push source. eg:视频采集和网络广播.一般地,live source不能控制数据到达的速率

一个filter怎么才能被称为live source?

需要满足以下条件之一即可

调用IAMFilterMiscFlags::GetMiscFlags方法返回AM_FILTER_MISC_FLAGS_IS_SOURCE,且至少有一个output pin暴露了IAMPushSource接口

暴露了IKsPropertySet接口,且有一个捕获pin(PIN_GATEGORY_CAPTURE)

如果live source提供了时钟,那么manager会使用这个时钟作为参考时钟, 就像上面说的那样

延时 latency

filter的延时主要是处理sample花的时间.对于live sources来说,延时由sample的buffer大小决定

eg: 经过graph, 视频延时33ms,音频延时500ms,那每一帧视频到达video render要比响应的audio sample要早470ms, 除非graph来补偿,否则音视频永远不同步

live source 同步的接口是IAMPushSource. manager要同步live source,除非先调用IAMGraphAtreams::SyncUsingStreamOffset来启动同步模式.如果开启同步,manager会对每个filter调用IAMPushSource. 如果filter支持IAMPushSource,manager可以通过IAMLatency::GetLatency方法来获取filter的延时,manager知道每个filter的延时之后,就知道了整个graph的延时,之后调用IAMPushSource::SsetStreamOffset让每个source filter知道offset,更具体一点就是告诉打时间戳的filter.

上面音视频的同步主要用于预览, 值得注意的是:预览pin中出来的sample没有时间戳,因此如果要用上面的音视频同步策略,就必须用capter pin出来的数据.

IAMPushSource接口已经支持vfw 和 audio filter

速率匹配 rate matching

如果sample捕获的时候用一个参考时钟,render的时候用另一个参考时钟,那就会出现问题.一般live source不会控制生产的速度,只能让render来进行一个速度匹配.

现今,出现这种问题的主要是audio, 对于audio 一般有以下规则:

如果graph没有使用参考时钟,那么audio render就不需要尝试去 match rate. (如果没有参考时钟,那sample来了就立马render了,这时并不关心速度)

graph有时钟的情况, audio render检查是否有live source, 如果没有,就不需要match rate

audio检查到有live source的情况, 并且live source支持IAMPushSource接口, audio render调用IAMPushSource::GetPushSourceFlags,返回值如下:

AM_PUSHSOURCECAPS_INTERNAL_RM: 这个标志表示live source自己有一套rate-match的机制,所以audio render不需要match rate

AM_PUSHSOURCECAPS_NOT_LIVE: 这个标志表示这个source filter并不是一个live source, 所以render并不需要match rate

AM_PUSHSOURCECAPS_PRIVATE_CLOCK: 这个表示source filter是基于另一个时钟来打时间戳的,那这个时候是需要match rate.(万事都有例外:如果sample没有时间戳,就忽略这个标志)

如果IAMPushSource::GetPushSourceFlags返回的不是以上flag,而是0,那么audio render的行为要基于graph时钟以及sample是否有时间戳来决定:

如果render使用的不是graph时钟,且sampel有时间戳: 进行match rate

如果sample没有时间戳,render会试图进行match rate

如果audio使用的是graph时钟,也会进行match rate

28 graph的动态创建

graph已经构建好了,需要更改,就是动态构建,流程是先停止graph,修改,重启graph,这是最好的方式

特殊情况下,需要在graph不停止的情况下修改graph, eg:

在回放中增加一个视频特效处理filter

source filter更改了媒体类型,接下来需要一个新的解码filter

app在graph增加了一个新的视频流

以上情况都属于动态graph构建,一句话:在graph运行中修改graph就是动态构建.

那么动态构建分三种情况:

动态格式改变: 只是格式改变,没有修改graph里的filter

动态重连: 在graph里增删filter

filter链: 增删控制一个filter链

29 graph动态构建之重连

就是在graph里增删filter,这时pin是要重连的,也可以理解为filter重连

一般dshow的graph运行时pin不能重连,graph需要在pin重连之前先停止graph.万事都有例外,有些filter就是支持pin重连,这个过程称为动态重连

动态重连要么由app完成要么由graph完成

eg: filter a –> output pin 001

input pin 002 –> filter b –> output pin 003

input pin 004 –> filter c

如果filter a连着b和c,这时要删掉b,而且是动态的,首先需要满足以下几点:

filter c的input pin 004需要支持IPinConnect接口,没有这个接口,就不能在运行过程中重连

filter a的output pin 001 在重连出现时,需要有阻塞媒体数据的机制,换句话说就是重连时不能有数据从001到004,一般pin 001必须支持IPinFlowControl接口,这个接口用于在重连时阻塞数据

动态重连的过程:

001阻塞数据

001和004重连,001到004中间有可能智能补全

解除阻塞,让数据流下去

动态重连的具体代码细节,后续若有需要再补齐

30 graph动态构建之filter链

首先要搞明白什么是filter链: 一个filter的集合,需要满足一下条件

每一个filter最多只有一个output pin,一个input pin

每个filter都连接一起,没有连在一起的不算

后续若有需要再补齐

31 plug-on distributors

简称pids, 是一种扩展manager功能的方法,利用com对象实现

后续若有需要再补齐

32 使用dshow

前面31个问题都是对dshow的一个介绍,接下来主要是结合理论的代码

001 使用graphedt模拟一个graph

graphedt主要是用于调试的工具, 构建graph的可视化工具

002 外部程序加载graph

graphedt 可以加载外部程序创建的graph, win2000之后就开始支持了, vista之后需要先注册proppage.dll

后续若有需要再补齐

003 保存filter graph

利用graphedt保存graph,后缀是.grf, 这么做是用于调试app

也可以在app中保存graph,然后在graphedt中调试

后续若有需要再补齐

004 加载graph文件

上面是保存.grf, 这是用app加载.grf文件, 是使用IPersistStream接口完成的

后续若有需要再补齐

005 graph文件格式

后续若有需要再补齐

006 dshow里面的video render

dshow里提供了多种filter来render video:

video renderer filter: 适用于所有支持dirextX的平台,没有特殊的系统要求.

如果可以,filter会使用directdraw来进行render,如果不行就使用gdi.

在xp以前是默认video renderer

video mixing renderer filter 7: vmr-7, xp可用,xp的默认video renderer,使用directdraw7来render

相比上一个,提供了很多强大的功能

video mixing renderer filter 9: vmr-9, 使用direct3D 9来render

适用于所有支持directX的平台,并不是默认的video renderer,因为她比第一个filter有更多的系统要求

overlay mixer: 为dvd和视频广播设计的filter,支持vpes,MPEG-2的硬件编码,数字电视视频直接丢到显卡.

enhanced video renderer: evr, vista之后的产物, 相比上面几个render,这个有更高的视频性能

一般,vista之后的系统使用evr更合适,而vmr-9适用于全平台.

好吧说到render,dshow里有两种render模式: windowed mode 和 windowless mode

一个是render自己创建一个窗口,一个是render直接画在app提供的窗口上面

video render filter只支持windowed mode, vmr-7, vmr-9支持两种模式,为了向后兼容,她们默认都是windowed mode,不过优先选择还是windowless mode,再到EVR就只支持windowless mode了.

007 如何选择正确的video renderer

EVR: 使用direct3D 9, 要求vista及以后

VMR-9: 使用direct3D 9, 需要xp及以后

VMR-7: 使用directDraw, xp

overlay mixer: directDraw,支持硬件覆盖(直接把数据丢到显卡)

video renderer filter: directdraw或gdi. 这种方式已经不推荐了

选择哪种renderer主要看要支持的平台, VMR-7现在用的也少,win10上已经很少看到了
在vista及以后,推荐使用evr(如果硬件支持的话), evr性能卓越,主要是为了dwm设计. 退而求其次,选择vmr9或vmr7
vista之前优先使用vmr9,退而求其次使用vmr7
老系统,可以使用overlay mixer或是video renderer filter.

IGraphBuilder::Render和RenderFile方法默认使用VMR-7,如果硬件不支持VMR-7, 会使用video renderer filter.
EVR和VMR-9从来都不是默认的renderer,这是为了向后兼容,不过条件允许,最好还是使用EVR和VMR-9


008 深入了解windowed mode

重复一下:video renderer filter只使用windowed mode; vmr7和vmr9默认是windowed mode,但也支持windowless mode; EVR只支持windowless mode

windowed mode, 由render自己创建一个窗口,窗口属性是top-level,带边框,标题栏, pop

好吧,再啰嗦一下基本概念: 要创建一个dshow的app,主要的步骤有:构建一个graph, 启动/停止,释放资源.

构建graph: 像上面说的,可以render,renderfile,还可以完全自己添加filter构建,不管是智能构建还是半智能构建还是全人工构建,这步的目的是弄出一个graph.

启动/停止: 这步可以通过dshow暴露的多个接口完成, 不过一般使用IMediaControl来简单控制就ok了,不够用的可以翻msdn

释放资源: 这步主要是释放com对象,也有dshow api指定要释放的.

接下来对着windowed_mode工程说一下, 要实现windowed mode,总共有5个步骤:

http://download.csdn.net/download/bai_lu_sz/9759823(windowed_mode工程)

在OnInitDialog中处理一下dshow的初始化:graph对象的创建,com环境等

windowed mode的处理全部放在test按钮的处理中OnBnClickedBtnTest,

接下来直接调用graph对象的RenderFile方法来智能构建一个完成的graph(注意上面一个是创建graph对象,一个是构建graph,这个构建就是用多个filter组成一个graph,前一个创建是创建一个com对象,后一个构建的graph是dshow里的一个概念)

graph构建好之后如果调用控制接口的Run就会弹出一个窗口,然后显示音视频,而此刻讨论的中心是windowed mode,也就是把弹出的这个窗口作为一个子窗口嵌入到另一个窗口中,而不是直接在指定的窗口绘制数据,所以windowed mode要做的工作主要是graph构建完成之后,Run之前处理.

i: 获取IVideoWindow对象, IID_IVideoWindow

ii: 设置一个父窗口(想让这个子窗口嵌入到哪个窗口就把那个窗口设为这个子窗口的父窗口)

iii: 更改子窗口的风格(默认的子窗口是pop的,要嵌入,需要设置为child)

iiii: 定位子窗口,并处理一下WM_MOVE消息

iiiii: 清理(包括暂停graph,隐藏子窗口,重置子窗口的拥有者属性)

总的来说,大部分设置在graph构建之后,run之前设置就ok了

工程中添加了一下其他东西,也属于比较重要的(反正对着msdn写出来的有问题,主要是很多细节藏在其他地方):

第一个:RenderFile之后的清理操作, 函数有一个属性是可重入性,就是可以调多次,所以清理操作很重要.msdn上写的清理,只是释放了资源,但是graph中的那么多filter是没有处理的,如果这个时候再RenderFile一个文件,那么两个文件都会播放,msdn上有写的,如果要干净的,就需要把graph里的所有filter全部删掉

第二个:当文件播放完或是出现问题之后的处理,为了不出现卡住的情况,msdn上有两种处理方式,一种是利用mfc的消息循环,一种是用dshow的方法,例子中选择了mfc的消息循环

009 深入了解windowless mode

就像上面讲的一样,vmr7和vmr9都支持windowless mode,evr只支持windowless mode. 这种模式是IVideoWindow的重大改进.

如果是说是重大改进,那windowed mode必定是存在重大缺陷才发出了windowless mode:

最重要的是线程间的消息发送可能会导致死锁

manager会将某些windows消息(例如WM_PAINT)丢给video renderer filter. app会通过IVideoWindow让manager保持正确的状态.

如果视频窗口(可以理解windowed mode子窗口嵌入的父窗口)要接收鼠标键盘事件,就必须要设置一下子窗口,让子窗口把消息转到视频窗口.这个设置称为message drain

为了防止复制问题,视频窗口必须有正确的窗口风格

为了解决上面几个问题,windowless mode是用directdraw直接把数据绘制在指定的窗口中.这样少了一个中间的子窗口,效果好多了.主要是明显少了很多死锁.vmr在使用windowless mode时并没有暴露IVideoWindow接口,因为没有必要.

vmr7和vmr9虽然暴露的是不同的接口,但是步骤都是一样的

vmr-9用的是direct3d,所以要包含两个头文件:d3d9.h和vmr9.h

和上面的windowed mode一样,还是对着例子来:

http://download.csdn.net/detail/bai_lu_sz/9760769 以vmr9为例子

因为指定了video mixer renderer9 filter 这个filter,所以不能想之前那样全自动构建graph,需要先指定vmr9,

i: 创建一个graph对象

ii: 创建一个vmr9 filter并加入到graph, 加入的时候为filter指定一个名字

iii: 通过IVMRFilterConfig9::SetRenderingMode设置VMR9Mode_Winidowless

iiii: 获取IVMRWindowlessControl9接口

iiiii:通过上面得到的接口设置哪个窗口来接收媒体数据,位置,是否裁剪等

接下来还有处理何时绘制,窗口移动时,大小变化时,已经媒体数据分辨率改变时的处理.就是处理WM_PAINT WM_SIZE 等, 必须要修改的是wm_paint.

wm_paint的处理方式msdn上有一个简单的.当绘制的数据覆盖在控件上导致控件闪烁时,需要设置窗口的属性(控件是否绘制属性,这个属性在mfc上设置)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dshow