vlc源码分析--播放速度控制原理,pts,dts

一:解码的速率控制
视频都有帧率,即每一秒应该显示的帧数,怎么根据这个帧率,来匀速地播放视频?播放的时候怎么实现快速播放,而不是跳帧式地快进?探究一下vlc源码里面对帧率和播放速度的控制。(选用只包含一个视频流且帧率的1的视频文件做的分析
https://blog.csdn.net/u012459903/article/details/89416892)

先上结论:
vlc 版本 3.0.5
vlc 由多个 module组成,多线程机制,核心的一个线程,在src/input.c 该线程调用demux的接口 解复用数据,demux模块的这个demux接口解复用完数据,输出 element stream 到 es_out模块。帧速度的控制,在input.c 线程中,根据视频帧率和设置的播放速度,进行延时,然后按指定的时间间隔输出到 es_out,即数据从demux出来的时候,已经是按时间间隔均匀输出了。
要达到指定速度播放,要么输入间接性输入一大块数据,输出取数据的时候匀速取,取完再输入一大块数据。要么输入的时候,就按照指定速度输入数据,输出取数据有数据就取走,显然vlc这里是第一种方式,读文件的时候根据需要匀速读数据,如果用户有需要可以倍速。

对应到代码,即es_out_timeshfit.c中的 Send函数,被demux模块调用的时候,已经是按照 播放显示的时间间隔来调用了,可以添加如下代码到 Send函数的开头来实际查看:

 struct timeval test_time;
 struct tm *st_tm = NULL;
  gettimeofday(&test_time,NULL);
  st_tm = gmtime(&test_time.tv_sec);
  printf("wang timeshiftsend  [%d %s]%d:%d:%d;  tv_usec %d \n",__LINE__,__FUNCTION__,st_tm->tm_hour,st_tm->tm_min,st_tm->tm_sec,test_time.tv_usec);

在es_out_timeshift.c文件加上上诉两个函数的头文件
#include <sys/time.h>
#include <time.h>
如果是fps = 1的存视频文件,按照默认速度播放,上面的输出应该是每秒一次。

实际代码的延时位置,在input.c文件中的输入线程 循环体 MainLoop()
{…
  while()
  {…
    for( ;; )
    {…
      ControlPop( p_input, &i_type, &val, i_deadline, b_postpone )
      {
        vlc_cond_timedwait( &p_sys->wait_control, &p_sys->lock_control,
i_deadline )
      }
    }
  }
}

上面的vlc_cond_timedwait函数作用即休眠指定的 i_deadline时间间隔。

Mainloop循环调用MainLoopDemux()函数,MainLoopDemux函数的作用是解复用获得一帧数据,这个函数被调用的频率是实际 播放 帧率的4倍(目前的精度值)。其中的一次调用会得到数据,这应该是一个精度的问题,所以上面的 i_deadline时间间隔,并不是简单的播放速度 两帧的时间间隔,也应该是 1/4的时间间隔.
拿一播放一个 MP4文件来看,demux/mp4.c中的demux函数,会使用到一个 p_sys->i_nztime += DEMUX_INCREMENT;
这个i_nztime 时间戳(vlc中的时间片都已 microsecond 微秒 为单位,即百万分之一秒,单位表示us,非 ms(毫秒)。 按照每一次调用 demux函数就自增 DEMUX_INCREMENT 的值来变化.

上面的 休眠时间间隔的获取,Mainloop 通过调用es_out_GetWakeup,从es_out中获取
/* Get date to wait before demuxing more data */
ES_OUT_GET_WAKE_UP,
即 input线程通过 cotrol从es_out模块获取到相应的休眠时间,进行休眠然后得以按指定频率调用demux模块来解复用数据,在给到es_out.

es_out中关于休眠时间的计算,得益于其中的 input_clock_t 结构,对应的代码在clock.c,外界播放视频使用 libvlc_media_player_set_rate设置播放速度的时候,会修改"rate" 变量值,从而触发改变变量修改时候的回调,进入input.c 的control 的INPUT_CONTROL_SET_RATE分支,转而进入es_out_SetRate, -->ES_OUT_SET_RATE. 休眠的时间间隔就会改变。

二:显示图片时的控制
上面按匀速将数据输入到解码器,解码,然后放到显示队列等待显示,正常情况下当然期望给一张图片他就解码一张,并且及时显示到屏幕,这样看到的才是按照我们设置的 播放速度在播放,不过由于某些原因,比如解码器解码太慢,会导致数据在给到显示端的时候滞后并且堆积,这个时候就要一个机制,要把这些滞后的数据帧给 drop掉,不至于堆积成山,这就需要用到 dts,pts时间戳了。

数据给到解码线程后:
decoder.c文件中,DecoderThread 线程函数
中,调用到DecoderProcess之后,分两种情况,
1: DecoderProcessSout();//数据给到sout
2: DecoderDecode();//Drain
the decoder module 数据下方到解码模块

涉及到四个函数:
DecoderPlaySout();
DecoderPlayVideo();
DecoderPlayAudio();
DecoderQueueSpu();
这四个函数,都会执行两个操作,
DecoderWaitUnblock( p_dec );//条件等待,阻塞
DecoderFixTs(); //修正pts时间戳,里面会用到延时配置数据
上面DecoderProcessSout()里面,给到sout之前会调用DecoderPlaySout()进行处理,
在DecoderDecode函数中,会调用已经注册的解码模块中的p_dec->pf_decode,解码
所有的解码模块的pf_decode接口,比如 codec/avcodec/video.c ffmpeng软件模块,执行完解码输出一帧picture,然后回调 到上层设置的 DecoderPlayVideo();
也就是说解码出数据后,再回调DecoderPlayVideo();或者DecoderPlayAudio(); 进行延时和修正时间戳,再给到输出。 DecoderFixTs修正时间戳用到的 i_ts_delay, 只是 针对具体的基本流进行单独差异化的延时,比如 要让音频 延后 100ms 等待。而不是file-caching和 net-caching,或者是其他的stream_out的delay。

猜你喜欢

转载自blog.csdn.net/u012459903/article/details/89459073