音视频同步的简要总结

来源:互联网 发布:mac打开app store很慢 编辑:程序博客网 时间:2024/05/16 18:02

平台为嵌入式音视频解码器,使用linux作为操作系统,音频采用alsa架构。

一、STC(本地时钟作为一个基准)
如果是ts封装,可以使用PCR;如果为RTP封装,可以使用音频时间戳或者rtcp的本地时钟。
原理就是将PCR或者音频时间戳(为了音频play时自己使用,要有一个偏移,但只要是同源就行)同步到本地STC中。
本地STC在解码端可以使用一个硬件STC维护多组,就是使用time_base+delta的方法。或者使用软件的定时器也可以,就是精度差一些。

二、缓冲
这个是比较重要的,多长时间的缓冲,决定你能抗击的时间波动。
没有缓冲无法进行同步。
可以使用buffer Tab的方式,循环使用。

三、视频
视频同步策略相对比较简单,就是当显示或者解码之前,将此帧的PTS与本地STC进行对比,delta = PTS - STC
如果delta < 0 则说明STC大,则此帧的播放时间已过,则需要加快速度追上去,所以将此帧丢弃不播放。
如果delta 近似为 0,则说明正好播放此帧,则直接播放;
如果delta > 0 则说明此帧的播放时间还没到来,所以需要先睡一会儿,再播。如果想提高精度,在睡的时候,可以1ms判断一下是否时间已到。注意睡的时间要有上限,防止PTS错误导致睡眠时间超长。

四、音频
音频比较麻烦,如果使用与视频近似的策略,由于ALSA架构的音频播放底层还有一个缓冲,如果这个底层缓冲空了的话,就会出现 underRun,属于音频播放的一种异常,会造成可听见的嗒嗒声出现。
如果采用视频的策略,则在睡过后进行播放时,很有可能出现这种underrun。
如果不进行同步,则无法准确的与视频唇齿一致。
总体来讲 ,同步音频应该采用少干涉的策略,就是在每次线程init音频设备后,进行一次缓冲操作,使用此帧的PTS作为基准,知道STC到达PTS时间后,再进行无干涉的循环播放,这是音频缓冲有一定数量的音频帧,播放时间也是准的。
在播放过程中,检测alsa写数据的时候的返回值是否为 -EPIPE,如果是,则发生了underRun,则退出这一级循环,同时deinit掉音频设备,再init音频设备,然后再缓冲一次,然后再进入循环播放,直至再次发生underRun。


static void audio_buffering(ipc_handle ipc_in, audio_play_obj *obj)
{
 //音频解码还没有送入音频帧时,一直等待
 while(get_ipc_numbers(ipc_in) <= 0)
 {
  mssleep(20);
  //避免死循环
  if (obj->startStop == 0)
  { 
   return;
  }
  continue;
 }
 //获取一帧,以及此帧时间戳
 buf = get_a_audio_frame_from_ipc(ipc_in);
 ts = get_timestamp_from_buffer(buf);
 
 while((stc = getSTC(chnId)) != 0)
 { 
  //只到STC追上此帧,则进行播放,9000为90K时钟100ms的时间
  if ((ts > (stc - 9000)) || (get_cached_frame_num_from_ipc(ipc_in) < 2))
  {
   mssleep(20);
   continue;
  }
  break;
 }
 
 release_a_frame_to_ipc(buf);
}


void pthread_audio_play(void)
{

while(1)//主循环
{

handle->initDev = 1;
while(handle->initDev)
{
 //初始化音频设备,设置hw、sw参数,open设备
 init_deivce(&handle);
 
 //进行缓冲操作
 if (need_sync)
 {
  //先清空缓冲,防止缓冲上溢
  fflush(ipc_in);
  //一直缓冲到STC==PTS为止
  audio_buffering(ipc_in, handle);
 }
 handle->startStop = 1;

 while(handle->startStop)
 {
  //从缓冲中获取一帧
  buf = get_a_audio_frame_from_ipc(ipc_in);

  //播放这帧
  if (audio_play(buf) < 0)
  {
   //退出两级循环,如果只退出一级循环,则buffering过后,还会遇到underRun,所以需要重新init设备才可以
   startStop = 0;
   initDev = 0;
  }
  //向IPC归还buffer
  release_a_frame_to_ipc(buf);
 }

 deinit_device(handle); 

}
}
}

0 0
原创粉丝点击