【续前一篇文章】
6 time and clocks in directshow
6.1 reference clocks
参考时钟是filter graph manager用来同步所有filter的。任何一个暴露了ireferenceclock 接口的对象都可以作为参考时钟。参考时钟可以是filter提供,例如声卡就可以提供一个硬件的时钟。作为应变,filter graph manager也可采用系统的时间。名义上,参考时钟的精确度在100纳秒,但实际上,没有那么精确。调用ireferenceclock::gettime可以获取时钟的当前时间。时间的基准是开始计时的时间,根据实现的不同,gettime的返回值不是绝对的。关键的是从时间开始的变化量。
尽管时钟的精确性还有所变动,但是gettime方法返回的保证时间是增加的。也就是说,时钟不会倒退回去,比如,对硬件时钟进行了调整,gettime方法就返回上次的时间,直到硬件时钟赶上。更多信息可参考cbasereferenceclock类。
6.1.1 default reference clock
当graph运行的时候,filter graph manager会自动选择一个参考时钟的,选择的法则如下
1、如果应用程序选择了时钟,就采用应用程序选择的时钟
2、如果graph包含活动的源filter,并且有ireferenceclock接口,那么就用这个时钟。
3、如果graph没有活动的源filter,就用graph中任何有ireferenceclock接口的filter,选择的方法是从renderers逆流向上,连接的filter优先,没有连接的filter次之。
4、如果没有任何filter符合条件,就采用系统参考时钟system reference clock
6.1.2 setting the reference clock
应用程序可以调用filter graph manager的接口imediafilter的setsyncsource方法来设置新的参考时钟。只有因为特殊原因需要设置其他时钟时才调用此函数。
如果你给setsyncsource传递的参数为null,graph不设置任何参考时钟。如果想恢复缺省的时钟,调用ifiltergraph::setdefaultsyncsource。当graph的参考时钟改变时, filter graph manager调用imediafilter::setsyncsource通知所有的每个filter。
6.3 clock times
directshow定义了两个相关的时间:参考时间和流时间。
·参考时间:是参考时钟返回的绝对时间
·流时间:和graph最近异常开始运行的时间有关。
·当graph正在运行,流时间就等于从开始时间减去开始时间
·当graph暂停,流时间就等于它暂停开始的时间
·在进行seek操作后,流时间重新设置为0
·当graph停止时,流时间没有定义。
当一个sample具有时间戳t,就表示sample应该在流时间t播放,因此流时间也叫播放时间。
应用程序调用imediacontrol::run运行graph时,filter graph manager会调用每个filter的imediafilter::run方法。为了补偿调用每个filter的时间延迟,filter graph manager会指定一个稍微将来的时间进行补偿。
6.4 time stamps
时间戳定义了媒体sample的开始和结束时间,用流时间表示。时间戳也叫播放时间。通过后面的知识你会了解到,并不是所有格式的数据流都采用同一种样式的时间戳。比如,并不是所有的mpeg sample都有时间戳。在mpeg filter graph,时间戳并不应用在每帧上直到它们从解码器输出。
当render filter接收到sample,根据sample的时间戳进行提交。如果sample达到晚了或者没有时间戳, filter立即提交sample。否则,在提交sample前,filter会一直等到sample的开始时间。(通过调用ireferenceclock::advisetime方法等待开始时间。)
source filter和parse filter有责任给处理的sample设置正确的时间戳。使用如下规则:
·文件回放:第一个sample的时间戳为0,随后的时间戳根据sample的长度和播放的速率来确定。这些又由文件格式确定。解析filter有责任计算正确的时间戳(例如avi splitter)。
·视频和音频的捕捉:每个sample都打上开始时间,它等于捕捉的流时间。注意下面两点:
·预览pin(与捕捉pin相反)出来的视频帧没有时间戳。因为graph延迟,打上捕捉时间的视频帧到达视频renerer时总是有延迟。这样就会使renderer丢帧以尝试质量控制。关于质量控制可参考quality-control management。
·音频捕捉:音频捕捉filter使用自己的缓冲,与音频驱动所使用的不同。音频捕捉驱动以固定间隔时间填充捕捉filter的缓冲。间隔时间取决于驱动,但通常不超过10毫秒。在音频sample上的时间戳反应了驱动填充捕捉filter缓冲的时间。这些时间有稍微不准确,特别是如果应用程序使用更小的缓冲。但是,媒体时间能准确反应缓冲中音频sample的数量。
·mux filter: 根据输出数据流的格式,mux filter可能需要生成时间戳,也许不需要。比如avi文件格式使用固定的帧率,没有时间戳,因此avi mux filter假定sample都是在大约正确的时间达到。如果到达时间比时间戳晚就会丢帧。而对于文件回放,新的时间戳是运行时生成的。
可以通过调用imediasample::settime来给sample设置时间。
另一个可选功能是filter可以给sample指定media time。在视频流,media time表示帧数。在音频流,表示数据包中的sample数量。比如,如果每个包有1秒的444.1khz的音频,第一个包的媒体开始时间是0媒体结束时间是44100。在支持seek的流,总是与流的开始时间有关。比如,如果对一个15fps的视频从开始定位到2秒。定位后的media sample的时间戳是0但是media time是30。
renderer和mux filter可通过检测间隔用media time判断帧或者sample是否被丢弃。但是,filter并不要求设置media time。设置media time可调用imediasample::setmediatime.
6.5 live source
活动的live filter,就是推模式的源,实时的接收数据。视频捕捉和网络广播就是例子,活动源通常无法控制数据流得速率。
filter如果有下面的任意一个特征,通常被认为是live filter:
1、iamfiltermiscflags::getmiscflags时返回am_filter_misc_flags_is_source,并且至少有一个输出pin暴露了iampushsource接口
2、filter暴露ikspropertyset接口,并且有个捕捉pin(pin_category_capture),更多信息可参考pin property set.
如果live source filter提供时钟,那么filter graph manager首先采用它作为参考时钟。
6.5.1 latency
filter的反映时间就是filter处理sample所花费的时间。对于live source,反应时间由sample的内存大小决定。例如,假设一个filter有一个33ms反应时间的视频源,一个500ms反应时间的音频源,每一个视频祯(video frame)比相应的音频sample要早470ms,除非graph进行补偿,否则视频和音频是不同步的。
live source可以通过iampushsource接口来进行同步。filter graph manager在应用程序调用iamgraphstreams::syncusingstreamoffset方法后就不会对源进行同步。如果开启同步,filter graph manager在每个source filter查询iampushsource接口,如果支持接口,filter graph manager就用iamlatency::getlatency来得到filter期望的反应时间。(iampushsource从iamlatency继承)。通过组合这些反应值,filter graph manager 决定graph的最大反应时间。然后调用iampushsource::setstreamoffset给每个源filter设置一个数据流偏移时间,filter给它产生的sample打时间戳的时候会加上偏移时间的。
这种方法主要用于实时预览。但是,实时捕捉设备的(比如摄像机)并不在sample上设置时间戳。因此,在实时捕捉设备上用此方法,必须从捕捉pin进行预览。可参考directshow video capture filter. 现在,vfw capture filter 和 audio capture filter.都支持iampushsource接口。
6.5.2 rate matching
当render filter利用参考时钟安排播放顺序的时候,如果源filter采用另一种时钟,在重放的时候就会发生故障。播放的速度大于源产生的速度,就会产生间隙停顿,或者播放速度小于源的产生速度,就会形成数据的堆积,直到graph丢帧。源一般来说是无法控制数据的产生速度的,因此,播放速度要随着源的速度改变而改变。
现在,只有在音频播放filter才能进行速率匹配,因为音频中的故障比视频中的更容易捕捉到。为了匹配播放速率,音频renderer必须要选择一个匹配标准。使用如下规则:
·如果graph没有使用参考时钟,没法进行速率匹配。(只要没有参考时钟sample都是立即提交)
·如果graph有参考时钟,renderer的上一级必须要有一个活动的源。否则不进行匹配。
·如果上一级有live source, 并且支持iampushsource接口,调用getpushsourceflags:
·返回am_pushsourcecaps_internal_rm,表示source有自己的匹配机制
·返回am_pushsourcecaps_not_live,表示不是live source,即使有iampushsource接口,因此audio renderer不匹配。
·返回am_pushsourcecaps_private_clock,表示source用私有时钟生成时间戳。此时,audio renderer用时间戳匹配。(如果没有时间戳,但是renderer忽略flag).
·如果getpushsourceflags返回0,播放filter就根据graph时钟和sample的时间戳来自己决定播放速率。
·如果audio renderer不是graph时钟并且sample有时间戳,就与时间戳匹配。
·如果sample没有时间戳,audio renderer尝试与进入audio data的频率匹配。
·如果audio renderer是graph时钟,尝试与进入audio data匹配。
最后一种情况的原因是:如果audio renderer是参考时钟,source filter用同样的时钟生成时间戳,然后audio renderer不能与时间戳匹配。如果这么做了,就可能尝试与自己匹配,这样会引起时钟混乱。因此,audio renderer就与进入audio data的频率匹配。