FLV 封装格式
参考:
- https://github.com/ossrs/srs (srs4.0)
- http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf (Annex E)
- https://www.cnblogs.com/shawn-meng/p/15925227.html
解析工具:
1. 概述
flv 是 adobe 公司推出的一种容器格式,并且作为 rtmp 最初的负载格式得到广泛应用。
本篇文章主要记录自己对于 flv 容器格式的学习。
2. flv 结构概览
flv 容器结构较为简单,以一个本地 flv 文件为例,可以分为:
- flv header,全局只有一个,且出现在文件开头
- flv tags,剩下的都是由一个个称为 tag 的结构组成
tag 可以分为 3 类:
- script tag,存储 metadata 信息,例如文件时长、文件大小、码率等信息,全局只有一个,存储在 flv header 之后
- audio tag,存储音频帧
- video tag,存储视频帧
tag 结构如下:
+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+
| flv header | previous tag len(4 Bytes,固定 0) | tag | previous tag len | ...... | tag | previous tag len |
+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+
除了 flv header 后面的 previous_tag_len 固定为 0,其余 tag 后面的 previous_tag_len 都是前面 tag 中 tag header + tag body 的总字节数。
注意 tag header 中也有一个 DataSize 字段,此字段为 tag body 的字节数,与 previous_tag_len 的关系是 previous_tag_len = DataSize + 11(tag header 固定 11 字节)。
3. flv header
flv header 长度为 9 个字节:
字段 | 类型 | 描述 |
---|---|---|
Signature | UI8 | 总是'F', 0x46 |
Signature | UI8 | 总是'L', 0x4C |
Signature | UI8 | 总是'V', 0x56 |
version | UI8 | 文件版本号,总是1 |
Reserved | UB5 | |
TypeFlagsAudio | UB1 | 0-无音频;1-有音频 |
Reserved | UB1 | |
TypeFlagsVideo | UB1 | 0-无视频;1-有视频 |
DataOffset | UI32 | flv头的长度,总是 9 |
4. tag 结构
tag 有如下结构:
- tag header
- tag data
4.1 tag header
无论是何种类型的 tag,tag header 结构都如下所示:
字段 | 类型 | 描述 |
---|---|---|
Reserved | UB2 | |
Filter | UB1 | 表示包是否被处理过,如加密处理,0-未加密,1-加密 |
TagType | UB5 | tag类型,8-audio,9-video,18-script data,只定义了 3 种 |
DataSize | UI24 | 消息的长度,从 StreamID 到 tag 结束的字节数(等于tag的长度-11) |
Timestamp | UI24 | 数据的时间戳,单位是 ms |
TimestampExtended | UI8 | 扩展时间戳,此字节与前3字节组成 SI32 的时间戳,前三位是低24bit值 |
StreamID | UI24 | 总是0 |
AudioTagHeader | AudioTagHeader | 如果 TagType==8,接着就是音频tag头 |
VideoTagHeader | VideoTagHeader | 如果 TagType==9,接着就是视频tag头 |
EncryptionHeader | EncryptionHeader | 如果 Filter==1,接着就是加密头 |
FilterParams | FilterParams | 如果 Filter==1 才有此字段 |
Data | 不定长 | tag 的数据部分,数据格式与 TagType 的类型有关: if TagType == 8,AUDIODATA if TagType == 9,VIDEODATA if TagType == 18,SCRIPTDATA |
4.2 tag data
根据 tag header 中 TagType 取值的不同,有 audio tag data(AUDIODATA)、video tag data(VIDEODATA)、script tag data(SCRIPTDATA)。
下面章节详细介绍。
5. script tag data(SCRIPTDATA)
SCRIPTDATA 由 ScriptTagBody 组成:
字段 | 类型 | 描述 |
---|---|---|
name | SCRIPTDATAVALUE | 即 string 类型的 AMF,SCRIPTDATAVALUE.Type=2(String) |
value | SCRIPTDATAVALUE | 即 array 类型的 AMF,SCRIPTDATAVALUE.Type=8(ECMA Array) |
以上,ScriptTagBody::name 字段一般为 'onMetaData',ScriptTagBody::value 字段为多个键值对组成的 array,键值对存储了 flv 文件长度、时长、码率等信息。
6. audio tag data(AUDIODATA)
AUDIODATA 由 AudioTagHeader 和 AudioTagBody 组成。
6.1 AudioTagHeader
AudioTagHeader 结构如如下:
字段 | 类型 | 描述 |
---|---|---|
SoundFormat | UB4 | 音频编码格式,定义入下: 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 7, 8, 14, 15是保留值 |
SoundRate | UB2 | 采样率: 0 = 5.5 kHz 1 = 11 kHz 2 = 22 kHz 3 = 44 kHz |
SoundSize | UB1 | 采样位数: 0 = 8-bit 1 = 16-bit |
SoundType | UB1 | 声道模式: 0 = 单声道 1 = 双声道(立体声) |
AACPacketType | UI8 | AAC包类型,如果SoundFormat = 10 才会有这个字段: 0 = AAC sequence header 1 = AAC raw |
6.2 AudioTagBody
这里只描述 AAC 音频的格式:
字段 | 类型 | 描述 |
---|---|---|
data | IF AACPacketType == 0 AudioSpecificConfig ELSE IF AACPacketType == 1 Raw AAC frame data in UI8 [] |
AudioSpecificConfig定义在 ISO/IEC 14496-3 2009 中第1.6.2.1小节。 请注意,这与来自MP4/F4V文件的esds box的内容不同 |
其中,AudioSpecificConfig 定义如下(https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio):
5 bits: object type #AudioObjectType
if (object type == 31)
6 bits + 32: object type
4 bits: frequency index #samplingFrequencyIndex
if (frequency index == 15)
24 bits: frequency
4 bits: channel configuration #channelConfiguration
var bits: AOT Specific Config
- AudioObjectType,5bit 或 6bit+32,媒体类型
- samplingFrequencyIndex,4bit 或 32bit,采样率
- channelConfiguration,4bit,通道配置,例如取值 2 表示双通道
- AOT 即 AudioObjectType,定义在 ISO/IEC 14496-3 的 1.5.1.1 table 1.1 中,是一种键值对结构,用于扩展更多信息
7. video tag data(VIDEODATA)
VIDEODATA 由 VideoTagHeader 和 VideoTagBody 组成。
7.1 VideoTagHeader
VideoTagHeader 结构如如下:
字段 | 类型 | 描述 |
---|---|---|
Frame Type | UB4 | 视频帧类型,定义如下: 1 = 关键帧 2 = 非关键帧(inter frame) 3 = disposable inter frame (H.263 only) 4 = generated key frame (reserved for server use only) 5 = video info/command frame |
CodecID | UB4 | 编码格式,定义如下: 2 = H.263 3 = Screen video 4 = On2 VP6 5 = On2 VP6 with alpha channel 6 = Screen video version 2 7 = AVC(H264) |
AVCPacketType | if CodecID == 7 UI8 | 0 = AVC sequence header(序列头) 1 = AVC NALU 2 = AVC end of sequence |
CompositionTime | IF CodecID == 7 SI24 IF AVCPacketType == 1 {Composition time offset} ELSE |
压缩时间定义在ISO 14496-12, 8.15.3,在FLV中时间偏移单位是ms |
7.2 VideoTagBody
这里只描述 AVC/H.264 视频格式。
我们知道,h264 码流有两种组织格式,分别为 Annex-B 和 avcc(参考 https://blog.csdn.net/yue_huang/article/details/75126155),flv 采用 avcc 码流格式存储视频帧。
因此 VideoTagBody 结构如下:
字段 | 类型 | 描述 |
---|---|---|
Data | ||
IF AVCPacketType == 0 {AVCDecoderConfigurationRecord} IF AVCPacketType == 1 |
如果AVCPacketType为0,data是AVC的编码配置信息,解码器需要使用,AVCDecoderConfigurationRecord定义在ISO/IEC 14496-15 5.2.4.1,此段和MP4/FLV中 avcc box 的内容一样;如果AVCPacketType为1,data是AVC的编码帧 |
7.2.1 AVCDecoderConfigurationRecord
AVCDecoderConfigurationRecord 定义如下(主要包含 sps、pps):
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1;
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = ‘111111’b;
unsigned int(2) lengthSizeMinusOne;
bit(3) reserved = ‘111’b;
unsigned int(5) numOfSequenceParameterSets;
for (i=0; i< numOfSequenceParameterSets; i++) {
unsigned int(16) sequenceParameterSetLength ;
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
}
unsigned int(8) numOfPictureParameterSets;
for (i=0; i< numOfPictureParameterSets; i++) {
unsigned int(16) pictureParameterSetLength;
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
}
if( profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
bit(6) reserved = ‘111111’b;
unsigned int(2) chroma_format;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_luma_minus8;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_chroma_minus8;
unsigned int(8) numOfSequenceParameterSetExt;
for (i=0; i< numOfSequenceParameterSetExt; i++) {
unsigned int(16) sequenceParameterSetExtLength;
bit(8*sequenceParameterSetExtLength) sequenceParameterSetExtNALUnit;
}
}
}
7.2.2 NALUs
4Bytes nalu 长度 + nalu
8. 时间戳
tag header 中定义了 24bit 的时间戳和 8bit 的扩展时间戳(不需要扩展时为 0),除开 script tag 中的时间戳没有意义外,每个 media tag(audio、video) 都必须携带有效时间戳。
这里的时间戳指的是 dts 时间戳,单位 ms。
打时间戳可以参考 srs 和 https://www.cnblogs.com/moonwalk/p/16190871.html。
9. srs rtc2rtmp
在 srs 中,当启用 rtc2rtmp 配置时,会将 rtc 接收到的数据,格式化为 flv。
rtc h264 解封装 rtp 包的代码参见 srs/trunk/src/app/srs_app_rtc_source.cpp::SrsRtmpFromRtcBridger 类实现。
h264 格式化为 flv 参见 srs/trunk/src/app/srs_app_dvr.cpp::SrsOriginHub 类实现。
下面介绍 flv 中各个元素是如何从 rtc 得到的:
- metadata:
- 如果是存储为本地 flv 文件,那么只能得到文件大小、时长等信息,无法得到宽、高等信息,参看 srs/trunk/src/app/srs_app_dvr.cpp::SrsDvrFlvSegmenter::refresh_metadata() 函数
- 如果是直接 rtc 转 rtmp 实时流,那么不会有 metadata
- AVC sequence header(AVCDecoderConfigurationRecord),来自于解析到的 sps&&pps,参看 srs/trunk/src/app/srs_app_source.cpp::SrsOriginHub::on_video() 函数
- tag header 时间戳,来自于 rtp timestamp 和 sdp 中的时间戳频率,并且需要经过 sr 包的时间戳同步,参看 srs/trunk/src/app/srs_app_rtc_source.cpp::SrsRtcRecvTrack::cal_avsync_time() 函数
10. 多 slices 时的封装
- 封装 flv 文件:
每个 video tag 都必须将一帧的多个 slices 封装到一个 tag 中,而不是分散 slice 到多个 tag 中(分散 tag 存储时播放器会无法正确解析),slices 码流之间采用 avcc 进行组织(4Bytes size + slice) - rtc to rtmp
解析 rtp 包时,可能一帧存在多个 slices 的情况,解析逻辑应该拿出每一个 slice 然后采用 avcc 组合多个 slices - rtmp to rtc
一般来说,rtp 发送多个 slices 时,可以将多个 slices 放到一个 stap-a 包里面发送(不超过 mtu 限制),但是不会多个 slices 采用 Annex-b 的方式组合在一起后再看做成一个 slice 发送。
但是参考 srs 中的处理 https://github.com/ossrs/srs/issues/307#issuecomment-612806318,会将多个 slices 组合成一个 access unit 后再由 rtp 发送(大于 mtu 时,采用 fu-a 的拆包方式)。
srs 可以这么做的前提是,一般应用层不用理解 rtp 包里面是不是存在多个 slices 或者 0x000001 分隔符,直接按照 rtp 打包规则(主要是时间戳) 提取出 rtp payload 即可。
所以 rtmp tag 存在多个 slices 时,可以每个 slice 单独按 rtp 打包规则进行多个 nalus 的打包发送(时间戳相同);也可以先合并 slices 为一整个 access unit,再按 rtp 打包规则打包一个 nalu。
11. 流媒体应用
基于 flv 容器的流媒体协议有 http-flv 和 rtmp-flv,下面进行简单介绍。
11.1 rtmp-flv
在发送 flv 文件时:
- 跳过 flv header
- script tag 通过 @setDataFrame amf 消息进行发送
- audio、video tag 通过普通 chunk message 进行发送
- rtmp chunk msg header 多个字段来自于 tag header,所以 tag header 不需要发送,只发送 tag data 即可
需要注意的是 rtmp timestamp,如果 flv 文件中 tag 时间戳不是从 0 开始或者音视频流不是混合单调递增的(rtc2rtmp 时可能会发生这种情况),参考 srs timerjitter 的处理 https://github.com/ossrs/srs/wiki/v4_CN_TimeJitter。
11.2 http-flv
基于 http 协议的流媒体传输协议有多种,例如 http-flv、http-mp4、hls、dash 等。