傳統的直播協議要麼使用 Adobe 的基於 TCP 的 RTMP 協議,要麼使用 Apple 的基於 HTTP 的 HLS 協議。html
今天我要向你們介紹另一種結合了 RTMP 的低延時,以及能夠複用現有 HTTP 分發資源的流式協議 HTTP-FLV。ios
首先介紹一下 FLV 文件格式的細節。網絡
FLV 文件格式標準是寫在 F4V/FLV file format spec v10.1 的附錄 E 裏面的 FLV File Format。ide
類型工具 |
說明優化 |
Unitui data編碼 types加密 |
|
SI8debug |
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 = 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 |
||
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。
因爲 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 文件中的音頻信息及數據提取出來,送給音頻解碼器正常播放了。
因爲 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]) |
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 值。
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 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 和快進快退的操做中,快速有效地跳轉到你想要找的關鍵幀位置進行處理。
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,即將音視頻數據封裝成 FLV,而後經過 HTTP 協議傳輸給客戶端。
HLS 實際上是一個 「文本協議」,而並不是流媒體協議。那麼,什麼樣的協議才能稱之爲流媒體協議呢?
流(stream): 數據在網絡上按時間前後次序傳輸和播放的連續音/視頻數據流。之因此能夠按照順序傳輸和播放連續是由於在相似 RTMP、FLV 協議中,每個音視頻數據都被封裝成了包含時間戳信息頭的數據包。而當播放器拿到這些數據包解包的時候可以根據時間戳信息把這些音視頻數據和以前到達的音視頻數據連續起來播放。MP四、MKV 等等相似這種封裝,必須拿到完整的音視頻文件才能播放,由於裏面的單個音視頻數據塊不帶有時間戳信息,播放器不能將這些沒有時間戳信息數據塊連續起來,因此就不能實時的解碼播放。
理論上(除去網絡延遲外),FLV 能夠作到僅僅一個音視頻 tag 的延遲。
能夠在必定程度上避免防火牆的干擾 (例如, 有的機房只容許 80 端口經過);
能夠很好的兼容 HTTP 302 跳轉,作到靈活調度;
可使用 HTTPS 作加密通道;
很好的支持移動端(Android,IOS);
推薦閱讀: