封装格式 -- FLV

FLV


内部结构

FLV(Flash Video)是Adobe公司推出的一种媒体封装格式,适合流媒体传输。优点是封装简单,文件体积小。后缀为.flv。总体上看,FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag及Tag Size对组成。
封装格式 -- FLV

1. FLV Header

– 内部结构图

封装格式 -- FLV

– 关键栏位

  • SigNature:开头三个字节,固定为 FLV 的ascii码(0x46 0x4c 0x56)
  • Version:表示FLV的版本号,比如FLV版本1,则该栏位是0x01
  • FlagsAudio:1表示有audio tag,0表示没有
  • FlagsVideo:1表示有video tag,0表示没有
  • DataOffset:表示FLV header的大小,单位为字节。对于FLV版本1,固定是9,也就是header总共9字节,有效字节有5个。

2. FLV file body

  • 由一系列tag和previousTagSize组成(如图: FLV结构图);
  • Tag的大小 = 11(tag header) + n(tag body),其中previousTagSize为4字节。
  • PreviousTagSize0总是0;其它PreviousTagSizeN的值表示TagN的大小,用于逆向读取处理(因为flv没有同步字节)。

FLV tag

  • 由tag header 和 tag body组成,tag header固定为11字节。如下图:
    封装格式 -- FLV

1. 类型

FLV tag分为3种类型:

  • Video Tag:存放视频相关数据
  • Audio Tag:存放音频相关数据
  • Script Tag:存放音视频元数据(metadata),一般会是第一个tag,紧跟着FLV header,有且只有一个。

2. FLV tag header

对于FLV版本1,tag header固定为11字节。

– 内部结构图

封装格式 -- FLV

– 关键栏位

  • TagType:
    tag类型,1字节
    tag类型 含义
    0x8 该tag为audio tag
    0x9 该tag为video tag
    0x12 该tag为script tag
    其它 保留
  • DataSize:
    tag body的大小,3字节(不包含tag header的11字节),所以整个tag大小 = DataSize + 11。
  • TimeStamp:
    与第一个tag的时间戳差值,单位为毫秒,3字节。对于视频来说,就是DTS,单位为ms。
    第一个audio和video tag的Timestamp分别为0,亦表示解码顺序。
  • TimeStampExtended:
    时间戳的扩展字段,当Timestamp栏位不够用时,会启用这字段,表示最高八位。
  • StreamID:
    总是0。

3. Audio Tag Body

音频tag第一个字节包含音频数据的参数信息(固定的),从第二个字节开始为音频流数据。

– 内部结构图

封装格式 -- FLV

– 关键栏位

  • SoundFormat:音频格式。
    ID format
    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 logrithmic PCM
    9 reserved
    10 AAC
    11 Speex
    14 MP3 8 kHz
    15 Device-specific sound
  • SoundRate:采样率。
    ID 采样率
    0 5.5 kHz
    1 11 kHz
    2 22 kHz
    3 44 kHz
  • SoundSize:采样位宽。对于压缩过的音频,一般是16位。
    ID 位宽
    0 8 Bits
    1 16 Bits
  • SoundType:声道类型。
    ID 声道数
    0 单声道
    1 双声道
  • SoundData:音频数据,
    对于AAC音频,也就是SoundFormat为10的case。
    从Tag的第二个字节起被称为AACAudioData,定义如下:
    字段 字段类型 字段含义
    AACPacketType UI8 0: AAC sequence header 1: AAC raw data
    Data UI8[n] AACPacketType为0,AudioSpecificConfigAACPacketType为1,AAC帧数据

– Audio config tag

即AACPacketType为0,Tag body第1个字节为简略的参数信息,第2个字节为AAC packet type,从第3个字节开始为audio specific config data。相比于tag body第一个字节中的参数信息,AudioSpecificConfig提供了更加详细的音频信息,它的定义在ISO14496-3中1.6.2.1。
一般的AudioSpecificConfig有2字节定义如下:

|AAC Profile 5bits | 采样率 4bits | 声道数 4bits | 其他 3bits |

对于AAC audio tag,FLV文件中第一个audio tag是audio config tag。
如下图为一个audio config tag的例子:
封装格式 -- FLV
audio config tag结构图如下:
封装格式 -- FLV

– Audio raw data tag

即AACPacketType为1的case,Tag body第1个字节为简略的参数信息,第2个字节为AAC packet type,从第3个字节开始为AAC raw data,如下图:
封装格式 -- FLV

4. Video Tag Body

视频tag body第一个字节包含视频数据的参数信息(固定),从第二个字节开始为视频数据。

– 内部结构图

封装格式 -- FLV

– 关键栏位

  • FrameType:帧类型
    ID 帧类型
    1 Keyframe;对于H264,IDR帧
    2 Inter frame;对于H264,普通I帧
    3 Disposable inter frame(H.263 only)
    4 generated keyframe
    5 video info (如sps、pps);
    command frame,普通的帧
  • Codec ID: 编解码器类型
    ID 编解码器类型
    1 JPEG
    2 Sorenson H.263
    3 Screen video
    4 On2 VP6
    5 On2 VP6 with alpha channel6
    7 AVC
  • VideoData:视频数据
    CodecID VideoDataType
    2 H263VideoPacket
    3 ScreenVideoPacket
    4 VP6FlvVideoPacket
    5 VP6FlvAlphaVideoPacket
    6 ScreenV2VideoPacket
    7 AVCVideoPacket
    其中,AVCVideoPacket的定义如下:
    字段 字段类型 字段含义
    AVCPacketType UI8 0:AVC sequence header,也就是sps和pps
    1:AVC NALU
    2:AVC end of sequence
    CompositionTime SI24 AVCPacketType == 1,该栏位有效,表示pts与dts的差值,单位ms;
    否则,为0
    Data UI8[n] AVCPacketType:
    0–> AVCDecoderConfigurationRecord
    1–> NALU(一个或多个)
    2–> 空

– Video config tag

即AVCPacketType为0的case,Tag body第一个字节固定为0x17(key frame,AVC),packet data里面是该视频的SPS和PPS。
下图为实例:
封装格式 -- FLV

– Video raw data tag

即AVCPacketType为1的case,Packet data里面是NALU。
下图是实例:
封装格式 -- FLV

– video tag例子

封装格式 -- FLV

– 关于CompositionTime(cts)

CompositionTime表示pts相对于该帧dts的差值,CompositionTime= (pts – dts) ms
当B帧存在时,视频帧的pts和dts可能不同,dts在flv中是指tag header中的timestamp。
所以视频帧的pts = CompositionTime + timestamp。
如果B帧不存在,则CTS固定为0,也就是CompositionTime这个栏位0,
即pts = timestamp。

5.Script Tag Body

  • Script Tag通常用来存放跟FLV中音视频相关的元数据信息(onMetaData),比如时长、分辨率等。
    采用AMF(Action Message Format)封装了一系列数据类型,如字符串,数值,数值等。
  • AMF格式的有一系列Object和SCRIPTDATAOBJECTEND(0x9,表示script data结束),示意图如下:
    封装格式 -- FLV
  • 在Script tag中一般包含2个AMF包。
    1> 第一个AMF包,内容固定,分别为:
    		▪ 第1个字节:固定为0x02,表示字符串类型
    		▪ 第2-3个字节:UI16类型,固定为0x000A,表示字符串的长度为10(onMetaData的长度);
    		▪ 第4-13个字节:字符串onMetaData对应的16进制数字(0x6F 0x6E 0x4D 0x65 0x74 0x61 0x44 0x61 0x74 0x61);
    	(这个包与Adobe的一些API调用有关)
    	
    2> 第二个AMF包:
    	▪ 第1个字节:固定为0x08,表示数组类型;
    	▪ 第2-5个字节:UI32类型,表示数组的长度,onMetaData中具体包含哪些属性是不固定的。
    	▪ 第6个字节+:
    	比如该AMF包表示duration,则:
    		○ 第6-9个字节:0x0008,表示长度为8个字节;
    		○ 第10-17个字节:0x64 75 72 61 74 69 6F 6E,表示 duration 这个字符串;
    		○ 第18个字节:固定为0x00,表示为数值类型;
    		○ 第19-26个字节:0x...,表示具体的时长;
    
    解析出来的script tag为:
    封装格式 -- FLV
    那说明总共有26组元数据信息,比如duration为391.495s,分辨率为1280X720

实际应用遇到的问题

  1. 如何probe一个数据流为FLV类型?
    满足以下三个条件:

    i. "FLV"。
    	数据流开头前3个字节是'F', 'L', 'V' --> FLV Header前3个字节固定是'F', 'L', 'V'
    ii. 保留位。
    	数据流第5个字节 & 0xf2的值为0  --> FLV Header前5个字节的高5位和第7位为保留位,固定为0
    iii. 大小。
    	数据流第6个字节到第9个字节固定为 0x 00 00 00 09  --> FLV Header的长度固定为9个字节,记录与FLV Header最后4个字节。
    
  2. 如何获得FLV文件的duration?
    首先,从Script tag中的duration metadata中获取duration。
    然后,拿到第一个video tag的timestamp 和 最后一个video tag的timestamp,两者的差值就是duration(单位ms)

  3. 如何识别一个tag,并且一个一个tag的读取数据?
    识别方法是:先找到一个字节为0x08、0x09或者0x12,如果是tag的话,那么后面三个字节为datasize,在往后跳(datasize + 11 + 4)bytes,如果第一个字节还是0x08、0x09或者0x12,再往后跳(datasize + 11 + 4)bytes,如此循环判断五次,每次都找到0x08、0x09和0x12的话,那说明第一个找到的字节为一个tag的开始。如果有一个找不到的话,那就寻找下一个字节为0x08、0x09或者0x12,按照上述方式再进行判断。

  4. 如何seek?
    ▪ FLV官方标准对seek的支持不好,每次seek都必须从当前位置加载tag数据,直到目标位置,从而导致缓冲时间长,效果极差。
    ▪ 而较为常用但非官方的做法是在Script Tag中加入keyframe字段,用来建立关键帧时间点和文件位置的映射表。
    ▪ keyframe字段包含file positions和times两个数组,times为关键帧时间点数组,filepositions数组为关键帧在文件中偏移量数组。两个数组的长度相同,类型为double类型,且fileposition和time一一对应。借助keyframe字段,seek过程是:

    i. 根据用户想要seek的时间点,在times数组中比对最接近值,拿到该值对应数组索引,记为KeyframeIndex
    ii. 根据keyframeIndex,在fileposition数组中取得对应的偏移量,记为keyframeOffset
    iii. 从该偏移量开始读取数据即可完成seek播放。
    
上一篇:分布式部署(vue+springboot)Nginx代理导致sessionId丢失问题


下一篇:JWT Token