vlc源碼分析(五) 流媒體的音視頻同步

http://www.cnblogs.com/jiayayao/p/6890882.htmlhtml

 

    vlc播放流媒體時實現音視頻同步,簡單來講就是發送方發送的RTP包帶有時間戳,接收方根據此時間戳不斷校訂本地時鐘,播放音視頻時根據本地時鐘進行同步播放。首先了解兩個概念:stream clock和system clock。stream clock是流時鐘,能夠理解爲RTP包中的時間戳;system clock是本地時鐘,能夠理解爲當前系統的Tick數。第一個RTP包到來時:ide

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時間戳this

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包不斷的賦值修改。spa

    在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_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來封裝的,因此解析的時候按照該協議解析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):code

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)中,等待渲染線程渲染。渲染線程會根據解碼後圖像的顯示時間,決定是否播放:htm

複製代碼
            ......
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,而後渲染線程和聲音播放線程根據本地時鐘決定是否要播放當前音視頻數據。

相關文章
相關標籤/搜索