多媒體檔案格式(三):M3U8 格式
一、M3U8 格式標準介紹
M3U8檔案是指UTF-8編碼格式的M3U檔案。M3U檔案是記錄了一個索引純文字檔案,開啟它時播放軟體並不是播放它,而是根據它的索引找到對應的音視訊檔案的網路地址進行線上播放。
M3U8是一種常見的流媒體格式,主要以檔案列表的形式存在,既支援直播又支援點播,尤其在Android、iOS等平臺最為常用。
下面是CCTV6直播播放地址: http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8 的M3U8的檔案列表:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:35232 #EXT-X-TARGETDURATION:10 #EXTINF:10.000, cctv6hd-1549272376000.ts #EXTINF:10.000, cctv6hd-1549272386000.ts #EXTINF:10.000, cctv6hd-1549272396000.ts #EXTINF:10.000, cctv6hd-1549272406000.ts #EXTINF:10.000, cctv6hd-1549272416000.ts #EXTINF:10.000, cctv6hd-1549272426000.ts
下面我們來分別說明一下相關的幾個欄位:
- EXTM3U:這個是M3U8檔案必須包含的標籤,並且必須在檔案的第一行,所有的M3U8檔案中必須包含這個標籤。
- EXT-X-VERSION:M3U8檔案的版本,常見的是3(目前最高版本應該是7)。
- EXT-X-TARGETDURATION:該標籤指定了媒體檔案持續時間的最大值,播放檔案列表中的媒體檔案在EXTINF標籤中定義的持續時間必須小於或者等於該標籤指定的持續時間。該標籤在播放列表檔案中必須出現一次。
- EXT-X-MEDIA-SEQUENCE:M3U8直播是的直播切換序列,當播放開啟M3U8時,以這個標籤的值作為參考,播放對應的序列號的切片。
- EXTINF:EXTINF為M3U8列表中每一個分片的duration,如上面例子輸出資訊中的第一片的duration為10秒。在EXTINF標籤中,除了duration值,還可以包含可選的描述資訊,主要為標註切片資訊,使用逗號分隔開。
關於客戶端播放M3U8的標準還有更多的講究,下面我們來介紹一些:
- 分片必須是動態改變的,序列不能相同,並且序列必須是增序的。
- 當M3U8沒有出現EXT-X-ENDLIST標籤時,無論這個M3U8列表中有多少分片,播放分片都是從倒數第三片開始播放,如果不滿三片則不應該播放。當然如果有些播放器做了特別定製了,則可以不遵照這個原則。
- 以播放當前分片的duration時間重新整理M3U8列表,然後做對應的載入動作。
- 前一片分片和後一片分片有不連續的時候,播放可能會出錯,那麼需要X-DISCONTINUTY標籤來解決這個錯誤。
- 如果播放列表在重新整理之後與之前的列表相同,那麼在播放當前分片duration一半的時間內在重新整理一次。
在上面,我們提到了,一些上面例子沒有出現的一些標籤欄位,下面我們針對一些額外的標籤做一些補充說明:
- EXT-X-ENDLIST:若出現EXT-X-ENDLIST標籤,則表明M3U8檔案不會再產生更多的切片,可以理解為該M3U8已停止更新,並且播放分片到這個標籤後結束。M3U8不僅僅是可以作為直播,也可以作為點播存在,在M3U8檔案中儲存所有切片資訊最後使用EXT-X-ENDLIST結尾,這個M3U8即為點播M3U8。EXT-X-ENDLIST標籤可能會出現在播放列表檔案的任何地方,但是不能出現兩次或以上。
- EXT-X-STREAM-INF:EXT-X-STREAM-INF標籤出現在M3U8時,主要是出現在多級M3U8檔案中時,例如M3U8中包含子M3U8列表,或者主M3U8中包含多位元速率M3U8時;該標籤後需要跟一些屬性,下面就來逐一說明一下這些屬性:
-
- BANDWIDTH:BANDWIDTH的值為最高位元速率值,當播放EXT-X-STREAM-INF下對應的M3U8時佔用的最大位元速率(必要引數)。
- AVERAGE-BANDWIDTH:AVERAGE-BANDWIDTH的值為平均位元速率值,當播放EXT-X-STREAM-INF下對應的M3U8時佔用的平均位元速率。(可選引數)。
- CODECS:CODECS的值用於宣告EXT-X-STREAM-INF下面對應M3U8裡面的音視訊編碼、視訊編碼的資訊(可選引數)。
- RESOLUTION:M3U8中視訊的寬高資訊描述(可選引數)。
- FRAME-RATE:子M3U8中的視訊幀率(可選引數)。
二、HLS 與 M3U8
HLS(全稱:Http Live Streaming)是由Apple公司定義的用於實時流傳輸的協議,HLS基於HTTP協議實現,傳輸內容包括兩部分,一是M3U8描述檔案,二是TS媒體檔案。
HLS的優勢為:自適應位元速率流播(adaptive streaming)。效果就是客戶端會根據網路狀況自動選擇不同位元速率的視訊流,條件允許的情況下使用高位元速率,網路繁忙的時候使用低位元速率,並且能夠自動在二者之間隨意切換。這對移動裝置網路狀況不穩定的情況下保障流暢播放非常有幫助。實現方法是伺服器端提供多位元速率視訊流,並且在列表檔案中註明,播放器根據播放進度和下載速度進行自動調整。
為什麼要用 TS 而不是 MP4?這是因為兩個 TS 片段可以無縫拼接,播放器能連續播放,而 MP4 檔案由於編碼方式的原因,兩段 MP4 不能無縫拼接,播放器連續播放兩個 MP4 檔案會出現破音和畫面間斷,影響使用者體驗。而且如果要在一段長達一小時的視訊中跳轉,如果使用單個 MP4 格式的視訊檔案,並且也是用 HTTP 協議,那麼需要代理伺服器支援 HTTP range request 獲取大檔案中的一部分。這樣的話,對於代理伺服器的效能來說要求較高。而 HTTP Live Streaming 則只需要根據列表檔案中的時間軸找出對應的 TS 片段下載即可,不需要 range request,對代理伺服器的要求小很多。所有代理伺服器都支援小檔案的高效快取。
三、FFmpeg轉HLS檔案(M3U8)實戰
1. FFmpeg轉MP4為HLS(M3U8)檔案
將MP4檔案轉換成HLS(M3U8)命令列:
ffmpeg -re -i 好漢歌.mp4 -c copy -f hls -bsf:v h264_mp4toannexb output.m3u8
可以看到生成的M3U8及相應的ts檔案:
檢視一下生成的M3U8檔案:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:19 #EXTINF:10.000000, output19.ts #EXTINF:10.000000, output20.ts #EXTINF:9.280000, output21.ts #EXTINF:4.120000, output22.ts #EXTINF:2.440000, output23.ts #EXT-X-ENDLIST
細心的人可能發現一個問題,就是生成的m3u8檔案裡只有最後的五個片段的資訊。這是因為ffmpeg 預設的list size 為5,所以只獲得最後的5個片段。為了解決這個問題,需要指定引數-hls_list_size 0,這樣就能包含所有的片段。
下面是優化後的命令列:
ffmpeg -re -i 好漢歌.mp4 -c copy -f hls -hls_list_size 0 -bsf:v h264_mp4toannexb output.m3u8
這時,我們可以看到從output0.ts到output23.ts的檔案列表了。
可能有人會發現,無論是優化之前的命令列,還是優化後的命令列都有一個引數-bsf:v h264_mp4toannexb,這個引數的作用是將MP4中的H.264資料轉換成為H.264 AnnexB標準的編碼,AnnexB標準的編碼常見於實時傳輸流中。如果原始檔為FLV、TS等可以作為直播傳輸流的視訊,則不需要這個引數。
下面我們逐一介紹下使用FFmpeg生成HLS時還可以配置的其他引數。
四、FFmpeg 轉 HLS (M3U8) 檔案命令引數
1. start_number 引數
start_number 引數用於設定M3U8列表中的第一片的序列數。
下面的例子中,我們使用start_number引數設定M3U8中的第一片序列書為100,命令列如下:
ffmpeg -re -i huijia.mp4 -c copy -f hls -start_number 100 -hls_list_size 0 -bsf:v h264_mp4toannexb output.m3u8
輸出的M3U8內容如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:3 #EXT-X-MEDIA-SEQUENCE:100 #EXTINF:3.000000, output100.ts #EXTINF:3.000000, output101.ts #EXTINF:3.000000, output102.ts #EXTINF:3.000000, output103.ts #EXTINF:3.000000, output104.ts #EXTINF:3.000000, output105.ts #EXTINF:3.000000, output106.ts #EXTINF:1.000000, output107.ts #EXT-X-ENDLIST
從輸出可以看出,切片的第一片編號是100,上面的命令列引數的-start_number引數已生效。
2. hls_time 引數
hls_time引數用於設定M3U8列表中切片的duration。
下面的例子中,我們使用hls_time引數設定M3U8的TS檔案的每一片時長為9秒左右。命令列如下:
ffmpeg -re -i huijia.mp4 -c copy -f hls -hls_time 9 -hls_list_size 0 -bsf:v h264_mp4toannexb output.m3u8
然後檢視輸出的M3U8內容如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:9 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:9.000000, output0.ts #EXTINF:9.000000, output1.ts #EXTINF:4.000000, output2.ts #EXT-X-ENDLIST
可以看到TS的檔案每一片的時常都是9秒左右,hls_time引數生效。
( 注意:hls_time設定後效果不一定準確,會受到關鍵幀大小及其他因素影響。)
3. hls_list_size 引數
hls_list_size引數用於為M3U8列表中的TS切片的個數。其中設定為0的時候,將包含所有。
這個命令,我們在第3節優化MP4轉HLS檔案的命令列時使用到了。
下面的例子中,我們使用hls_list_size引數設定只保留2片TS切片。命令列如下:
ffmpeg -re -i huijia.mp4 -c copy -f hls -hls_list_size 2 -bsf:v h264_mp4toannexb output.m3u8
檢視輸出的M3U8內容如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:3 #EXT-X-MEDIA-SEQUENCE:6 #EXTINF:3.000000, output6.ts #EXTINF:1.000000, output7.ts #EXT-X-ENDLIST
從輸出的M3U8內容可以看出,在M3U8檔案中只保留了2片TS的檔案資訊,可以看出hls_list_size設定生效了。
4. hls_base_url引數
hls_base_url 引數用於為M3U8列表的檔案路徑設定前置基本路徑引數,因為在FFmpeg中生成M3U8時寫入的TS切片路徑預設為M3U8生成的路徑相同,但是實際上TS所儲存的路徑既可以為本地絕對路徑,也可以為相對路徑,還可以為網路路徑,因此使用hls_base_url引數可以達到該效果,命令列如下:
ffmpeg -re -i huijia.mp4 -c copy -f hls -hls_base_url /Users/renhui/Desktop/test/ -bsf:v h264_mp4toannexb output.m3u8
檢視輸出的M3U8內容如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:3 #EXT-X-MEDIA-SEQUENCE:3 #EXTINF:3.000000, /Users/renhui/Desktop/test/output3.ts #EXTINF:3.000000, /Users/renhui/Desktop/test/output4.ts #EXTINF:3.000000, /Users/renhui/Desktop/test/output5.ts #EXTINF:3.000000, /Users/renhui/Desktop/test/output6.ts #EXTINF:1.000000, /Users/renhui/Desktop/test/output7.ts #EXT-X-ENDLIST
可以看到,TS的路徑變為絕對路徑了,使用ffplay output.m3u8播放,看到播放是能夠正常播放的。這樣就可以說明hls_base_url生效了。