FLV
简介
FLV(Flash Video)是 Adobe 公司推出的一种媒体文件格式,是一种非常常见的音视频封装格式,尤其是在流媒体场景中。在直播领域,通常采用 RTMP 推流,HTTP-FLV 播放的方案。
FLV 由 FLV Header,FLV Body 组成,FLV Body 由 FLV Tag 组成,FLV Tag 由 FLV Tag Header 和 FLV Tag Body 组成。FLV Tag 分为 Video Tag、Audio Tag 和 Script Tag,分别用来存放视频数据、音频数据和 MetaData 数据。
FLV Header
FLV Header 占用 9 个字节。
- 3 个字节是文件的标识,固定是 FLV。
- 1 个字节表示版本
- 1 个字节中的第 6 位表示是否存在音频数据,第 8 位表示是否存在视频数据,其他位为 0 。
- 4 个字节表示 FLV Header 的大小。
直播场景中的 HTTP-FLV 播放必须首先发送 FLV Header 。
FLV Tag
FLV Tag 分为Script Tag、Video Tag 和 Audio Tag,分别用来存放 MetaData 数据、视频数据和音频数据。
FLV Tag Header
Reserved ~ StreamID 为 Tag Header 字段,共 11 字节。
FLV Script Tag
ScriptTagBody
示例
直播场景中的 HTTP-FLV 播放在发送 Video Tag 和 Audio Tag 之前,需要先发送 MetaData 即 Script Tag , 主要包括宽、高、时长、采样率等基础信息。
Script Data 使用 2 个 AMF(Action Message Forma) 包来存放信息。
第一个 AMF 包是 onMetaData 包。第 1 个字节表示的是 AMF 包的类型,第一个 AMF 包一般是字符串类型,字符串类型的值表示是 0x02,之后是 2 字节表示的长度,一般长度总是 10,值是 0x000A,之后就是 10 字节长度字符串,值是 onMetaData 。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
onMetaData
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+
第二个 AMF 包是一个 Objcet 类型的 AMF 包,Object 类型的值表示是 0x03,Objcet 元素为元素名称和值组成的对,常见的元素如下:
winshining/nginx-http-flv-module 的 meta :
static ngx_rtmp_amf_elt_t out_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("Server"),
"NGINX HTTP-FLV (https://github.com/winshining/nginx-http-flv-module)", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("width"),
&v.width, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("height"),
&v.height, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("displayWidth"),
&v.width, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("displayHeight"),
&v.height, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("duration"),
&v.duration, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("framerate"),
&v.frame_rate, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("fps"),
&v.frame_rate, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("videodatarate"),
&v.video_data_rate, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("videocodecid"),
&v.video_codec_id, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("audiodatarate"),
&v.audio_data_rate, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("audiocodecid"),
&v.audio_codec_id, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("profile"),
&v.profile, sizeof(v.profile) },
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
&v.level, sizeof(v.level) },
};
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"onMetaData", 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
out_inf, sizeof(out_inf) },
};
FLV Tag Audio Header
特别说明:
- SoundFormat = 3, Linear PCM little endian,存储原始 PCM 样本。如果数据为 8 位,则样本为无符号字节。如果数据是 16 位,则样本存储为小端、有符号数字。如果数据是立体声,则左右样本交错存储:左-右-左-右-等等。
- SoundFormat = 0 的 PCM 与 SoundFormat = 3 的 PCM 区别:SoundFormat = 0 的 PCM 按创建文件的平台的字节序顺序存储 16 位 PCM 样本。
- SoundFormat = 5 的 Nellymoser 8 kHz 和 SoundFormat = 6 的 Nellymoser 16 kHz 是特殊情况,因为 SoundRate 字段不能表示 8 kHz 和 16 kHz 采样率。当在 SoundFormat 中指定 Nellymoser 8 kHz 或 Nellymoser 16 kHz 时,Flash Player 将忽略 SoundRate 和 SoundType 字段。对于其他 Nellymoser 采样率,请指定正常的 Nellymoser SoundFormat 并照常使用 SoundRate 和 SoundType 字段。
- 如果 SoundFormat = 10 指示 AAC,SoundType 应为 1 (立体声),SoundRate 应为 3(44kHz)。但是,这并不意味着 FLV 中的 AAC 音频总是立体声、44kHz。相反,Flash Player会忽略 SoundType 和 SoundRate,而是根据 AAC Audio Data 中的 AAC sequence header 提取通道(单/双通道)和采样率。
- 如果 SoundFormat = 11 指示 Speex,则音频是以 16 kHz 采样的压缩单声道,SoundRate 应为 0,SoundSize 应为 1,SoundType 应为 0 。
FLV Tag Audio Data
AudioData 包含音频有效载荷:
- 如果加密,则为 EncrytedBody
- 如果非加密,则为 AudioTagBody
AudioTagBody :
- 如果 SoundFormat == 10 (AAC),则为 AACAudioData
- 否则,因 SoundFormat 而异,通常是对应格式的 Raw audio frame data
AACAudioData :
- 如果 AACPacketType == 0,表示 AAC Header
- 如果 AACPacketType == 1,表示 AAC Raw audio frame data
在直播场景中,若使用 AAC 格式的音频,则通常需要首先发送 AAC Header(包含通道(单/双通道)、采样率等信息 ),再发送 AAC Raw audio frame data 。
FLV Tag Video Header
FLV Tag Video Data
VideoData 包含音频有效载荷:
- 如果加密,则为 EncrytedBody
- 如果非加密,则为 VideoTagBody
VideoTagBody :
- 如果 FrameType == 5,则 VideoTagBody 为 Video frame payload 或 frame info
- 否则
2.1 如果 CodecId == 2,则 VideoTagBody 为 H263 Video Packet
2.2 如果 CodecId == 3,则 VideoTagBody 为 Screen Video Packet
2.3 如果 CodecId == 4,则 VideoTagBody 为 VP6 FLV Video Packet
2.4 如果 CodecId == 5,则 VideoTagBody 为 VP6 FLV Alpha Video Packet
2.5 如果 CodecId == 6,则 VideoTagBody 为 Screen V2 Video Packet
2.6 如果 CodecId == 7,则 VideoTagBody 为 AVC Video Packet
AVC Video Packet :
- 如果 AVCPacketType == 0,表示 AVC sequence Header
- 如果 AVCPacketType == 1,表示 One or more NALUs (more 是因为 AVC slice,在 AVC 中)
在直播场景中,若使用 AVC 格式的视频,则通常需要首先发送 AVC sequence Header (包含解码信息等 ),再发送 NALUs 。
AVCPacketType == 0 时,AVCPacket 为 AVC sequence header。 AVC sequence header 是 AVCDecoderConfigurationRecord 结构(包含SPS、PPS等编码参数集),该结构在标准文档 ISO-14496-15 AVC file format 中有详细说明。
AVCPacketType == 1 时 AVCPacket 包含 1个 或 多个 NALU。一个 AVCPacket 应该包含一个 frame,但是在 H.264 的 VCL (视频编码层)中可能会将 frame 分成 slice,每个 slice 作为 RBSP 被封装在单独的 NALU 中,因此一个 AVCPacket 包含一个 frame 时可能需要包含多个 NALU (即多个 slice)。
FLV 中包含的 NALU 通常采用 AVCC 格式封装,AVCC在每个NALU前都加上一个大端格式的前缀(1、2、4 字节,通常采用 4 字节)代表NALU长度。
HTTP-FLV
HTTP-FLV 是将音视频数据以 FLV 文件格式进行封装,再将 FLV 格式数据封装在 HTTP 协议中进行传输的一种流媒体传输方式。
HTTP-FLV 被广泛采用的原因:
- HTTP 优点:
a. 一些防火墙会墙掉 RTMP 或者其他的一些协议,但是防火墙对 HTTP 非常友好,不会墙掉 HTTP,因此基于 HTTP 传输的成功率更高。 - FLV 优点:
a. MP4、MKV 等封装格式将音视频数据和音视频元数据、索引、时间戳等分开存放,必须拿到完整的音视频文件才能播放,因为里面的单个音视频数据块不带有时间戳信息,播放器不能将这些没有时间戳信息的数据块连续起来,因此不能实时的解码播放。(当然 MP4 后来扩展了 FMP4 用于流媒体)
b. FLV 格式的 FLV Tag Header 中携带时间戳,FLV 将每一帧音视频数据(Tag Body)封装成包含时间戳等音视频元数据(Tag Header)的数据包(Tag)。当播放器拿到 Tag 后,可根据时间戳等音视频元数据进行解码和播放。
HTTP-FLV 的实现原理: HTTP-FLV 利用 HTTP/1.1 分块传输机制发送 FLV 数据。虽然直播服务器无法知道直播流的长度,但是 HTTP/1.1 分块传输机制可以不填写 conten-length
字段而是携带 Transfer-Encoding: chunked
字段,这样客户端就会一直接受数据。