FLV 封装格式

参考:

解析工具:

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 等。

posted @ 2022-04-27 20:23  小夕nike  阅读(454)  评论(0编辑  收藏  举报