vlc音视频同步
vlc播放里媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断矫正本地时钟
播放音视频时根据本地时钟进行同步播放。首先了解两个概念:stream clock 和system clock,stream clock是流时钟,即打包
RTP中的时间戳,system clock是本地时钟,当前系统的tick。当第一个rtp包来到时
fSyncTimestamp = rtpTimestamp; ///本地时间戳直接赋值rtp时间戳
fSyncTime = timeNo; ///本地同步时钟直接赋值本地时钟,随后需要修正
之后若有rtp到来,则根据上一次rtp包的时间戳差值计算得到的真实的时间差值
double timeDiff = timestampDiff/(double) timestampFrequency;
当RTCP的Sender Report(SR)包到来时,会对fSyncTime进行重置,直接赋值NTP时间戳
fSyncTime.tv.sec=ntpTimestampMSW - 0x83AA7E80 /// 1900/1/1->1970/1/1
double microseconds = (ntpTimestampLSW * 15625.0)/0x040000000; ///
fSyncTime.tv_usec = (unsigned)(microseconds + 0.5);
然后以此差值更新synctime,也就是说live555接收部分的时钟fsynctime既有rtp包时间戳不断地校正,也由rtcp的sr包
不断的赋值修改,在rtsp建立会话并创建解码器的本地时钟,本地时钟是一对时钟,包括stream clock 和system clock
初始值均为invalid
static inline clock_point_t clock_point_Create( mtime_t i_stream, mtime_t i_system )
{
clock_point_t p;
p.i_stream = i_stream;// VLC_TS_INVALID
p.i_system = i_system;// VLC_TS_INVALID
return p;
}
当rtp数据到来时,不仅会更新vlc接收部分的时钟,vlc解码部分的时钟也会通过input_clock_update函数更新,当解码部分根据
判定stream clock出现较大延迟时,还会充值本地时钟对,重置时设置system clock为当前本地时钟tick数,live555接收到
rtp数据后,存入bufferedpacket中,由于rtp封装h264是按照rfc3984来封装的,所以解析的时候按照该协议解析,解析时发
现nalu起始,就会放入一个block_t中,然后推入fifo队列,等待解码线程解码,block_t带有pts和dts,均为RTPSource的 pts
解码时如果视频音频都有的化,会创建两个解码器,每个解码器包含一个fifo,同时会创建两个解码线程(音频和视频),分别从各自的fifo取出数据解码。视频和音频的解码入口都是DecoderThread,从fifo中取出数据进入视频或者音频的解码分支,视频解码线程在解码时会将pts和dts传递给avpacket(modules/codec/avcodec/video.c)
pkt.pts=p_block->i_pts;
pkt.dts=p_block->i_dts;
ffmpeg解码视频后,avframe将带有时间戳,但是这个时间戳是stream clock,之后会把stream clock转换为system clock
static mtime_t ClockStreamToSystem( input_clock_t *cl, mtime_t i_stream )
{
if( !cl->b_has_reference )
return VLC_TS_INVALID;
return ( i_stream - cl->ref.i_stream ) * cl->i_rate / INPUT_RATE_DEFAULT + cl->ref.i_system;
}
同理,音频解码完后,也会进行stream clock到system clock的转换,音频的解码后的数据会直接播放,视频解码完的图像帧会放入图像fifo中,等待渲染线程渲染,渲染县城会根据解码后图像的显示时间,决定时候播放。
......
decoded = picture_fifo_Pop(vout->p->decoder_fifo);
if (is_late_dropped && decoded && !decoded->b_force) {
const mtime_t predicted = mdate() + 0; /* TODO improve */
const mtime_t late = predicted - decoded->date;
if (late > VOUT_DISPLAY_LATE_THRESHOLD) {// 延迟大于20ms,则不予播放,直接释放该图像
msg_Warn(vout, "picture is too late to be displayed (missing %d ms)", (int)(late/1000));
picture_Release(decoded);
lost_count++;
continue;
} else if (late > 0) {// 延迟大于0小于20ms,打印日志并播放
msg_Dbg(vout, "picture might be displayed late (missing %d ms)", (int)(late/1000));
}
}
......
两个解码线程并没有直接联系,他们之间的联系是通过音视频数据包的stream clock转换为 system clock,然后渲染线程和声音播放线程根据本地时钟决定是否要播放当前音视频数据