作者: 葉餘 來源: https://www.cnblogs.com/leisure_chn/p/10662941.html
FLV (Flash Video) 是由 Adobe 公司推出的一種封裝格式,主要用于流媒體系統。FLV 封裝的媒體檔案具有體積輕巧、封裝播放簡單等特點,很适合網絡應用。目前各浏覽器普遍使用 Flash Player 作為網頁播放器,使得安裝有浏覽器的計算機終端不需要另外安裝播放器,這也是 FLV 格式廣為流行的原因之一。
FLV 封裝格式的檔案擴充名為 .flv。FLV 檔案主要由一個 Header 加上由多個 Tag 組成的 Body 構成。如下所述:
1. FLV Header
所有 FLV 格式檔案都以 FLV Header 開頭。FLV Header 類型是 FLVHEADER,FLVHEADER 定義如下:
字段 | 類型 | 說明 |
Signature | UI8 | 'F' (0x46) |
'L' (0x4C) | ||
'V' (0x56) | ||
Version | FLV 版本。例如,0x01 表示 FLV 版本 1 | |
TypeFlags | b[0] 是否存在視訊流 b[2] 是否存在音頻流 其他字段保留,值為0 | |
DataOffset | UI32 | FLV Header 長度(位元組) |
在 FLV 版本 1 中,“資料偏移”字段值為 9。在 FLV 未來版本中,此字段可相容更大尺寸的 FLV Header。
typedef struct {
UI8 Signature;
UI8 Signature;
UI8 Signature;
UI8 Version;
UI8 TypeFlags;
UI32 DataOffset;
} FLVHEADER;
2. FLV Body
一個 FLV 檔案,除開頭的 FLV Header 外,剩餘部分就是 FLV Body。FLV Body 由一系列 back-pointer 和 tag 交織構成。back-pointer 表示前一 tag 大小。FLV Body 類型是 FLVBODY,FLVBODY 定義如下:
PreviousTagSize0 | 值總為 0 | |
Tag1 | FLVTAG | 第一個 Tag |
PreviousTagSize1 | 前一 Tag 大小,機關位元組。FLV 版本 1 中, 此值等于前一 Tag 的 DataSize + 11 | |
Tag2 | 第二個 Tag | |
... | ||
PreviousTagSizeN-1 | 倒數第二個 Tag 大小,機關位元組 | |
TagN | 最後一個 Tag | |
PreviousTagSizeN | 最後一個 Tag 的大小,機關位元組 |
typedef struct {
UI32 PreviousTagSize0;
FLVTAG Tag1;
UI32 PreviousTagSize1;
FLVTAG Tag2;
...
UI32 PreviousTagSizeN-1;
FLVTAG TagN;
UI32 PreviousTagSizeN;
} FLVBODY;
3. FLV Tag
FLV Tag 包含音頻、視訊或腳本中繼資料、可選的加密中繼資料和 payload。FLV Tag 類型是 FLVTAG,FLVTAG 定義如下:
Reserved | UB [2] | 用于 FMS 的保留字段, 值為 0 |
Filter | UB [1] | 訓示 packet 是否需要預處理。 0 = 不需要預處理。 1 = packet 在渲染前需要預處理(例如解密)。 未加密檔案中此值為0,加密檔案中此值為1。 |
TagType | UB [5] | 8 = 音頻 9 = 視訊 18 = 腳本資料 |
DataSize | UI24 | Tag 中除通用頭外的長度,即 Header + Data 字段的長度 (等于 Tag 總長度 – 11) |
Timestamp | 目前 Tag 的解碼時間戳 (DTS),機關是毫秒。FLV 檔案中第一個 Tag 的 DTS 總為 0 | |
TimestampExtended | 和 Timestamp 字段一起構成一個 32 位值, 此字段為高 8 位。機關毫秒 | |
StreamID | 總為 0 | |
Header | IF TagType == 8 AudioTagHeader IF TagType == 9 VideoTagHeader | 音頻或視訊 TagHeader,注意腳本沒有 TagHeader |
Data | AUDIODATA VIDEODATA IF TagType == 18 SCRIPTDATA | 音頻、視訊或腳本 TagBody |
typedef struct {
UB[2] Reserved;
UB[1] Filter;
UB[5] TagType;
UI24 DataSize;
UI24 Timestamp;
UI8 TimestampExtended;
UI24 StreamID;
IF TagType == 8
AudioTagHeader Header;
IF TagType == 9
VideoTagHeader Header;
IF TagType == 8
AUDIODATA Data;
IF TagType == 9
VIDEODATA Data;
IF TagType == 18
SCRIPTDATA Data;
} FLVTAG;
一個 FLVTAG 中,前 11 個位元組是通用 TagHeader,後面緊跟跟着音頻 Tag、視訊 Tag 或腳本 Tag,其中音頻 Tag 和視訊 Tag 都包含 TagHeader 和 TagBody 兩部分,腳本 Tag 隻有 TagBody 部分。
上面 Timestamp 和 TimestampExtended 兩個字段拼成一個 32 位的時間戳,是目前 Tag 的解碼時間戳 (DTS)。對于音頻幀來說,PTS 和 DTS 相同。對于視訊幀來說,若含 B 幀,則 PTS 和 DTS 不同,H264 視訊幀 PTS = DTS + CTS,CTS 就是 CompositionTime 字段,參考 3.2.1 節 CompositionTime 字段的定義。
3.1 Audio Tag
Audio Tag 包括 AudioTagHeader 和 AudioTagBody 兩部分。
3.1.1 AudioTagHeader
AudioTagHeader 定義如下:
SoundFormat | UB [4] | 聲音格式: 0 = Linear PCM, platform endian 1 = ADPCM 2 = MP3 3 = Linear PCM, little endian 4 = Nellymoser 16-kHz mono 5 = Nellymoser 8-kHz mono 6 = Nellymoser 7 = G.711 A-law logarithmic PCM 8 = G.711 mu-law logarithmic PCM 9 = reserved 10 = AAC 11 = Speex 14 = MP3 8-Khz 15 = Device-specific sound |
SoundRate | 采樣率: AAC: 總為3 0 = 5.5 kHz 1 = 11 kHz 2 = 22 kHz 3 = 44 kHz | |
SoundSize | 采樣位深。此參數僅适用未壓縮格式,壓縮格式總在内部被解碼為16位。 0 = 8位 1 = 16位 | |
SoundType | 0 = 單聲道 1 = 立體聲 | |
IF SoundFormat == 10 AACPacketType | AAC幀類型。僅當聲音格式為 10 時,存在此字段 0 = AAC sequence header 1 = AAC raw |
格式 3,linear PCM,存儲原始 PCM 采樣點。如果采樣位深為 8,采樣點資料為無符号型。如果采樣位深為 16,采樣點資料為小端存儲的帶符号型。如果是立體聲,左右聲道采樣點交織存放:左-右-左-右-...
格式 0 與格式 3 的不同之處隻有一點:格式 0 存儲 16 位采樣資料,采用的大小端順序是建立 FLV 檔案的平台所使用的大小端順序。是以,不應使用格式 0,而應使用格式 3。
格式 4 (Nellymoser 16-kHz mono) 和格式 5 (Nellymoser 8 kHz mono),是兩種特殊情況, 因為采樣率字段無法表示 8 kHz 和 16 kHz。當采樣格式是格式 4 或格式 5 時,Flash 播放器會忽略采樣率和聲音類型兩個字段。對于其他采樣率的 Nellymoser 格式, 即格式 6,則正常使用采樣率和聲音類型兩個字段。
格式 10,AAC,聲音類型應為 1 (立體聲) 且采樣率應為 3 (44 kHz)。這并不表示 FLV 中的 AAC 音頻總是立體聲、44 kHz的資料。實際上,Flash 播放器會忽略這兩個值,而從已編碼的 AAC 位流中提取出聲道數和采樣率資訊。
格式 11,Speex,音頻以 16 kHz采樣率壓縮為單聲道,采樣率字段值應為 0,采樣位深字段值應為 1,聲音類型字段值應為 0。
格式 7,8,14 和 15 保留。
typedef struct {
UB [4] SoundFormat;
UB [2] SoundRate;
UB [1] SoundSize;
UB [1] SoundType;
IF SoundFormat == 10
UI8 AACPacketType;
}
3.1.2 AudioTagBody/AUDIODATA
AUDIODATA 定義如下:
Body | IF Encrypted EncryptedBody ELSE AudioTagBody | 類型分加密與非加密兩種 |
typedef struct {
IF Encrypted
EncryptedBody Body
else
AudioTagBody Body;
} AUDIODATA;
AUDIODATA 包含 Body 字段。如果采用了加密,Body 的類型是 EncryptedBody,可參考規範文檔“附件 F. FLV 加密”章節獲得詳細資訊,此處略。如果未采用加密,則 Body 的類型是 AudioTagBody,下面詳述。
AudioTagBody 定義如下:
SoundData | AACAUDIODATA Varies by format | 字段類型根據聲音格式确定 |
typedef struct {
IF SoundFormat == 10
AACAUDIODATA SoundData;
ELSE
Varies by format
} AudioTagBody;
3.1.3 AACAUDIODATA
Flash 播放器 9.0.115.0 及以上版本支援 AAC 格式。AACAUDIODATA 定義如下:
IF AACPacketType == 0 AudioSpecificConfig ELSE IF AACPacketType == 1 Raw AAC frame data in UI8 [] | AudioSpecificConfig 在 ISO 14496-3 中定義 |
3.2 Video Tag
Video Tag 包含 VideoTagHeader 和 VideoTagBody 兩部分。
3.2.1 VideoTagHeader
FrameType | 幀類型: 1: keyframe (for AVC, a seekable frame) 2: inter frame (for AVC, a non-seekable frame) 3: disposable inter frame (H.263 only) 4: generated keyframe (reserved for server use only) 5: video info/command frame | |
CodecID | 編解碼器辨別: 1: JPEG (currently unused) 2: Sorenson H.263 3: Screen video 4: On2 VP6 5: On2 VP6 with alpha channel 6: Screen video version 2 7: AVC | |
IF CodecID == 7 AVCPacketType | AVC幀類型: 0 = AVC sequence header 1 = AVC NALU 2 = AVC end of sequence (lower level NALU sequence ender is not required or supported) | |
CompositionTime | PTS 與 DTS 的時間偏移值,機關 ms,記作 CTS。參考 "ISO 14496-12, 8.15.3" |
H.264 的命名遵循了 ITU-T 的命名約定,它是 VCEG 視訊編碼标準 H.26x 線中的一員;MPEG-4 AVC 的命名來自 ISO/IEC MPEG 的命名約定,它是 ISO/IEC 14496 的第 10 部分,該協定族被稱為 MPEG-4。
3.2.2 VideoTagBody/VIDEODATA
VIDEODATA 定義如下:
VideoTagBody |
typedef struct {
IF Encrypted
EncryptedBody Body
else
VideoTagBody Body;
} VIDEODATA;
VIDEODATA 包含 Body 字段。如果采用了加密,Body 的類型是 EncryptedBody,可參考規範文檔“附件 F. FLV 加密”章節獲得詳細資訊,此處略。如果未采用加密,則 Body 的類型是 VideoTagBody,下面詳述。
VideoTagBody 包含視訊幀淨荷資料。VideoTagBody 定義如下:
長度 | ||
VideoData | IF FrameType == 5 UI8 ELSE ( IF CodecID == 2 H263VIDEOPACKET IF CodecID == 3 SCREENVIDEOPACKET IF CodecID == 4 VP6FLVVIDEOPACKET IF CodecID == 5 VP6FLVALPHAVIDEOPACKET IF CodecID == 6 SCREENV2VIDEOPACKET AVCVIDEOPACKET ) | 視訊幀淨荷資料或視訊幀資訊 除 AVCVIDEOPACKET 外的所有格式都可以參考 SWF 檔案格式規範 |
typedef struct {
IF FrameType == 5
UI8 VideoData;
ELSE (
IF CodecID == 2
H263VIDEOPACKET VideoData;
IF CodecID == 3
SCREENVIDEOPACKET VideoData;
IF CodecID == 4
VP6FLVVIDEOPACKET VideoData;
IF CodecID == 5
VP6FLVALPHAVIDEOPACKET VideoData;
IF CodecID == 6
SCREENV2VIDEOPACKET VideoData;
IF CodecID == 7
AVCVIDEOPACKET VideoData;
)
} VideoTagBody;
3.2.3 AVCVIDEOPACKET
AVCVIDEOPACKET 包含 AVC(H264) 視訊淨荷資料。AVCVIDEOPACKET 定義如下:
IF AVCPacketType == 0 AVCDecoderConfigurationRecord IF AVCPacketType == 1 One or more NALUs (Full frames are required) | 參考 ISO 14496-15, 5.2.4.1 中對 AVCDecoderConfigurationRecord 的描述 |
typedef struct {
IF AVCPacketType == 0
AVCDecoderConfigurationRecord Data;
IF AVCPacketType == 1
One or more NALUs
} AVCVIDEOPACKET;
3.3 Data Tag
資料 Tag 封裝了單一方法,此方法通常在 Flash 播放器中的網絡流對象上被調用。資料 Tag 包含方法名和一組參數。
3.3.1 ScriptTagBody/SCRIPTDATA
SCRIPTDATA 定義如下:
ScriptTagBody |
typedef struct {
IF Encrypted
EncryptedBody Body
else
ScriptTagBody Body;
} SCRIPTDATA;
SCRIPTDATA 包含 Body 字段。如果采用了加密,Body 的類型是 EncryptedBody,可參考規範文檔“附件 F. FLV 加密”章節獲得詳細資訊,此處略。如果未采用加密,則 Body 的類型是 ScriptTagBody,下面詳述。
ScriptTagBody 包含以 AMF(Action Message Format) 編碼的 SCRIPTDATA。AMF 是一種緊湊二進制格式,用于序列化 ActionScript 對象圖。ScriptTagBody 定義如下:
Name | SCRIPTDATAVALUE | 方法名或對象名 |
Value | AMF 參數或對象屬性 |
這裡的 Name 就是上面提到的資料 Tag 中的方法名,Value 是此方法的一組參數。
typedef struct {
SCRIPTDATAVALUE Name;
SCRIPTDATAVALUE Value;
} ScriptTagBody;
3.3.2 SCRIPTDATAVALUE
一個 SCRIPTDATAVALUE 記錄包含一個特定類型的 ActionScript 值。
SCRIPTDATAVALUE 定義如下:
Type | ScriptDataValue 的類型: 0 = Number 1 = Boolean 2 = String 3 = Object 4 = MovieClip (保留,不支援) 5 = Null 6 = Undefined 7 = Reference 8 = ECMA array 9 = Object end marker 10 = Strict array 11 = Date 12 = Long string | |
ScriptDataValue | Type 字段值 -> 本字段類型: 0 -> DOUBLE 1 -> UI8 2 -> SCRIPTDATASTRING 3 -> SCRIPTDATAOBJECT 7 -> UI16 8 -> SCRIPTDATAECMAARRAY 10 -> SCRIPTDATASTRICTARRAY 11 -> SCRIPTDATADATE 12 -> SCRIPTDATALONGSTRING | 腳本資料值 |
SCRIPTDATAVALUE 的兩個字段,Type 是類型,ScriptDataValue 是值。Type 的值确定 ScriptDataValue 的類型。因為 ScriptDataValue 的類型是動态的,由運作時解析得到的 Type 的值确定,是以這裡類型和值用了兩個字段。如果是靜态類型,顯然隻用一個字段就可以了。
typedef struct {
UI8 Type;
IF Type == 0
DOUBLE ScriptDataValue;
IF Type == 1
UI8 ScriptDataValue;
IF Type == 2
SCRIPTDATASTRING ScriptDataValue;
IF Type == 3
SCRIPTDATAOBJECT ScriptDataValue;
IF Type == 7
UI16 ScriptDataValue;
IF Type == 8
SCRIPTDATAECMAARRAY ScriptDataValue;
IF Type == 10
SCRIPTDATASTRICTARRAY ScriptDataValue;
IF Type == 11
SCRIPTDATADATE ScriptDataValue;
IF Type == 12
SCRIPTDATALONGSTRING ScriptDataValue;
} SCRIPTDATAVALUE;
3.3.1 節中 Name 字段和 Value 字段的類型都是SCRIPTDATAVALUE。Name 表示方法名,實際類型通常是SCRIPTDATASTRING。Value 字段表示方法的一組參數,實際類型通常是SCRIPTDATAECMAARRAY。後文将介紹 SCRIPTDATASTRING 和 SCRIPTDATAECMAARRAY 兩種類型。其他類型略,詳情可參考 FLV 規範文檔。
3.3.3 SCRIPTDATASTRING
SCRIPTDATASTRING 和 SCRIPTDATALONGSTRING 兩種類型用于存儲字元串,二者可存儲字元串長度不同,SCRIPTDATASTRING 用于存儲不超過 65535 個字元的字元串。
SCRIPTDATASTRING 定義如下:
StringLength | UI16 | StringData 字段的長度,機關位元組。 |
StringData | STRING | 字元串實際資料,注意不帶結束符 NUL。 |
typedef struct {
UI16 StringLength;
STRING StringData;
} SCRIPTDATASTRING;
3.3.4 SCRIPTDATAECMAARRAY
SCRIPTDATAECMAARRAY 記錄存儲 ECMA 數組(下表中的 Variables 字段)。ECMA 數組是一個關聯數組,應在 ActionScript 數組包含無序索引時使用。所有索引(無序或有序)都是字元串而不是整數。出于序列化的目的,SCRIPTDATAECMAARRAY 類型與匿名 ActionScript 對象非常相似。
SCRIPTDATAECMAARRAY 定義如下:
ECMAArrayLength | ECMA 數組元素數量(近似) | |
Variables | SCRIPTDATAOBJECTPROPERTY[] | 變量名和變量值的清單,即 ECMA 數組 |
ListTerminator | SCRIPTDATAOBJECTEND | 清單終止符 |
typedef struct {
UI32 ECMAArrayLength;
SCRIPTDATAOBJECTPROPERTY[] Variables;
SCRIPTDATAOBJECTEND ListTerminator;
} SCRIPTDATAECMAARRAY;
其中,SCRIPTDATAOBJECTPROPERTY 類型定義了 ActionScript 對象或關聯數組變量的對象屬性。
SCRIPTDATAOBJECTPROPERTY 定義如下:
PropertyName | SCRIPTDATASTRING | 對象屬性或變量的名稱 |
PropertyData | 對象屬性或變量的值和類型 |
typedef struct {
SCRIPTDATASTRING PropertyName;
SCRIPTDATAVALUE PropertyData;
} SCRIPTDATAOBJECTPROPERTY;
3.3.5 執行個體:onMetaData 對象
FLV 中繼資料對象應在名為 onMetadata 的 SCRIPTDATA 标簽中攜帶。各種屬性對通過 NetStream.onMetaData 屬性運作的 ActionScript 程式有效。可用的屬性根據建立 FLV 檔案的軟體而有所不同。典型屬性包括:
audiocodecid | Number | 音頻編解碼器 ID |
audiodatarate | 音頻碼率,機關 kbps | |
audiodelay | 由音頻編解碼器引入的延時,機關秒 | |
audiosamplerate | 音頻采樣率 | |
audiosamplesize | 音頻采樣點尺寸 | |
canSeekToEnd | Boolean | 訓示最後一個視訊幀是否是關鍵幀 |
creationdate | String | 建立日期與時間 |
duration | 檔案總時長,機關秒 | |
filesize | 檔案總長度,機關位元組 | |
framerate | 視訊幀率 | |
height | 視訊高度,機關像素 | |
stereo | 音頻立體聲标志 | |
videocodecid | 視訊編解碼器 ID | |
videodatarate | 視訊碼率,機關 kbps | |
width | 視訊寬度,機關像素 |
onMetaData 标簽通常會成為 FLV Body 中的第一個标簽,緊跟在 FLV Header 之後。onMetaData 标簽中存儲的是一些視訊、音頻及檔案相關的中繼資料資訊:如視訊幀率,音頻采樣率、檔案長度等。
結合 3.3.1 節,onMetaData 标簽的 Name 字段主要就是存儲 “onMetaData” 字元串。具體為:第 1 個位元組值是 0x02,表示 Name 字段是字元串類型。第 2-3 個位元組為 UI16 類型值,辨別字元串的長度,值為 0x000A (“onMetaData” 這個字元串的長度)。後面跟着的資料為具體的字元串,值為 “onMetaData”。
onMetaData 标簽的 Value 字段存儲上表所示的各屬性鍵值對。具體為:第 1 個位元組值是 0x08,表示 Value 字段是數組類型。第 2-5 個位元組為UI32類型值,表示數組元素個數。後面緊跟着數組,數組元素為屬性名稱和值組成的對(鍵值對)。最後是數組的結束符。
ScriptTagBody onMetaData;
onMetaData.Name.Type == 0x02
onMetaData.Name.ScriptDataValue.StringLength == 0x000A
onMetaData.Name.ScriptDataValue.StringData == "onMetaData"
onMetaData.Value.Type == 0x08
onMetaData.Value.ScriptDataValue.ECMAArrayLength ==
onMetaData.Value.ScriptDataValue.Variables[0].PropertyName == {0x0005, "width"} // SCRIPTDATASTRING 類型
onMetaData.Value.ScriptDataValue.Variables[0].PropertyData == {0x00, 1280.0} // SCRIPTDATAVALUE 類型
onMetaData.Value.ScriptDataValue.Variables[1].PropertyName == {0x0005, "height"} // SCRIPTDATASTRING 類型
onMetaData.Value.ScriptDataValue.Variables[1].PropertyData == {0x00, 720.0} // SCRIPTDATAVALUE 類型
...
4. 總結
FLV 結構如下圖所示:
在 C 語言中定義 FLV 檔案結構,一目了然:
/*
* @brief flv file header 9 bytes
*/
typedef struct flv_header {
uint8_t signature[3];
uint8_t version;
uint8_t type_flags;
uint32_t data_offset; // header size, always 9
} __attribute__((__packed__)) flv_header_t;
/*
* @brief flv tag general header 11 bytes
*/
typedef struct flv_tag {
uint8_t tag_type;
uint32_t data_size;
uint32_t timestamp;
uint8_t timestamp_ext;
uint32_t stream_id;
void *data; // will point to an audio_tag or video_tag
} flv_tag_t;
typedef struct audio_tag {
uint8_t sound_format; // 0 - raw, 1 - ADPCM, 2 - MP3, 4 - Nellymoser 16 KHz mono, 5 - Nellymoser 8 KHz mono, 10 - AAC, 11 - Speex
uint8_t sound_rate; // 0 - 5.5 KHz, 1 - 11 KHz, 2 - 22 KHz, 3 - 44 KHz
uint8_t sound_size; // 0 - 8 bit, 1 - 16 bit
uint8_t sound_type; // 0 - mono, 1 - stereo
void *data;
} audio_tag_t;
typedef struct video_tag {
uint8_t frame_type;
uint8_t codec_id;
void *data;
} video_tag_t;
typedef struct avc_video_tag {
uint8_t avc_packet_type; // 0x00 - AVC sequence header, 0x01 - AVC NALU
uint32_t composition_time;
uint32_t nalu_len;
void *data;
} avc_video_tag_t;
5. 參考資料
[1] Adobe Flash Video File Format Specification Version 10.1, "Annex E. The FLV File Format"
[2]
FLV 存在 B 幀情況下的 DTS 和 PTS,
https://www.jianshu.com/p/1cbe31baa7116. 修改記錄
2019-03-30 V1.0 初稿
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。