HLS流在播放時是先解協議(hls.c)后解封裝(mpegts.c),libavformat下的hls.c和mpegts.c實際上是同一個級別的,同屬於demuxer。
一、解HLS協議
1. FFmpeg代碼分析
首先看一下ff_hls_demuxer的定義:
AVInputFormat ff_hls_demuxer = { .name = "hls,applehttp", .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"), .priv_class = &hls_class, .priv_data_size = sizeof(HLSContext), .read_probe = hls_probe, .read_header = hls_read_header, .read_packet = hls_read_packet, .read_close = hls_close, .read_seek = hls_read_seek, };
(1)FFmpeg在拿到hls流后,它一開始並不知道該用哪個demuxer,這個時候它會進行probe,即依次調用demuxer的read_probe函數,選擇返回分數最高的一個demuxer,最后選定ff_hls_demuxer。AVFormatContext中的s->iformat->priv_data一般是指demuxer內部的結構體,在解HLS協議時,這個priv_data就是HLSContext;
(2)之后FFmpeg會調用read_header函數,即執行hls_read_header,獲取hls的播放列表,並賦值到HLSContext結構體中;
(3)當准備工作妥當后,FFmpeg會調用hls_read_packet讀取數據,傳遞給上層;
(4)如果有seek操作會執行hls_read_seek;
(5)視頻關閉時,FFmpeg會調用hls_close;
2. discontinue字段
FFmpeg3.4沒有支持HLS標准的discontinue字段。discontinue字段常用於ts播放列表里插入一段廣告,這段廣告的參數可以與正片的參數不一致。上層在識別到這個參數后,可以重置解碼器。對於iOS,需要重新創建解碼器,對於Android,解碼器兼容了這種情況,無需重新創建解碼器。
二、解ts封裝
以mpegts.c為例,probe一般是將讀取到的probe數據與ts格式對比,如果是ts格式則返回高分數,上層選擇最高的分數的demuxer;
PES包:分割打包的ES流,加入了PES頭。
struct MpegTSFilter { int pid; int es_id; int last_cc; /* last cc code (-1 if first packet) */ int64_t last_pcr; enum MpegTSFilterType type; union {// 一個Filter是下邊的一種類型 MpegTSPESFilter pes_filter; MpegTSSectionFilter section_filter; } u; };
handle_packet函數處理一個ts包,在函數中switch case語句中處理ts包。一般是先處理ts header, 之后是pes header,代碼中狀態定義:
/* TS stream handling */ // 標識TS流狀態 enum MpegTSState { MPEGTS_HEADER = 0, MPEGTS_PESHEADER, MPEGTS_PESHEADER_FILL, MPEGTS_PAYLOAD, MPEGTS_SKIP, };
從av_read_frame讀到的pkt,其音頻和視頻的pts是連續的,但是兩者之間不是連續的,因為pts乘以時基才是真正的顯示時間,比如如下打印日志,pkt時間進行換算后,可以看到其整體pts是連續的。
martinjia time_base:1 30000, pkt index:0, pkt pts:83083(pts換算后:2.76s)
martinjia martinjia time_base:1 48000, pkt index:1, pkt pts:118784(pts換算后:2.47s)