在數位內容的時代,影音的閱讀比例也越來越高,相關的直播與影音串流服務越來越多,而其中最常使用的串流技術規格便是HLS;
近日工作上開始使用不少HLS服務,決定自己翻Spec搞懂整個技術架構,並整理成筆記分享。
目前預計會寫個兩篇:
上篇(此篇)介紹 HLS Spec內容
下篇介紹影音處理相關的規範與工具使用
全文內容基於文件 https://www.rfc-editor.org/rfc/rfc8216.txt ,但內文為自己閱讀後並重組,如果有不清楚或是筆誤之處,再麻煩留言囉
簡介與概論
RFC 8216 主要是介紹 HTTP Live Streaming (HLS),主要定義資料的檔案格式
與Server/Client應該有的相對應行為
,目前的最新版本為 7。
HLS 提供穩定、省花費的方式推送連續性或長時段的影片,有幾個特性
- 接收方可以根據網路狀況調整不同解析度
- 支援加密
- 同個影音內容不同的播放條件,例如字幕切換
- 基於HTTP,可以有良好的Cache機制(省花費的原因)
HLS 大致上是將影片切成小塊狀,並透過播放清單推送,Client 收到切成塊狀的影片片段後就可以直接播放,而不需等到整個影片載完才可以播;
而每個片段都可以被Cache在CDN,方便推送到全世界。
小塊狀的影片稱為 Media Segment
,常用的格式為 ts / fmp4
播放清單則是 Playlist,格式為 m3u8
HLS 有兩種 Playlist 格式: Master Playlist
與 Media Playlist
Master Playlist 主要是為了提供多種解析度、翻譯字幕的播放清單 (Media Playlist),當 Client (如播放器)依據網路條件與用戶選擇等,則選擇支援的播放清單下載影片檔
Master Playlist
主要針對相同內容提供不同的 Streaming,例如不同的解析度、不同的國家有不同的字幕等
|
|
大意是在平均流量6000000時播放 hi.m3u8,在比較低點的1000000播放 low.m3u8,如果是針對音檔而已則播 audio-only.m3u8。
Media Playlist
就是 Master Playlist中 Streaming指定的串流播放清單,主要說明要播放的片段URI、播放時長、如何解碼影片或是解密內容等等
|
|
這個 Media Playlist 說明了大約最大個Media Segments播放時長為10秒;
第一個片段 URI 是 http:…/first.ts 長度為 9秒;
第一個片段播完換第二個片段,以此類推。
Media Segments
Media Playlist 是由多個 Media Segments
而組成,每個Media Segments 都是必須註明檔案來源(URI),且基本上需要是與前一個Media Segments 相連續的內容,也就是 1 播完換 2 ,2 播完換 3 的概念,除非有顯示宣告兩個Media Segments 是不連續的。
任一個Media Segments 都必須帶有足夠的資訊讓播放器可以解碼,在不同格式下,有些格式的初始化片段(Media Initialization Segments) 是相同的,這時候可以宣告 EXT-X-MAP
指定,後續的Media Segments 就可以共用。
以下常見的支援格式有
- MPEG-2 Transport Stream
- Fragmented MPEG-4
- Packed Audio(音訊)
- WebVTT(字幕)
前兩者是不同的影音編碼格式,MPEG-2被廣泛應用於有線電視、DVD等,而MPEG-4 是基於 MPEG-2 改良,各自有不同的格式與 Media Initialization Segments 的格式。
WebVTT通常需要額外的 EXT-TIMESTAMP-MAP 去與影音播放校時。
Playlist
Playlist 的檔名必須是 .m3u8
/ .m3u
,並且回傳的 HTTP Response Content-Type必須為 application/vnd.apple.mpegur
或是 audio/mpegurl
;
編碼必須採用 utf-8
,除了 CR / LF 外不能有其他 utf-8定義的控制碼。
在Playlist中,每一行可以是 URI / 空白或是由 #
開頭的字串;
特別注意 # 開頭的字串可以是標籤或是註解,標籤像是 #EXT-X
開頭,且區分大小寫(標籤全大寫)
URI 部分可以是相對路徑,相對於 Playlist 的URI。
接著介紹Playlist的組成標籤,這部分主要是節錄 Spec內容,因為是名詞定義有點枯燥,有興趣可以深入查詢,後續會有實際範例。
Basic Tags
Playlist 可分成 Media Playlist 與 Master Playlist,以下為兩者共用的標籤。
- EXTM3U
指定檔案為 Extented M3U,必須是檔案的第一行(雷同於html的 DOCTYPE) - EXT-X-VERSION
指定版號,目前最新版為 7,整份 Playlist只應該出現一次。
Media Segments Tags
Media Segments Tag 會應用於下一個 Media Segment 或是後續所有的Media Segment 上。
- EXTINF
註明該 Media Segment的時間長度,這個標籤是必須的。
格式為#EXTIF:<duration>,[<title>]
duration 單位為秒,建議用浮點數表示,如果版號小於三才使用整數 - EXT-X-BYTERANGE
註記該 Media Segment只截取某範圍而非整個完整檔案
格式為#EXT-X-BYTERANGE:<n>[@<o>]
if( o ) {
o 代表 byte offset,n表示從o開始取幾個 byte
} else {
o 取上一個 Media Segment的結尾
} - EXT-X-DISCONTINUITY
先前說每個 Media Segment 都必須是連續的,但如果檔案格式、影音軌(Track)的數字/型別等、timestamp 有改變,就必須要顯示宣告 - EXT-X-KEY
Media Segment 可以被加密,而#EXT-X-KEY
則是宣告解密的方式 - EXT-X-MAP
定應 Media Initialization Segments 來源,效力應用於每個Media Segment 上;
格式為#EXT-X-MAP:<Attribute-List>
- EXT-X-PROGRAM-DATE-TIME
宣告第一個Media Segment 的絕對時間,格式為 ISO-8601,須包含時區,例如#EXT-X-PROGRAM-DATE-TIME:2010–02–19T14:54:23.031+08:00
- EXT-X-DATERANGE
指定該時間範圍內的屬性定義,這部分我看不懂在幹嘛OTZ
Media Playlist Tags
- EXT-X-TARGETDURATION
單個 Media Segments 的 EXTINT最大值,單位為秒,格式是整數,此標籤為必填。 - EXT-X-MEDIA-SEQUENCE
註記第一個Media Segment的序號,如果不存在則預設為0;
此標籤需出現於所有Media Segments 之前 - EXT-X-DISCONTINUITY-SEQUENCE
用於多個串流同步的序號。 - EXT-X-ENDLIST
不會有更多的 Media Segment被加入 Playlist中,可以出現在清單的各處 - EXT-X-PLATLIST-TYPE
可以是 EVENT 或是 VOD
EVENT 表示 Media Segment 會持續加到 Playlist之後
VOD表示 Playlist 不會改變了 - EXT-X-I-FRAMES-ONLY
每個 Media Segment都是獨立的I-Frame,影片的編碼與前後沒有相依性,可用於快轉、快速倒帶、關鍵影格等場景*
Master Playlist Tags
- EXT-X-MEDIA
註明在不同的場景(例如純音檔/英文字幕/法文字幕等)下播放不同的 Media Playlist,也就是不同的Rendition,但這些Playlist都必須是相同的內容;
其中有許多屬性可以設定TYPE
:可以是AUDIO/VIDEO/SUBTITLE/CLOSE-CAPTIONS(隱藏字幕)URI
:對應的Media Playlist 資源位置LANGUAGE
ASSOC-LANGUAGE
:用於其他地方的語言指定,如語音 vs 文字DEFAULT
:預設播放此PlaylistGROUP-ID
:自定義的名稱
等等 - EXT-X-STREAM-INF
宣告 Variant Stream,屬性有BANDWITDH
:加總Media Playlist的最大 Segment Bit rateAVERAGE-BANDWITDH
:平均每秒幾個bitsCODES
:編解碼格式VIDEO
:其值需對應#EXT-X-MEDIA
的GROUP-ID
,且該#EXT-X-MEDIA
的TYPE必須是 VIDEOAUDIO
:狀況雷同#EXT-X-STREAM-INF
與#EXT-X-MEDIA
相輔相成 - EXT-X-I-FRAME-STREAM-INF
- EXT-X-SESSION-DATA
任意的 Session值 - EXT-X-SESSION-KEY
預先加載在 Media Playlist宣告的 EXT-X-KEY的值
Media or Master Playlist Tags
- EXT-X-INDEPENDENT_SEGMENTS
註記每個Media Segments 可以單獨decode而不需要其他 segment的資訊
如果出現在Master Playlist對所有 Media Playlist都有效 - EXT-X-START
從何處開始播放清單
需指名 TIME-OFFSET屬性,正值代表從影片開頭開始算,負值則從影片尾端開始算
TIME-OFFSET值不可超過影片的長度,如果超過則正值從尾部算而負值從頭算(一個循環的概念)
以上是所有的標籤,也不是每個意思都很好理解,後續盡量以範例說明
Key files
EXT-X-KEY的URI屬性標記Key的檔案位置,用於解密Media Segments;
目前的加密方式可支援 AES-128,如果採用此加密方式,則需要 IV值,用於增強AES-128加密的初始值。
Client / Server Responsibilities
第六章主要定義Server / Client 遇到不同標籤的處理行為,算是上面的大統整,以及實做上的引導說明。
Server
負責產生Playlist與提供Media Segments
實際如何產生來源媒體檔不再 Spec範疇中,而Server必須負責把來源媒體檔分割成連續且時長小於總目標時長的Media Segments,需注意當Server分割檔案時需要切在 packet
/ key frame
上;
接著針對每個Media Segment分配特定的URI,如果Server支援 Byte-Range GET則可以在Playlist中使用 EXT-X-BYTERANGE
當Media Segment 被運用在 Playlist中,Server必須確保資源可以被取得且不會出錯,且下載必須是立即下載而非 Streaming
如果TYPE是 VOD,Server 產生出 Playlist就不應該修改;
如果TYPE 是 EVENT,已經產生的Playlist也不能修改,只能在Playlist之後繼續增加行數。
如果Media Playlist沒有包含 EXT-X-ENDLIST,則Server 必須提供包含新的Media Segments 來更新Playlist,而提供的時間必須等 0.5 ~ 1.5 目標時間內,因為這樣才能最大化利用網路效能。
Client
當Client 收到Master Playlist ,可以選擇一個 Variant Stream 播放,Client 必須檢查版號與標籤的語法,如果不合則停止播放,但為了更好的向前相容,遇到無法識別的標籤或屬性值就自動忽略。
如果 Media Playlist TYPE 是 Event 且沒有 EXT-X-ENDLIST 則必須定期重載 Playlist 以便獲取最新的 Media Segments,但為了不帶給Server過多負擔,重新獲取的時間差必須在至少一倍的 target duration,如果第一次重載後沒有更新則至少等半個target duration 再重試。
這部分內容很多,有興趣可以慢慢翻,主要就是講如何載入資源與遇到標籤要怎麼處理等等
Examples
Example Playlists for HTTP Live Streaming | Apple Developer Documentation
Spec 中也有範例,但這邊我擷取Apple Developer 的範例
安插廣告
|
|
注意在 ad1.ts 與 movieA.ts 中有個 #EXT-X-DISCONTINUITY
,因為 Ad 跟 Movie 勢必是兩個不同的片段,所以必須加入才不會因為 timestamp 銜接不上而導致播放錯誤!
Master Playlist with Alternative Audio
|
|
如果希望可以讓Video 依據不同的頻寬切換,而音檔根據不同的語言選擇切換,就可以參考這個範例
#EXT-X-MEDIA
定義了Rendition,透過LANGUAGE指定語言,接著TYPE表明為AUDIO聲音檔,GROUP-ID自取為 aac;
而#EXT-X-STREAM-INF
定義的是 Variant Stream,AUDIO指定為 aac,也就是對應 #EXT-X-MEDIA
的GROUP-ID,其URI則指定播放 video。
可以看出常用的標籤就那幾個,有些標籤我來回看了幾次還是看不懂,像是 EXT-X-DATERANGE就很難懂,範例也都沒用上,如果有人知道實際上的應用與原理再麻煩留言了OTZ
結語
整份Spec 死嗑完很多部份也還是懵懵懂懂,但至少有個全局的認知與介紹,下一章從影片處理到Playlist修改,來看實際應用的處理。
註記
影片編碼
參考維基百科
影像是一幀一幀的圖片連續快速播放,但如果儲存時把每一幀都用圖片方式儲存資料量會非常大,但是影片往往一段時間的場景雷同,如果我可以先記第一張圖,其餘去計算第二張與第一張差多少的概念去儲存,就可以避免儲存過多相同冗余的數據
但因為要避免掉了一點資料就全毀,保留一些彈性所以會在不同時刻安插取全圖的時機。
影片壓縮透過建立 I-Frame / P-Frame / B-Frame方式
I-Frame 關鍵影格,基本上就儲存並壓縮整張圖,作為其他Frame解碼的依據
P-Frame則僅儲存與上一個 I Frame差異的像素
B-Frame則為前後預測圖像,會參考前一個 I-Frame 與後一個 P-Frame
所以大致會長成 IBB..PIBB..P
,取 I-Frame / B-Frame 的頻率可以停整
影音檔與 FFMpeg 教學:ffmpeg-libav-tutorial