HLS協議解析


1. 綜述

HLS(HTTP Live Streaming) 把整個流分成一個個小的基於 HTTP 的文件來下載,每次只下載一些。HLS 協議由三部分組成:HTTP、M3U8、TS。這三部分中,HTTP 是傳輸協議,M3U8 是索引文件,TS 是音視頻的媒體信息。

關於 HLS 的詳細介紹可參考: HTTP Live Streaming draft-pantos-http-live-streaming-18

HLS 是提供一個 m3u8 地址,Apple 的 Safari 瀏覽器直接就能打開 m3u8 地址,譬如:

http://demo.srs.com/live/livestream.m3u8

Android 不能直接打開,需要使用 html5 的 video 標簽,然后在瀏覽器中打開這個頁面即可,譬如:

<!-- livestream.html -->
<video width="640" height="360"
        autoplay controls autobuffer 
        src="http://demo.srs.com/live/livestream.m3u8"
        type="application/vnd.apple.mpegurl">
</video>

HLS 的 m3u8,是一個 ts 的列表,也就是告訴瀏覽器可以播放這些 ts 文件,譬如:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:64
#EXT-X-TARGETDURATION:12
#EXTINF:11.550
livestream-64.ts
#EXTINF:5.250
livestream-65.ts
#EXTINF:7.700
livestream-66.ts
#EXTINF:6.850
livestream-67.ts

有幾個關鍵的參數,這些參數在 SRS 的配置文件中都有配置項:

  • EXT-X-TARGETDURATION:所有切片的最大時長。有些 Apple 設備這個參數不正確會無法播放。SRS 會自動計算出 ts 文件的最大時長,然后更新 m3u8 時會自動更新這個值。用戶不必自己配置。
  • EXTINF:ts 切片的實際時長,SRS 提供配置項 hls_fragment,但實際上的 ts 時長還受 gop 影響。
  • ts 文件的數目:SRS 可配置 hls_window,指定 m3u8 中保存多少個切片,SRS 會自動清理舊的切片。
  • livestream-67.ts:SRS 會自動維護 ts 切片的文件名,在編碼器重推之后,這個編號會繼續增長,保證流的連續性。直到 SRS 重啟,這個編號才重置為 0。

譬如,每個 ts 切片為 10 秒,窗口為 60 秒,那么 m3u8 中會保存 6 個 ts 切片。

每一個 .m3u8 文件,分別對應若干個 ts 文件,這些 ts 文件才是真正存放視頻的數據,m3u8 文件只是存放了一些 ts 文件的配置信息和相關路徑,當視頻播放時,.m3u8 是動態改變的,video 標簽會解析這個文件,並找到對應的 ts 文件來播放,所以一般為了加快速度,.m3u8 放在 web 服務器上,ts 文件放在 cdn 上。

.m3u8 文件,其實就是以 utf-8 編碼的 m3u 文件,這個文件本身不能播放,只是存放了播放信息的文本文件。

HLS 整體框架圖:Server、CDN 和 Client

HLS 協議編碼格式要求

  • 視頻的編碼格式:H264
  • 音頻的編碼格式:AAC、MP3、AC-3
  • 視頻的封裝格式:ts
  • 保存 ts 索引的 m3u8 文件

HLS 協議優勢

  • HLS 相對於 RTMP 來講使用了標准的 HTTP 協議來傳輸數據,可以避免在一些特殊的網絡環境下被屏蔽。
  • HLS 相比 RTMP 在服務器端做負載均衡要簡單得多。因為 HLS 是基於無狀態協議 HTTP 實現的,客戶端只需要按照順序使用下載存儲在服務器的普通 ts 文件進行播放就可以。而 RTMP 是一種有狀態協議,很難對視頻服務器進行平滑擴展,因為需要為每一個播放視頻流的客戶端維護狀態。
  • HLS 協議本身實現了碼率自適應,在不同帶寬情況下,設備可以自動切換到最適合自己碼率的視頻播放。

HLS 協議缺點

  • HLS 協議在直播的視頻延遲時間很難做到 10 s 以下延時,而 RTMP 協議的延時可以降到 3s-4s 左右。

HLS 相關文章

HLS 協議詳解

2. HLS 之 M3U8

m3u8 文件是用文件方式對媒體文件進行描述,由一些列標簽組成。

m3u8 文件示例 1:單碼率適配流

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:2
#EXT-X-TARGETDURATION:16
#EXTINF:14.357, no desc
livestream-2.ts
#EXTINF:15.617, no desc
livestream-3.ts
#EXTINF:14.358, no desc
livestream-4.ts
#EXTINF:15.618, no desc
livestream-5.ts
#EXTINF:11.130, no desc
livestream-6.ts

該 m3u8 文件只是一個簡單的 Media Playlist。

m3u8 文件示例 2:多碼率適配流

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8

包含多種比特率的 Master Playlist。該文件是一個實際使用中的頂級 m3u8 文件,該文件中又定義了 http://example.com/low.m3u8http://example.com/mid.m3u8等幾個二級文件。頂級 m3u8 文件主要是做碼率適配的,二級 m3u8 才是真正的切片文件,客戶端會默認選擇碼率最高的請求,如果發現碼率達不到,會請求降低碼率的流。客戶端拿到二級 m3u8 文件后,會繼續請求里面的文件,這時就可以進行播放了。

2.1 基礎概念

2.1.1 Playlist file

一個 m3u 的 Playlist 就是一個由多個獨立行組成的文本文件,每行由回車/換行區分。每一行可以是一個 URI、空白行或是一個 以 "#" 號開頭的字符串,並且空格只能存在於一行中不同元素間的分隔。

一個 URI 表示一個媒體段或是 "variant Playlist file"(最多支持一層嵌套,即一個 m3u8 文件中嵌套另一個 m3u8),以 "EXT" 開頭的表示一個 "tag",否則表示注釋,直接忽略。

2.1.2 Tags

  1. #EXTM3U
    每個 m3u8 文件第一行必須是這個 tag,如上面的兩個示例。
  2. #EXTINF
    指定每個媒體段(ts)的持續時間,這個僅對其后面的 URI 有效,每兩個媒體段 URI 間被這個 tag 分隔開
    其格式為:#EXTINF:<duration>,<title>
    • duration:表示持續的時間(秒),"Durations MUST be integers if the protocol version of the Playlist file is
      less than 3",否則可以是浮點數。
  3. #EXT-X-BYTERANGE
    表示媒體段是一個媒體 URI 資源中的一段,只對其后的 media URI 有效,
    格式為:#EXT-X-BYTERANGE:<n>[@o]
    • n:表示這個區間的大小
    • o:表示在 URI 中的 offset
    • The EXT-X-BYTERANGE tag appeared in version 4 of the protocol
  4. #EXT-X-TARGETDURATION
    指定當前視頻流中的單個切片(即 ts)文件的最大時長(秒)。所以 #EXTINF 中指定的時間長度必須小於或是等於這個最大值。這個 tag 在整個 Playlist 文件中只能出現一次(在嵌套的情況下,一般有真正
    ts url 的 m3u8 才會出現該 tag)。格式為:#EXT-X-TARGETDURATION:<s>
    • s:表示最大的秒數。
  5. #EXT-X-MEDIA-SEQUENCE
    每一個 media URI 在 Playlist 中只有唯一的序號,相鄰之間序號 +1。
    格式為:#EXT-X-MEDIA-SEQUENCE:<number>。一個 media URI 並不是必須要包含的,如果沒有,默認為 0.
  6. #EXT-X-KEY
    表示怎么對 media segments 進行解碼。其作用范圍是下次該 tag 出現前的所有 media URI。
    格式為:#EXT-X-KEY:<attribute-list>
    • NONE 或者 AES-128。如果是 NONE,則 URI 以及 IV 屬性必須不存在,如果是 AES-128(Advanced Encryption Standard),則 URI 必須存在,IV 可以不存在。
    • 對於 AES-128 的情況,keytag 和 URI 屬性共同表示了一個 key 文件,通過 URI 可以獲得這個 key,如果沒有 IV(Initialization Vector),則使用序列號作為 IV 進行編解碼,將序列號的高位賦到 16 個字節的 buffer 中,左邊補 0;如果有 IV,則將該值當成 16 個字節的 16 進制數。
  7. #EXT-X-PROGRAM-DATE-TIME
    將一個絕對時間或是日期和一個媒體段中的第一個 sample 相關聯,只對下一個 media URI 有效,格式如下:#EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
    • 例如:#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
  8. #EXT-X-ALLOW-CACHE
    是否允許做 cache,這個可以在 Playlist 文件中任意地方出現,並且最多只出現一次,作用效果是所有的媒體段。格式如下:#EXT-X-ALLOW-CACHE:<YES|NO>
  9. #EXT-X-PLAYLIST-TYPE
    提供關於 Playlist 的可變性的信息,這個對整個 Playlist 文件有效,是可選的,格式如下: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
    • VOD,即為點播視頻,服務器不能改變 Playlist 文件,換句話說就是該視頻全部的 ts 文件已經被生成好了
    • EVENT,就是實時生成 m3u8 和 ts 文件。服務器不能改變或是刪除 Playlist 文件中的任何部分,但是可以向該文件中增加新的一行內容。它的索引文件一直處於動態變化中,播放的時候需要不斷下載二級 index 文件
  10. #EXT-X-ENDLIST
    表示 m3u8 文件的結束,live m3u8 沒有該 tag。它可以在 Playlist 中任意位置出現,但是只能出現一個,格式如下: #EXT-X-ENDLIST
  11. #EXT-X-MEDIA
    被用來在 Playlist 中表示相同內容的不同語種/譯文的版本,比如可以通過使用 3 個這種 tag 表示 3 種不同語音的音頻,或者用 2 個這個 tag 表示不同角度的 video。在 Playlist 中,這個標簽是獨立存在的,其格式如下:#EXT-X-MEDIA:<attribute-list>
    • 該屬性列表中包含:URI、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、AUTOSELECT。
    • URI:如果沒有,則表示這個 tag 描述的可選擇版本在主 PlayList 的 EXT-X-STREAM-INF 中存在
    • TYPE:AUDIO and VIDEO
    • GROUP-ID:具有相同 ID 的 MEDIAtag,組成一組樣式
    • LANGUAGE:identifies the primary language used in the rendition
    • NAME:The value is a quoted-string containing a human-readable description of the rendition. If the LANGUAGE attribute is present then this description SHOULD be in that language
    • DEFAULT:YES 或是 NO,默認是 No,如果是 YES,則客戶端會以這種選項來播放,除非用戶自己進行選擇
    • AUTOSELECT:YES 或是 NO,默認是 No,如果是 YES,則客戶端會根據當前播放環境來進行選擇(用戶沒有根據自己偏好進行選擇的前提下)
    • The EXT-X-MEDIA tag appeared in version 4 of the protocol。
  12. #EXT-X-STREAM-INF
    指定一個包含多媒體信息的 media URI 作為 Playlist,一般做 m3u8 的嵌套使用,它只對緊跟后面的 URI 有效,格式如下:#EXT-X-STREAM-INF:<attribute-list>
    • 常用的屬性如下:
    • BANDWIDTH:帶寬,必須有
    • PROGRAM-ID:該值是一個十進制整數,唯一地標識一個在 Playlist 文件范圍內的特定的描述。一個 Playlist 文件中可能包含多個有相同 ID 的此 tag
    • CODECS:指定流的編碼類型,不是必須的
    • RESOLUTION:分辨率
    • AUDIO:這個值必須和 AUDIO 類別的 "EXT-X-MEDIA" 標簽中 "GROUP-ID" 屬性值相匹配
    • VIDEO:同上
  13. #EXT-X-DISCONTINUITY
    當遇到該 tag 的時候說明以下屬性發生了變化:
    • file format
    • number and type of tracks
    • encoding parameters
    • encoding sequence
    • timestamp sequence
  14. #ZEN-TOTAL-DURATION
    表示這個 m3u8 所含 ts 的總時間長度

3. HLS 之 TS

來自: hls之m3u8、ts流格式詳解

ts 文件為傳輸流文件,視頻編碼主要格式為 H264/MPEG4,音頻為 AAC/MP3。

ts 文件分為三層:

  • ts 層:Transport Stream,是在 pes 層的基礎上加入數據流的識別和傳輸必須的信息。
  • pes 層: Packet Elemental Stream,是在音視頻數據上加了時間戳等對數據幀的說明信息。
  • es 層:Elementary Stream,即音視頻數據。

3.1 ts 層:Transport Stream

ts 包大小固定為 188 字節,ts 層分為三個部分:ts header、adaptation field、payload。ts header 固定 4 個字節;adaptation field 可能存在也可能不存在,主要作用是給不足 188 字節的數據做填充;payload 是 pes 數據。

3.1.1 ts header


ts 層的內容是通過 PID 值來標識的,主要內容包括:PAT 表、PMT 表、音頻流、視頻流。解析 ts 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到音視頻流了。PAT 表的和 PMT 表需要定期插入 ts 流,因為用戶隨時可能加入 ts 流,這個間隔比較小,通常每隔幾個視頻幀就要加入 PAT 和 PMT。PAT 和 PMT 表是必須的,還可以加入其它表如 SDT(業務描述表)等,不過 hls 流只要有 PAT 和 PMT 就可以播放了。

  • PAT 表:主要的作用就是指明了 PMT 表的 PID 值。
  • PMT 表:主要的作用就是指明了音視頻流的 PID 值。
  • 音頻流/視頻流:承載音視頻內容。

3.1.2 adaptation field


自適應區的長度要包含傳輸錯誤指示符標識的一個字節。pcr 是節目時鍾參考,pcr、dts、pts 都是對同一個系統時鍾的采樣值,pcr 是遞增的,因此可以將其設置為 dts 值,音頻數據不需要 pcr。如果沒有字段,ipad 是可以播放的,但 vlc 無法播放。打包 ts 流時 PAT 和 PMT 表是沒有 adaptation field 的,不夠的長度直接補 0xff 即可。視頻流和音頻流都需要加 adaptation field,通常加在一個幀的第一個 ts 包和最后一個 ts 包里,中間的 ts 包不加。如下圖所示:

PAT 格式如下圖

PMT 格式如下圖

3.2 pes 層:Packet Elemental Stream

pes 層是在每一個視頻/音頻幀上加入了時間戳等信息,pes 包內容很多,這里只留下最常用的。

pes 層格式如下圖:

pes 層內容如下圖:


pts 是顯示時間戳、dts 是解碼時間戳,視頻數據兩種時間戳都需要,音頻數據的 pts 和 dts 相同,所以只需要 pts。有 pts 和 dts 兩種時間戳是 B 幀引起的,I 幀 和 P 幀的 pts 等於 dts。如果一個視頻沒有 B 幀,則 pts 永遠和 dts 相同。從文件中順序讀取視頻幀,取出的幀順序和 dts 順序相同。dts 算法比較簡單,初始值 + 增量即可,pts 計算比較復雜,需要在 dts 的基礎上加偏移量。

音頻的 pes 中只有 pts(同 dts),視頻的 I、P 幀兩種時間戳都要有,視頻 B 幀只要 pts(同 dts)。打包 pts 和 dts 就需要知道視頻幀類型,但是通過容器格式我們是無法判斷幀類型的,必須解析 h.264 內容才可以獲取幀類型。

舉例說明:

.           I    P    B    B    B    P
讀取順序:   1    2    3    4    5    6
dts 順序:   1    2    3    4    5    6
pts 順序:   1    5    3    2    4    6
點播視頻 dts 算法:

dts = 初始值 + 90000 / video_frame_rate,初始值可以隨便指定,但是最好不要取 0,video_frame_rate 就是幀率,比如 23、30。

pts 和 dts 是以 timestamp 為單位的,1s = 90000 time scale,一幀就應該是 90000/video_frame_rate 個 timescale。

用一幀的 timescale 除以采樣頻率就可以轉換為一幀的播放時長。

點播音頻 dts 算法:

dts = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate,audio_samples_per_frame 這個值與編解碼相關,aac 取值 1024,mp3 取值 1158,audio_sample_rate 是采樣率,比如 24000、41000. AAC 一般解碼出來是每聲道 1024 個 sample,也就是說一幀的時長為 1024/sample_rate 秒。所以每一幀時間戳依次0,1024/sample_rate, ..., 1024*n/sample_rate 秒

注:直播視頻的 dts 和 pts 應該直接用直播數據流中的時間,不應該按公式計算。

3.3 es 層:Elementary Stream

es 層指的就是音視頻數據。這里只介紹 h.264 視頻和 aac 音頻。

3.3.1 h.264 視頻

打包 h.264 數據時必須給視頻數據加上一個 nalu(Network Abstraction Layer Unit),nalu 包括 nalu header 和 nalu type,nalu header 固定為 0x00000001(幀開始)或 0x000001(幀中)。h.264 的數據是由 slice 組成的,slice 的內容包括:視頻、sps、pps 等。nalu type 決定了后面的 h.264 數據內容。

.    
      0 1 2 3 4 5 6 7 
     +-+-+-+-+-+-+-+-+
     |F|NRI|  TYPE   |  
     +-+-+-+-+-+-+-+-+
  • F:1bit,forbidden_zero_bit,h.264 規定必須取 0。
  • NRI:2bits,nal_ref_idc,取值為 0~3,指示這個 nalu 的重要性,I 幀、sps、pps 通常取 3,P 幀常取 2,B 幀通常取 0
  • Type:5bits,取值如下表所示:
    image

打包 es 層數據時 pes 頭和 es 數據之間要加入一個 type=9 的 nalu,關鍵幀 slice 前必須要加入 type=7 和 type=8 的 nalu,而且是緊鄰的。如下圖所示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM