HLS : http live streaming
主要關注點:
1: 把音視訊的package 封裝成TS流檔案 2: m3u8 索引檔案和分段政策
m3u8 格式詳解:
注意看切片索引檔案:
#EXTM3U m3u檔案頭,必須放在第一行
#EXT-X-MEDIA-SEQUENCE 第一個TS分片的序列号
更新規則:
移除一個檔案播放清單中靠前的(認為已播放的)檔案
不斷更新 EXT-X-MEDIA-SEQUENCE 标簽,以步長為1進行遞增
#EXT-X-TARGETDURATION 每個分片TS的最大的時長
#EXT-X-ALLOW-CACHE 是否允許cache
如果播放清單檔案包含了EXT-X-ALLOW-CATCH标簽,并且它的值為NO,那麼用戶端在播放以後不可以緩存媒體檔案。否則允許緩存用來以後重播。
#EXT-X-ENDLIST
如果不存在EXT-X-ENDLIST标簽,表示此時是直播,并且用戶端想正常播放媒體(按順序以标準速率播放),那麼用戶端就從播放清單檔案尾部選擇三個target duration的媒體檔案,進行下載下傳播放。
為什麼要選擇最後三個? 原因是因為,直播,用戶端會預設播放最近的視訊。
#EXTINF
訓示出下面TS片的時間長度,機關是秒,可以是整數也可以浮點數,浮點數一般精确到小數點後面3位。 同時,EXTINF也影響了播放器重新整理M3U8檔案的間隔,正常情況下,播放器會把目前下載下傳的TS片的EXTINF的值作為每次重新整理M3U8檔案的間隔;如果播放器發現本次取到的M3U8檔案内容沒有更新,會在1-2秒内再次重新整理。
#EXT-X-ENDLIST
其中 #EXT-X-ENDLIST
為切片終止标記,如果沒有該标記,浏覽器會在檔案讀取完後再請求索引檔案,如果有更新則繼續下載下傳新檔案,以此達到直播效果。
EXT-X-PROGRAM-DATE-TIME
用戶端可以使用EXT-X-PROGRAM-DATE-TIME标簽來為使用者顯示節目的起始時間。如果這個值包含了時區資訊,那麼用戶端應該考慮到這點;如果不包含,那麼用戶端不可以推測時區。
例子:
#EXTM3U
#EXT-X-TARGETDURATION:11
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.133333,
fileSequence0.ts
#EXTINF:10.000666,
fileSequence1.ts
#EXT-X-ENDLIST
mpeg-ts流詳解:
typedef struct _tag_PACKET_HEADER
{
unsigned sync_byte: 8; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;">//同步位元組,總是0x47(0100 0111),表示這個包是正确的ts包</span>
unsigned transport_error_indicator: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;">//傳輸錯誤訓示符,1:暫且定義為本包有個無法修複的錯誤</span>
unsigned payload_unit_start_indicator: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //負載機關開始訓示符,1:PES或者PSI的開始部分,0:其他</span>
unsigned transport_priority: 1; <span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //傳輸優先訓示符,1:比相同PID的包有更高的優先級</span>
unsigned PID: 13;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //Packet ID</span>
//加擾控制訓示符
//00:未加擾(俗稱清流);01:留着等以後使用
//10:以even key方式加擾;11:以odd key方式加擾
unsigned scrambling_control: 2;
//适配區存在訓示符
//00:留着等以後使用;
//01:本報不含适配區,隻有負載區
//10:本包隻含适配區,沒有負載區
//11:本包不僅含有适配區,還含有負載區
//其實可以把這兩bit分開來了解,第一個bit訓示是否有适配區,第二個訓示是否有負載區
unsigned adaptation_field_exist: 2;
//當本包目前有負載區時,它才回遞增
//例如,當上面的字段為01or11時,本字段才回遞增
unsigned continuity_counter: 4;
} PACKET_HEADER;
以上就是header的結構,可見sizeof(PACKET_HEADER) == 4 Bytes
根據header的情況,我可以推測出剩餘的PACKET_DATA最長隻有184Bytes了
typedef _tag_PACKET_DATA {
struct adaptation_field;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //0或者更長,依據PACKET_HEADER.adaptation_field_exist的第一bit而定</span>
struct payload_data<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> ; //0或者更長,依據PACKET_HEADER.adaptation_field_exist的第二bit而定</span>
}
下面請看adaptation_field的結構定義:
typedef struct adaptation_field {
adaptation_field_length: 8;<span style="line-height: 25.2px; white-space: pre-wrap; font-family: Arial, Helvetica, sans-serif;"> //本區域除了本位元組剩下的長度(不包含本位元組!!!切記)</span>
//不連續訓示符
//1:本ts包在continuity_counter或者PCR,是處于不連續狀态的
discontinuity_indicator: 1;
//可随機通路訓示符
//1:本包中的PES包可以啟動一個視訊/音頻序列
//就是說如果是1,可以從這個包開始牽出該條ES流
random_access_indicator: 1;
//es流優先級訓示符
//1:當同pid時,該es流有更進階的優先權
elementary_stream_priority_indicator: 1;
//Program Clock Reference,含有“節目時鐘參考”标志
//1:适配區裡面含有PCR field字段,0:不含有
PCR_flag: 1;
//1:适配區裡面含有splice_countdown字段
splicing_point_flag: 1;
//1:适配區裡面含有private_data_bytes字段
trasport_private_data_flag: 1;
//1:适配去裡面含有adaptation_field_extension字段
adaptation_field_extension_flag: 1;
//下面幾個字段依據上面幾個訓示器的值而存在與否
//Program Clock Reference,共6個位元組,48bits,大端模式存儲!
//33bits是基本的,6個填充bits,9個擴充bits
//時間參考,至少約100ms會确定一次audio和video之間的同步問題
PCR: 33+6+9;
//Original Program Clock Reference
//原始PCR,當一個ts包被複制到另一個包裡面去了,這裡保留其原始的PCR值
OPCR: 33+6+9
//分隔倒數器,我自己翻譯的,求指點
//表示當一個分割點出現時,從這個ts包起,還剩多少個ts包(有可能是負數)
splice_countdown: 8;
//填充位元組,全0xFF,目的是為了使得每一個packet的長度為188Bytes
//這個的結果可以使得碼流為恒定值
//當然,如果一次想攜帶的内容超過184位元組了怎麼辦?那就得分隔成多個ts packet了
stuffing_bytes: 不定長;
}
payload_data結構就是所攜帶的純粹的資料了,遵守不足184Bytes就填充FF,超過184Bytes就分隔。
是以ts包可以這樣表示
【header】+【data】 == 【188Bytes】
【header】== 4 Bytes
【data】 == 184 Bytes == 【adaptation_field】+【payload_data+填充】
【adaptation_field】 == adaptation_field->adaptation_field_length
【payload_data+填充】 == 184 - field.adaptation_field_length
HLS原理: 用戶端:
1:首先下載下傳m3u8檔案,然後解析該檔案 2:解析出每個ts檔案的url位址,檔案時長,檔案序号。 3:然後,逐個下載下傳ts檔案。 4:将TS檔案解碼并進行播放。
用戶端重新整理清單的規則: 1:用戶端首先會下載下傳播放清單。 2:根據清單中,逐個下載下傳ts檔案。 3:并根據最後一個ts檔案的時長的作為每次重新整理M3U8檔案的間隔 4:如果發現清單沒有變化,繼續等待,
第一次是0.5倍*ts檔案的時長,第二次1.5倍,3倍。。。
服務端:
如果是點播
1:首先對資料源檔案進行解碼 2:之後打包成一段一段的mpeg-ts資料,将該資料存成ts 檔案 3:并将該檔案時長資訊以及url位址逐漸寫到m3u8檔案裡面。
如果是直播:
1:資料源将資料傳輸到伺服器端之後,如果資料是裸流的話,直接打包成mpeg-ts檔案 2:如果資料不是裸流(例如是YUV),就解碼,然後打包成mpeg-ts檔案 3:将該檔案時長資訊以及url位址逐漸寫到m3u8檔案裡面。
直播清單:
1:裡面最少含有一個url,最多不超過3個。 2: 如果伺服器期望移除示範文稿,它必須使播放清單檔案對于用戶端不可用,在播放清單被清除時,它應該確定播放清單檔案中的所有媒體檔案對于用戶端來說至少在一個播放清單檔案持續時間内是可用的。 3:那些不包含EXT-X-ENDLIST标簽的播放清單檔案的持續時間必須至少三倍于targrtdutration。