vlc源码分析(五) 流媒体的音视频同步
vlc播放流媒體時實現音視頻同步,簡單來說就是發送方發送的RTP包帶有時間戳,接收方根據此時間戳不斷校正本地時鐘,播放音視頻時根據本地時鐘進行同步播放。首先了解兩個概念:stream clock和system clock。stream clock是流時鐘,可以理解為RTP包中的時間戳;system clock是本地時鐘,可以理解為當前系統的Tick數。第一個RTP包到來時:
fSyncTimestamp = rtpTimestamp;// rtp時間戳賦值為本地記錄的時間戳 fSyncTime = timeNow;// 本地同步時鐘直接賦值為本地當前時鐘,注意這樣賦值是錯誤的,但隨后就會被RTCP的SR包修正? ? 之后有RTP包到來,則根據上一次RTP包的時間戳差值計算得到真實的時間差值:
// Divide this by the timestamp frequency to get real time: double timeDiff = timestampDiff/(double)timestampFrequency;// 差值除以90KHz得到真實時間? ? 當RTCP的Sender Report(SR)包到來時,會對fSyncTime進行重置,直接賦值為NTP時間戳
fSyncTime.tv_sec = ntpTimestampMSW - 0x83AA7E80; // 1/1/1900 -> 1/1/1970 double microseconds = (ntpTimestampLSW*15625.0)/0x04000000; // 10^6/2^32 fSyncTime.tv_usec = (unsigned)(microseconds+0.5);? ??然后以此差值更新fSyncTime,也就是說live555接收部分的時鐘fSyncTime既由RTP包時間戳不斷的校正,也由RTCP的SR包不斷的賦值修改。
? ? 在RTSP的Session建立時會創建解碼器的本地時鐘,本地時鐘是一對時鐘,包括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_INVALIDp.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來封裝的,所以解析的時候按照該協議解析H264數據,解析時發現NALU起始,就會放入一個block_t中,然后該block_t就被推入以block_t為單位的數據fifo(src\misc\block.c)中,等待解碼線程解碼。block_t帶有pts和dts,均為RTPSource的pts。
? ? 解碼時如果視頻音頻都有的話,會創建兩個Decoder,每個Decoder包含一個fifo,同時會創建兩個解碼線程(視頻和音頻),分別從各自的fifo中取出數據解碼。視頻和音頻解碼入口都是DecoderThread,從fifo中取出數據數據進入視頻或者音頻的解碼分支。視頻解碼線程在解碼時會將block_t的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(src\misc\picture_fifo.c)中,等待渲染線程渲染。渲染線程會根據解碼后圖像的顯示時間,決定是否播放:
......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,然后渲染線程和聲音播放線程根據本地時鐘決定是否要播放當前音視頻數據。
總結
以上是生活随笔為你收集整理的vlc源码分析(五) 流媒体的音视频同步的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS详解top命令各个数据的含义
- 下一篇: 有关/etc/resolv.conf、/