傳統的直播協議要麼使用 Adobe 的基於 TCP 的 RTMP 協議,要麼使用 Apple 的基於 HTTP 的 HLS 協議。
今天我要向大家介紹另外一種結合了 RTMP 的低延時,以及可以複用現有 HTTP 分發資源的流式協議 HTTP-FLV。
FLV
首先介紹一下 FLV 檔案格式的細節。
FLV Adobe 官方標準
FLV 檔案格式標準是寫在 F4V/FLV file format spec v10.1 的附錄 E 裡面的 FLV File Format。
單位說明
型別 |
說明 |
Unit data types |
|
SI8 |
Signed 8-bit integer |
SI16 |
Signed 16-bit integer |
SI24 |
Signed 24-bit integer |
SI32 |
Signed 32-bit integer |
SI64 |
Signed 32-bit integer |
UI8 |
Unsigned 8-bit integer |
UI16 |
Unsigned 16-bit integer |
UI24 |
Unsigned 24-bit integer |
UI32 |
Unsigned 32-bit integer |
UI64 |
Unsigned 64-bit integer |
xxx[] |
Slice of type xxx |
xxx[n] |
Array of type xxx |
STRING |
Sequence of Unicode 8-bit characters (UTF-8), terminated with 0x00 |
FLV 檔案頭和檔案體 (E.2, E.3)
從整個檔案上看,FLV = FLV File Header + FLV File Body。
欄位 |
型別 |
說明 |
FLV File Header |
||
Signature |
UI8[3] |
簽名,總是 “FLV” (0x464C56) |
Version |
UI8 |
版本,總是 0x01,表示 FLV version 1 |
TypeFlagsReserved |
UB [5] |
全 0 |
TypeFlagsAudio |
UB[1] |
1 = 有音訊 |
TypeFlagsReserved |
UB[1] |
全 0 |
TypeFlagsVideo |
UB[1] |
1 = 有視訊 |
DataOffset |
UI32 |
整個檔案頭長度,對於FLV v1,總是 9 |
FLV File Body |
||
PreviousTagSize0 |
UI32 |
總是 0 |
Tag1 |
FLVTAG |
第一個 tag |
PreviousTagSize1 |
UI32 |
前一個 tag 的大小, 包括他的 header,即:11 + 前一個 tag 的大小 |
Tag2 |
FLVTAG |
第二個 tag |
… |
||
PreviousTagSizeN-1 |
UI32 |
前一個 tag 大小 |
TagN |
FLVTAG |
最後一個 tag |
PreviousTagSizeN |
UI32 |
最後一個 tag 大小,包括他的 header |
通常,FLV 的前 13 個位元組(flv header + PreviousTagSize0)完全相同,所以,程式中會單獨定義一個常量來指定。
FLV Tag (E.4)
欄位 |
型別 |
說明 |
FLV Tag |
||
Reserved |
UB[2] |
保留給FMS, 應為 0 |
Filter |
UB[1] |
0 = unencrypted tags,1 = encrypted tags |
TagType |
UB [5] |
型別,0x08 = audio,0x09 = video,0x12 = script data |
DataSize |
UI24 |
message 長度,從 StreamID 到 tag 結束(len(tag) - 11) |
Timestamp |
UI24 |
相對於第一個 tag 的時間戳(unit: ms),第一個 tag 總是 0 |
TimestampExtended |
UI8 |
Timestamp 的高 8 位。 擴充套件 Timestamp 為 SI32 型別 |
StreamID |
UI24 |
總是 0,至此為 11 bytes |
AudioTagHeader |
IF TagType == 0x08 |
|
VideoTagHeader |
IF TagType == 0x09 |
|
EncryptionHeader |
IF Filter == 1 |
|
FilterParams |
IF Filter == 1 |
|
Data |
AUDIODATA 或者 VIDEODATA 或者 SCRIPTDATA |
Timestamp 和 TimestampExtended 組成了這個 TAG 包資料的 PTS 資訊,PTS = Timestamp | TimestampExtended << 24。
AudioTag (E.4.2)
由於 AAC 編碼的特殊性,這裡著重說明了 AAC 編碼的 Tag 格式。
欄位 |
型別 |
說明 |
Audio Tag |
||
AudioTagHeader |
||
SoundFormat |
UB[4] |
音訊編碼格式。2 = MP3,10 = AAC,11 = Speex |
SoundRate |
UB[2] |
取樣率。0 = 5.5 kHz,1 = 11 kHz,2 = 22 kHz,3 = 44 kHz |
SoundSize |
UB[1] |
取樣大小。 0 = 8-bit,1 = 16-bit |
SoundType |
UB[1] |
音訊聲道數。0 = Mono,1 = Stereo |
AACPacketType |
UI8 |
只有當 SoundFormat 為 10 時,才有該欄位。0 = AAC sequence header,1 = AAC raw |
AACAUDIODATA |
||
Data |
AudioSpecificConfig |
IF AACPacketType == 0,包含著一些更加詳細音訊的資訊 |
Data |
Raw AAC frame data in UI8 [n] |
IF AACPacketType == 1,audio payload,n = [AAC Raw data length] - ([has CRC] ? 9:7) |
AudioTagHeader 的第一個位元組,也就是接跟著 StreamID 的 1 個位元組包含了音訊型別,取樣率等的基本資訊。
AudioTagHeader 之後跟著的就是 AUDIODATA 部分了。但是,這裡有個特例,如果音訊格式(SoundFormat)是 AAC,AudioTagHeader 中會多出 1 個位元組的資料 AACPacketType,這個欄位來表示 AACAUDIODATA 的型別:0 = AAC sequence header,1 = AAC raw。
AudioSpecificConfig 結構描述非常複雜,在標準文件中是用虛擬碼描述的,這裡先假定要編碼的音訊格式,做一下簡化。
音訊編碼為:AAC-LC,音訊取樣率為 44100。
欄位 |
型別 |
說明 |
AudioSpecificConfig |
||
audioObjectType |
UB[5] |
編碼結構型別,AAC-LC 為 2 |
samplingFrequencyIndex |
UB[4] |
音訊取樣率索引值,44100 對應值 4 |
channelConfiguration |
UB[4] |
音訊輸出聲道,2 |
GASpecificConfig |
||
frameLengthFlag |
UB[1] |
標誌位,用於表明 IMDCT 視窗長度,0 |
dependsOnCoreCoder |
UB[1] |
標誌位,表明是否依賴於 corecoder,0 |
extensionFlag |
UB[1] |
選擇了 AAC-LC,這裡必須為 0 |
在 FLV 的檔案中,一般情況下 AAC sequence header 這種包只出現1次,而且是第一個 audio tag,為什麼需要這種 tag,因為在做 FLV demux 的時候,如果是 AAC 的音訊,需要在每幀 AAC ES 流前邊新增 7 個位元組 ADST 頭,ADST 是解碼器通用的格式,也就是說 AAC 的純 ES 流要打包成 ADST 格式的 AAC 檔案,解碼器才能正常播放。就是在打包 ADST 的時候,需要 samplingFrequencyIndex 這個資訊,samplingFrequencyIndex 最準確的資訊是在 AudioSpecificConfig 中,這樣,你就完全可以把 FLV 檔案中的音訊資訊及資料提取出來,送給音訊解碼器正常播放了。
VideoTag (E.4.3)
由於 AVC(H.264) 編碼的特殊性,這裡著重說明了 AVC(H.264) 編碼的 Tag 格式。
欄位 |
型別 |
說明 |
Video Tag |
||
VideoTagHeader |
||
FrameType |
UB[4] |
1 = key frame,2 = inter frame |
CodecID |
UB[4] |
7 = AVC |
AVCPacketType |
UI8 |
IF CodecID == 7,0 = AVC sequence header(AVCDecoderConfigurationRecord),1 = One or more AVC NALUs (Full frames are required),2 = AVC end of sequence |
CompositionTime |
SI24 |
IF AVCPacketType == 1 Composition time offset ELSE 0 |
VideoTagHeader 的第一個位元組,也就是接跟著 StreamID 的 1 個位元組包含著視訊幀型別及視訊 CodecID 等最基本資訊。
VideoTagHeader 之後跟著的就是 VIDEODATA 部分了。但是,這裡有個特例,如果視訊格式(CodecID)是 AVC,VideoTagHeader 會多出 4 個位元組的資訊。
AVCDecoderConfigurationRecord 包含著是 H.264 解碼相關比較重要的 SPS 和 PPS 資訊,在給 AVC 解碼器送資料流之前一定要把 SPS 和 PPS 資訊送出,否則的話,解碼器不能正常解碼。而且在解碼器 stop 之後再次 start 之前,如 seek,快進快退狀態切換等,都需要重新送一遍 SPS 和 PPS 的資訊。AVCDecoderConfigurationRecord 在 FLV 檔案中一般情況也只出現 1 次,也就是第一個 video tag。
AVCDecoderConfigurationRecord 長度為 sizeof(UI8) * (11 + sps_size + pps_size)。
欄位 |
型別 |
說明 |
AVCDecoderConfigurationRecord |
||
configurationVersion |
UI8 |
版本號,1 |
AVCProfileIndication |
UI8 |
SPS[1] |
profileCompatibility |
UI8 |
SPS[2] |
AVCLevelIndication |
UI8 |
SPS[3] |
reserved |
UB[6] |
111111 |
lengthSizeMinusOne |
UB[2] |
NALUnitLength - 1,一般為 3 |
reserved |
UB[3] |
111 |
numberOfSequenceParameterSets |
UB[5] |
SPS 個數, 一般為 1 |
sequenceParameterSetNALUnits |
UI8[sps_size + 2] |
sps_size(16bits) + sps(UI8[sps_size]) |
numberOfPictureParameterSets |
UI8 |
PPS 個數, 一般為 1 |
pictureParameterSetNALUnits |
UI8[pps_size + 2] |
pps_size(16bits) + pps(UI8[pps_size]) |
SCRIPTDATA (E.4.4)
ScriptTagBody 內容用 AMF 編碼
欄位 |
型別 |
說明 |
SCRIPTDATA |
||
ScriptTagBody |
||
Name |
SCRIPTDATAVALUE |
Method or object name. SCRIPTDATAVALUE.Type = 2 (String) |
Vale |
SCRIPTDATAVALUE |
AMF arguments or object properties. |
SCRIPTDATAVALUE |
||
Type |
UI8 |
ScriptDataValue 的型別 |
ScriptDataValue |
各種型別 |
Script data 值 |
一個 SCRIPTDATAVALUE 記錄包含一個有型別的 ActionScript 值。
onMetadata (E.5)
FLV metadata object 儲存在 SCRIPTDATA 中, 叫 onMetaData。不同的軟體生成的 FLV 的 properties 不同。
欄位 |
型別 |
說明 |
onMetaData |
||
audiocodecid |
Number |
Audio codec ID used in the file |
audiodatarate |
Number |
Audio bit rate in kilobits per second |
audiodelay |
Number |
Delay introduced by the audio codec in seconds |
audiosamplerate |
Number |
Frequency at which the audio stream is replayed |
audiosamplesize |
Number |
Resolution of a single audio sample |
canSeekToEnd |
Boolean |
Indicating the last video frame is a key frame |
creationdate |
String |
Creation date and time |
duration |
Number |
Total duration of the file in seconds |
filesize |
Number |
Total size of the file in bytes |
framerate |
Number |
Number of frames per second |
height |
Number |
Height of the video in pixels |
stereo |
Boolean |
Indicating stereo audio |
videocodecid |
Number |
Video codec ID used in the file (see E.4.3.1 for available CodecID values) |
videodatarate |
Number |
Video bit rate in kilobits per second |
width |
Number |
Width of the video in pixels |
keyframes 索引資訊
官方的文件中並沒有對 keyframes index 做描述,但是,flv 的這種結構每個 tag 又不像 TS 有同步頭,如果沒有 keyframes index 的話,需要按順序讀取每一個tag, seek 及快進快退的效果會非常差。後來在做 flv 檔案合成的時候,發現網上有的 flv 檔案將 keyframes 資訊隱藏在 Script Tag 中。
keyframes 幾乎是一個非官方的標準, 也就是民間標準。兩個常用的操作 metadata 的工具是 flvtool2 和 FLVMDI,都是把 keyframes 作為一個預設的元資訊專案。在 FLVMDI 的主頁上有描述:
keyframes: (Object) This object is added only if you specify the /k switch. 'keyframes' is known to FLVMDI and if /k switch is not specified, 'keyframes' object will be deleted. |
也就是說 keyframes 中包含著 2 個內容 “filepositions” 和 “times”分別指的是關鍵幀的檔案位置和關鍵幀的 PTS。通過 keyframes 可以建立起自己的 Index,然後在 seek 和快進快退的操作中,快速有效地跳轉到你想要找的關鍵幀位置進行處理。
FLV 分析工具
-
yamdi:將flv轉成帶索引的flv,yamdi -i i.flv -o o.flv
-
flvlib: pip install flvlib, 檢視索引資訊:debug-flv --metadata file.flv
-
flvcheck:http://www.adobe.com/products/adobe-media-server-family/tool-downloads.html
HTTP-FLV
HTTP-FLV,即將音視訊資料封裝成 FLV,然後通過 HTTP 協議傳輸給客戶端。
HLS 其實是一個 “文字協議”,而並非流媒體協議。那麼,什麼樣的協議才能稱之為流媒體協議呢?
流(stream): 資料在網路上按時間先後次序傳輸和播放的連續音/視訊資料流。之所以可以按照順序傳輸和播放連續是因為在類似 RTMP、FLV 協議中,每一個音視訊資料都被封裝成了包含時間戳資訊頭的資料包。而當播放器拿到這些資料包解包的時候能夠根據時間戳資訊把這些音視訊資料和之前到達的音視訊資料連續起來播放。MP4、MKV 等等類似這種封裝,必須拿到完整的音視訊檔案才能播放,因為裡面的單個音視訊資料塊不帶有時間戳資訊,播放器不能將這些沒有時間戳資訊資料塊連續起來,所以就不能實時的解碼播放。
延遲分析
理論上(除去網路延遲外),FLV 可以做到僅僅一個音視訊 tag 的延遲。
相比 RTMP 的優點:
-
可以在一定程度上避免防火牆的干擾 (例如, 有的機房只允許 80 埠通過);
-
可以很好的相容 HTTP 302 跳轉,做到靈活排程;
-
可以使用 HTTPS 做加密通道;
-
很好的支援移動端(Android,IOS);
推薦閱讀: