DTS和PTS(HLS音視訊同步)
原由:
- 近來在研究HLS(HTTP Live Streaming),以實現android上播放m3u8檔案。由於TS段的切分不統一,每個視訊網站給出的m3u8 playlists總有差別,在時間戳顯示上有差異,所以對DTS和PTS進行了研究。
- DTS和PTS是音視訊同步的關鍵技術,同時也是丟幀策略密切相關。
dts/pts定義 dts: decoding time stamp pts: present time stamp 在ISO/IEC13818-1中制定90k Hz 的時鐘,如果編碼幀頻是30,那麼時間戳間隔就該是90000 / 30 = 3000。 在FFMPEG中有三種時間單位:秒、微秒和dts/pts。從dts/pts轉化為微秒公式:
dts* AV_TIME_BASE/ denominator
其中AV_TIME_BASE為1,000,000,denominator為90,000。 拿到m3u8播放列表後,首先進行解析。HTTP Live Streaming標準草案可以從這裡http://tools.ietf.org/html/draft-pantos-http-live-streaming-08檢視。 解析程式碼在ffmpeg/libavformat/hls.c中
parse_playlist原始碼解析播放列表的問題:
當解析到#EXT-X-TARGETDURATION標籤時,後面緊跟著的是TS段的最大時長,當前沒有什麼用。#EXTINF標籤後緊跟的是當前TS段的時長,當EXT-X-VERSION標籤大於等於3時,TS段的時長可以為小數,當前(2012-07-26)的FFMPEG程式碼還不支援EXT-X-VERSION標籤的判斷,TS段的時長也為整數。seg->duration儲存了當前段的時長,單位為秒。當前草案中還有EXT-X-DISCONTINUITY標籤,它表徵其後面的視訊段檔案和之前的不連續,這意味著檔案格式、時間戳順序、編碼引數等的變化。但是很遺憾,當前FFMPEG仍然不支援,這意味著該標籤出現後,後續的PES中攜帶的dts和pts將重新從零開始計數。
HLS上下文中存在當前的段序號,在HLS.c檔案中,hls_read()函式根據判斷得到當前段讀取完畢後,將cur_seq_no加一,從而讀取下一個TS段。在hls_read_packet()函式讀取一個packet,該packet包含一幀可被解碼的影象,或者一幀或多幀音訊。
hls_read_packet原始碼這裡c->seek_timestamp為標誌位,它表徵當前視訊發生了SEEK事件,當發生SEEK事件後首先呼叫hls_read_seek()函式定位到應該讀取的TS段,更新HLS上下文中的段序號。當讀取到該段的packet,有兩種判斷。 在ffplay中,當外界發起seek請求後,將執行以下操作。
- 呼叫avformat_seek_file(),完成檔案的seek定位
- 清空解碼前packet佇列(音訊、視訊、字幕)
- 呼叫avcodec_flush_buffers(),清空解碼buffer和相關狀態
在第一個步驟中,將在HLS層進行seek操作,seek流程圖如下圖所示:
首先讀取packet,判斷是否有seek操作,沒有則直接將該packet返回,送人後續的解碼操作。如果是seek情況,則讀取dts時間戳,如果dts沒有值,則直接清除seek標誌並返回packet(問題一)。如果dts時間戳有值,則將該值轉化為微秒並與seek傳入的時間進行比較,看是否大於seek時間,如果大於則表明讀取的packet達到了seek要求(問題二),否則繼續讀packet。如果seek時間已經滿足,則看該packet的flags是否是關鍵幀,如果是則返回該packet(問題三),否則繼續讀packet。
該流程很簡單,但是帶來了三個問題。分別解釋
- 問題一,如果dts沒有值,返回回去後,解碼狀態全部進行了reset,則送入的第一幀資訊應該為關鍵幀,否則該幀需要參考其他幀,產生花屏。
- 問題二,如果dts時間戳有誤,將出現dts轉化為微秒後永遠小於seek傳入時間問題,則永遠無法返回packet,導致seek僵死。
- 問題三,判斷packet是否為關鍵幀,忽略了該packet是否為視訊,如果該packet為音訊並且flag & AV_PKT_FLAG_KEY的結果為真,則將返回該packet並清空seek標準。後續讀到的視訊也有非關鍵幀的可能,從而導致花屏。