簡(jiǎn)介
FLV(Flash Video)是現(xiàn)在非常流行的流媒體格式,由于其視頻文件體積輕巧、封裝播放簡(jiǎn)單等特點(diǎn),使其很適合在網(wǎng)絡(luò)上進(jìn)行應(yīng)用,目前主流的視頻網(wǎng)站無(wú)一例外地使用了FLV格式。另外由于當(dāng)前瀏覽器與Flash Player緊密的結(jié)合,使得網(wǎng)頁(yè)播放FLV視頻輕而易舉,也是FLV流行的原因之一。
FLV是流媒體封裝格式,我們可以將其數(shù)據(jù)看為二進(jìn)制字節(jié)流??傮w上看,F(xiàn)LV包括文件頭(File Header)和文件體(File Body)兩部分,其中文件體由一系列的Tag及Tag Size對(duì)組成。

FLV格式解析
先來(lái)一張圖,這是《東風(fēng)破》——周杰倫(下載)的一個(gè)MV視頻。我使用的是Binary Viewer的二進(jìn)制查看工具。

ffmpeg看一下視頻信息
C:\Users\li\Downloads>ffprobe -show_format dongfengpo.flv
ffprobe version 4.4-full_build-www.gyan.dev Copyright (c) 2007-2021 the FFmpeg developers
built with gcc 10.2.0 (Rev6, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
libpostproc 55. 9.100 / 55. 9.100
Input #0, flv, from 'dongfengpo.flv':
Metadata:
encoder : Lavf57.41.100
Duration: 00:05:14.47, start: 0.000000, bitrate: 431 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 352x240 [SAR 3675:3674 DAR 245:167], 283 kb/s, 29.97 fps, 29.97 tbr, 1k tbn, 59.94 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
[FORMAT]
filename=dongfengpo.flv
nb_streams=2
nb_programs=0
format_name=flv
format_long_name=FLV (Flash Video)
start_time=0.000000
duration=314.470000
size=16954257
bit_rate=431310
probe_score=100
TAG:encoder=Lavf57.41.100
[/FORMAT]
header
頭部分由一下幾部分組成
Signature(3 Byte)+Version(1 Byte)+Flags(1 Bypte)+DataOffset(4 Byte)
- signature 占3個(gè)字節(jié)
固定FLV三個(gè)字符作為標(biāo)示。一般發(fā)現(xiàn)前三個(gè)字符為FLV時(shí)就認(rèn)為他是flv文件。 - Version 占1個(gè)字節(jié)
標(biāo)示FLV的版本號(hào)。這里我們看到是1 - Flags 占1個(gè)字節(jié)
內(nèi)容標(biāo)示。第0位和第2位,分別表示 video 與 audio 存在的情況.(1表示存在,0表示不存在)。截圖看到是0x05,也就是00000101,代表既有視頻,也有音頻。 - DataOffset 4個(gè)字節(jié)
表示FLV的header長(zhǎng)度。這里可以看到固定是9
body
FLV的body部分是由一系列的back-pointers + tag構(gòu)成
- back-pointers 固定4個(gè)字節(jié),表示前一個(gè)tag的size。
- tag 分三種類(lèi)型,video、audio、scripts。
tag組成
tag type+tag data size+Timestamp+TimestampExtended+stream id+ tag data
- type 1個(gè)字節(jié)。8為Audio,9為Video,18為scripts
- tag data size 3個(gè)字節(jié)。表示tag data的長(zhǎng)度。從streamd id 后算起。
- Timestreamp 3個(gè)字節(jié)。時(shí)間戳
- TimestampExtended 1個(gè)字節(jié)。時(shí)間戳擴(kuò)展字段
- stream id 3個(gè)字節(jié)??偸?
- tag data 數(shù)據(jù)部分
我們根據(jù)實(shí)例來(lái)分析:
看到第一個(gè)TAG
type=0x12=18。這里應(yīng)該是一個(gè)scripts。
size=0x000125=293。長(zhǎng)度為293。
timestreamp=0x000000。這里是scripts,所以為0
TimestampExtended =0x00。
stream id =0x000000
我們看一下TAG的data部分:

tag的劃分
圖中紅色部分是我標(biāo)出的兩個(gè)back-pointers,都是4個(gè)字節(jié)。而中間就是第一個(gè)TAG。那是怎么計(jì)算的呢?我們就以這個(gè)做個(gè)示例。
- 首先第一個(gè)back-pointers是
0x00000000,那是因?yàn)楹竺媸堑谝粋€(gè)TAG。所以他為0。 - 然后根據(jù)我們我們前面格式獲取到size是
0x000125。也就是說(shuō)從stream id后面再加上293個(gè)字節(jié)就到了第一個(gè)TAG的末尾,我們數(shù)一下一下。stream id以前總共有24個(gè)字節(jié)(9+4+11)。那么到第一個(gè)TAG結(jié)束,下一個(gè)TAG開(kāi)始的位置是293+24=137=0x13D。 - 接下來(lái)我們找到
0x13D的地址,從工具上很容易找到,正好就是紅色下劃線(xiàn)的前面。紅色部分是0x00000130=304,這代表的是上一個(gè)TAG的大小。 - 最后我們計(jì)算一下,上一個(gè)TAG數(shù)據(jù)部分是293個(gè)字節(jié),前面type、stream id等字段占了11個(gè)字節(jié)。正好是匹配的。
上面我們已經(jīng)知道了怎么取劃分每個(gè)TAG。接下來(lái)我們就看TAG的具體內(nèi)容
tag的內(nèi)容
前面已經(jīng)提到tag分3種。我們一個(gè)個(gè)看
script
腳本Tag一般只有一個(gè),是flv的第一個(gè)Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等。
首先介紹下腳本的數(shù)據(jù)類(lèi)型。所有數(shù)據(jù)都是以數(shù)據(jù)類(lèi)型+(數(shù)據(jù)長(zhǎng)度)+數(shù)據(jù)的格式出現(xiàn)的,數(shù)據(jù)類(lèi)型占1byte,數(shù)據(jù)長(zhǎng)度看數(shù)據(jù)類(lèi)型是否存在,后面才是數(shù)據(jù)。
一般來(lái)說(shuō),該Tag Data結(jié)構(gòu)包含兩個(gè)AMF包。AMF(Action Message Format)是Adobe設(shè)計(jì)的一種通用數(shù)據(jù)封裝格式,在Adobe的很多產(chǎn)品中應(yīng)用,簡(jiǎn)單來(lái)說(shuō),AMF將不同類(lèi)型的數(shù)據(jù)用統(tǒng)一的格式來(lái)描述。第一個(gè)AMF包封裝字符串類(lèi)型數(shù)據(jù),用來(lái)裝入一個(gè)“onMetaData”標(biāo)志,這個(gè)標(biāo)志與Adobe的一些API調(diào)用有,在此不細(xì)述。第二個(gè)AMF包封裝一個(gè)數(shù)組類(lèi)型,這個(gè)數(shù)組中包含了音視頻信息項(xiàng)的名稱(chēng)和值。具體說(shuō)明如下,大家可以參照?qǐng)D片上的數(shù)據(jù)進(jìn)行理解。
| 值 | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| 0 | Number type | 8 Bypte Double |
| 1 | Boolean type | 1 Bypte bool |
| 2 | String type | 后面2個(gè)字節(jié)為長(zhǎng)度 |
| 3 | Object type | |
| 4 | MovieClip type | |
| 5 | Null type | |
| 6 | Undefined type | |
| 7 | Reference type | |
| 8 | ECMA array type | 數(shù)組,類(lèi)似Map |
| 10 | Strict array type | |
| 11 | Date type | |
| 12 | Long string type | 后面4個(gè)字節(jié)為長(zhǎng)度 |

上圖為第一個(gè)AMF包
- type=
0x02對(duì)應(yīng)String - size=
0A=10 -
value=onMetaData 正好是10個(gè)字節(jié)。
5.png
上圖為第二個(gè)AMF
- type=
0x08對(duì)應(yīng)ECMA array type。
表示數(shù)組,類(lèi)似Map。后面4個(gè)字節(jié)為數(shù)組的個(gè)數(shù)。然后是鍵值對(duì),第一個(gè)為鍵,2個(gè)字節(jié)為長(zhǎng)度。后面跟具體的內(nèi)容。接著1個(gè)字節(jié)表示值的類(lèi)型,然后根據(jù)類(lèi)型判斷長(zhǎng)度。
上圖我們可以判斷,總共有13個(gè)鍵值對(duì)。
- 第一個(gè)長(zhǎng)度為8個(gè)字節(jié)是duration。值類(lèi)型是
0x004073,第一個(gè)字節(jié)是00,所以是double,8個(gè)字節(jié)4073A7851EB851EC,通過(guò)計(jì)算Double.longBitsToDouble(0x4073A7851EB851ECL)得到314.47與視頻信息里一致 00:05:14.47。 - 第二個(gè)長(zhǎng)度5個(gè)字節(jié)是width。值也是double類(lèi)型,8個(gè)字節(jié)。
依次解析下去...
到處,我們已經(jīng)知道了如何解析FLV中Tag為script的數(shù)據(jù)。
video

type=
0x09=9。這里應(yīng)該是一個(gè)video。size=
0x000030=48。長(zhǎng)度為48。timestreamp=
0x000000。TimestampExtended =
0x00。stream id =
0x000000我們看到數(shù)據(jù)部分:
視頻信息+數(shù)據(jù)
視頻信息,1個(gè)字節(jié)。
前4位為幀類(lèi)型Frame Type
| 值 | 類(lèi)型 |
|---|---|
| 1 | keyframe (for AVC, a seekable frame) 關(guān)鍵幀 |
| 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 |
后4位為編碼ID (CodecID)
| 值 | 類(lèi)型 |
|---|---|
| 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 |
特殊情況
視頻的格式(CodecID)是AVC(H.264)的話(huà),VideoTagHeader會(huì)多出4個(gè)字節(jié)的信息,AVCPacketType 和CompositionTime。
- AVCPacketType 占1個(gè)字節(jié)
| 值 | 類(lèi)型 |
|---|---|
| 0 | AVCDecoderConfigurationRecord(AVC sequence header) |
| 1 | AVC NALU |
| 2 | AVC end of sequence (lower level NALU sequence ender is not required or supported) |
AVCDecoderConfigurationRecord.包含著是H.264解碼相關(guān)比較重要的sps和pps信息,再給AVC解碼器送數(shù)據(jù)流之前一定要把sps和pps信息送出,否則的話(huà)解碼器不能正常解碼。而且在解碼器stop之后再次start之前,如seek、快進(jìn)快退狀態(tài)切換等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情況也是出現(xiàn)1次,也就是第一個(gè)video tag.
- CompositionTime 占3個(gè)字節(jié)
| 條件 | 值 |
|---|---|
| AVCPacketType ==1 | Composition time offset |
| AVCPacketType !=1 | 0 |
我們看第一個(gè)video tag,也就是前面那張圖。我們看到AVCPacketType =0。而后面三個(gè)字節(jié)也是0。說(shuō)明這個(gè)tag記錄的是AVCDecoderConfigurationRecord。包含sps和pps數(shù)據(jù)。
再看到第二個(gè)video tag

我們看到 AVCPacketType =1,而后面三個(gè)字節(jié)為
000043。這是一個(gè)視頻幀數(shù)據(jù)。
解析到的數(shù)據(jù)完全符合上面的理論。
sps pps
前面我們提到第一個(gè)video 一般存放的是sps和pps。這里我們具體解析下sps和pps內(nèi)容。先看下存儲(chǔ)的格式(圖6):
0x01+sps[1]+sps[2]+sps[3]+0xFF+0xE1+sps size+sps+01+pps size+pps
我們看到圖 。
sps[1]=0x64
sps[2]=00
sps[3]=0D
sps size=0x001B=27
跳過(guò)27個(gè)字節(jié)后,是0x01
pps size=0x0005=5
跳過(guò)5個(gè)字節(jié),就到了back-pointers。
視頻幀數(shù)據(jù)
解析出sps和pps tag后,后面的video tag就是真正的視頻數(shù)據(jù)內(nèi)容了

這是第二個(gè)video tag其實(shí)和圖8一樣,只是我圈出來(lái)關(guān)鍵信息。先看下格式
frametype=
0x17=00010111AVCPacketType =1
Composition Time=
0x000043后面就是NALU DATA
Audio
與視頻格式類(lèi)似
| 字段 | 字段類(lèi)型 | 字段含義 |
|---|---|---|
| SoundFormat | UB[4] | 音頻格式,重點(diǎn)關(guān)注 10 = AAC 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 | UB[2] | 采樣率,對(duì)AAC來(lái)說(shuō),永遠(yuǎn)等于3 0 = 5.5-kHz 1 = 11-kHz 2 = 22-kHz 3 = 44-kHz |
| SoundSize | UB[1] | 采樣精度,對(duì)于壓縮過(guò)的音頻,永遠(yuǎn)是16位 0 = snd8Bit 1 = snd16Bit |
| SoundType | UB[1] | 聲道類(lèi)型,對(duì)Nellymoser來(lái)說(shuō),永遠(yuǎn)是單聲道;對(duì)AAC來(lái)說(shuō),永遠(yuǎn)是雙聲道; 0 = sndMono 單聲道 1 = sndStereo 雙聲道 |
| SoundData | UI8[size of sound data] | 如果是AAC,則為 AACAUDIODATA; |
AACAUDIODATA
| 字段 | 字段類(lèi)型 | 字段含義 |
|---|---|---|
| AACPacketType | UI8 | 0: AAC sequence header 1: AAC raw |
| Data | UI8[n] | 如果AACPacketType為0,則為AudioSpecificConfig 如果AACPacketType為1,則為AAC幀數(shù)據(jù) |
AudioSpecificConfig
| 字段 | 字段類(lèi)型 | 字段含義 |
|---|---|---|
| AudioObjectType | UB[5] | 編碼器類(lèi)型,比如2表示AAC-LC |
| SamplingFrequencyIndex | UB[4] | 采樣率索引值,比如4表示44100 |
| ChannelConfiguration | UB[4] | 聲道配置,比如2代表雙聲道,front-left, front-right |
我們看到第三個(gè)TAG

這個(gè)留給大家自己來(lái)解析吧。
從AF=1010 1111
SoundFormat = 1010 = AAC編碼
SoundRate = 11 = 44-kHz
SoundSize = 1 = snd16Bit
SoundType = 1 = sndStereo 雙聲道
AACAUDIODATA
AACPacketType = 00 = AAC sequence headerAudioSpecificConfig
AudioObjectType = 00010 = 2
SamplingFrequencyIndex = 0100 = 4
ChannelConfiguration = 0010

解析工具下載地址
最后,我們使用工具來(lái)看我們的實(shí)例文件和我們自己解析的是否一致

