天天看點

FLV 封裝格式解析

作者: 葉餘 來源: 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 結構如下圖所示:

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/1cbe31baa711

6. 修改記錄

2019-03-30 V1.0 初稿

「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
FLV 封裝格式解析