懒人李冰

记录我的生活、学习

媒体文件格式分析之FLV

FLV 是 FLASH VIDEO 的简称,FLV 流媒体是随着Flash MX 的推出发展而来的视频格式。FLV 一般由文件头(FLV header) 和文件体(FLV body) 组成。其中文件体(FLV body)由一些列tag组成,tag又可分成三类:audio/video/script,分别代表音频流、视频流、脚本流(关键字或文件信息之类)。 在 FLV 文件中,每种 tag 类型都由一个单独的流组成,即在 FLV 文件中最多有一个视频流和一个音频流,对同一种类型的流,FLV 中不能够定义多个独立的流。 与 SWF 文件不同,FLV 文件以大字节序存储多字节。比如,0x300(0x12C) 在 SWF 文件中的字节序为 0x2C 0x01,在FLV 文件中则为 0x01 0x2C。

FLV 文件的整体结构如下图所示:

FLV 视频标准格式标准

FLV Header

以 FLV Header 46 4C 56 01 05 00 00 00 09为例:

Field Type Comment
Signature 1 byte 必须为’F’(0x46)
Signature 1 byte 必须为’L’(0x4C)
Signature 1 byte 必须为’V’(0x56)
(版本)Version 1 byte 通常为0x01
TypeFlagsReserved 5 bits 必须为0
TypeFlagsAudio 1 bit 表示是否含有音频
TypeFlagsReserved 1 bit 必须为0
TypeFlagsVideo 1 bit 表示是否含有视频
DataOffset 4 bytes 文件头部的大小(从文件开始位置到body的偏移量),通常为9

FLV Header 的前三个字节是固定的FLV的 ASCII 码的值0x46 0x4C 0x56; 接下来的一个字节表示 FLV 的版本号,例如 0x01 代表 FLV 版本号为 1。第 5 个字节中的第0位和第2位分别表示video和audio的存在情况(1表示存在,0表示不存在),其余6位必须为0.最后的4字节表示FLV Header的长度,对于version 1,此处为9.

FLV File Body

FLV Header 之后,FLV 文件的剩余部分称为 Body,它是由tag组成,它们交替如下:

Field Type Comment
PreviousTagSize0 4 bytes 总是0
Tag1 FLVTAG结构 第一个tag
PreviousTagSize0 4 bytes 上一个tag的大小,包含了tag的头部。对FLV版本1来讲,它的值等于上一个tag的数据大小+11
Tag2 FLVTAG结构 第二个tag
PreviousTagSizeN - 1 4 bytes 倒数第二个tag的大小
TagN FLVTAG结构 最后一个tag
PreviousTagSizeN 4 bytes 最后一个tag的大小

FLV tags

FLV tag格式如下:

Field Type Comment
Tag类型(TagType) 1 bytes 8:音频、9:视频、18:script数据
数据大小(DataSize) 3 bytes 数据字段的长度
时间戳(Timestamp) 3 bytes 毫秒为单位,第一个tag时,该值总是0
时间戳扩展(TimeStampExtended) 1 bytes 时间戳扩展为4bytes,代表高8位,很少用到
流ID 3bytes 总是0
数据(Data) 音频、视频或script 数据实体

以 Tag 12 00 12 A9 00 00 00 00 00 00 00 02 00 0A……为例,0x12代表该 tag 为script data,00 12 A9代表该 tag 的 DataSize 为 681 byte,00 00 00代表该 tag 的 TimeStamp 为 0,00代表该 tag 的 TimeStampExtended 为 0,StreamID 总是 0,接下来的 681 byte 为script data 的内容。

播放过程中,FLV tag的时间信息完全依赖于 FLV 时间戳,内置的其他时间信息都被忽略掉。

Audio tags

Audio Tag 与 SWF 文件格式中的 DefineSound Tag 类似。它们的 Payload 数据除了额外的Nellymoser 8-kHz格式外是相同的,该格式在 SWF 格式中不被允许。关于 SWF 文件格式的介绍,请看SWF(File Format Sepecification).

Audio Data
数据大小 名称 备注
bit[4] Sound Format 2(0x2)-MP3;3(0x3)-PCM;10(0xA)-AAC
bit[2] Sample Rate 0(0x0)-5500Hz;1(0x1)-11025Hz;2(0x2)-22050Hz;3(0x3)-44100Hz
bit[1] Sample Size 0(0x0)-8bit;1(0x1)-16bit
bit[1] Channel Count 0(0x0)-Mono;1(0x1)-Stereo
byte[Payload Size -1] Sound Data 音频数据,随 Sound Format 不同,格式也不同

如果 Sound Format 为 0xA(AAC),Sound Data 为:

数据大小 名称 备注
byte Type 0(0x0)-Sequence Header;1(0x1)-Raw;一般 Sequence Header 为第一个 Audio Tag,并且全文件只出现一次
bit[5] Object Type 1(0x1)-AAC Main;2(0x2)-AAC LC;31(0xFE)-Escape
bit[6] Extend Object Type 如果 Object Type 为 0xFE
bit[4] Sample Frequency Index 0~12分别为96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350
uint24 Sample Frequeny 如果 Sample Frequency Index 为 15,此处为 Sample Frequency
bit[4] Channel Count 声道数,4 为 Stereo
AACAUDIODATA

AAC格式在Flash Player中被支持。

Field Type Comment
AACPacketType UI8 0:AAC sequence header;1:AAC raw
Data UI8[n] if AACPacketType == 0 AudioSpecificConfig else if AACPacketType == 1 Raw AAC frame data

Video tags

Video Tag 与 SWF 文件格式中的 VideoFrame Tag 类似,它们的 Payload 数据是同一的。关于 SWF 文件格式的介绍,请看SWF(File Format Specification)

09 00 00 2D 00 00 00 00 00 00 00 17 00 00 00 00 01 4D 40 1F FF E1 00 19 67 4D 40 ……为例,09表示 Tag Type 为 Video Tag;00 00 2D表示 DataSize 为 45;00 00 00表示 Timestamp 为 0;00表示 TimestampExtended 为 0;00 00 00表示 StreamID 为 0;17中的 8 bit,其中前 4 bit1表示 FrameType 为 keyframe,后 4 bit7表示 CodecID 为 AVC;之后的 45 byte 为 VideoData;

Video Data
Field Type Comment
帧类型(FrameType) 4bits 1.关键帧 2.inter frame(for AVC, a non-seekable frame) 3.disposable inter frame(H.263 only) 4.generated keyframe(reserved for serve use only) 5.video info/command frame
CodeID 4bits 1:JPEG 2:H.263 3:Screen video 4:VP6 5:VP6 6:Screen video version 2 7:AVC
视频数据(VideoData) if CodecID == 7 AVCVIDEOPACKET Video frame payload or UI8(see note following table)

如果 FrameType = 5,此时的 VideoData 含义如下:

  • 0 = Start of client-side seeking video frame sequence
  • 1 = End of client-side seeking video frame sequence
AVCVIDEOPACKET

AVCVIDEOPACKET 携带一个AVC video data。

Field Type Comment
AVCPacketType 1 byte 0:AVC sequence header 1:AVC NALU 2:AVC end of sequence
CompositionTime SI24 if AVCPacketType == 1 CompositionTime offset else 0
Data UI8[n] if AVCPacketType == 0 AVCDecoderConfigurationRecord else if AVCPacketType == 1 one or more NALUs

Data tags

onMetaData

  • duration : 一个 DOUBLE 指定了整个文件的总时长,单位 seconds
  • width : 一个 DOUBLE 指定了视频的宽,单位 pixel
  • height : 一个 DOUBLE 指定了视频的高,danwei pixel
  • videodatarate : 一个DOUBLE 指定了 video bit rate,单位 kilobits per second
  • framerate : 一个DOUBLE 指定了每秒的 frame 数
  • videocodeid : 一个DOUBLE 指定了 video codec ID
  • audiosamplerate : 一个 DOUBLE 指定了音频采样的分辨率
  • stereo : 一个 BOOL 指定了data 是否是stereo
  • filesize : 一个 DOUBLE 指定了文件的总大小,单位 byte.

FFmpeg FLV Demuxer源码分析

FFmpeg 中关于flv demuxer的定义如下:

ff_flv_demuxer
1
2
3
4
5
6
7
8
9
10
11
12
AVInputFormat ff_flv_demuxer = {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .priv_data_size = sizeof(FLVContext),
    .read_probe     = flv_probe,
    .read_header    = flv_read_header,
    .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
    .read_close     = flv_read_close,
    .extensions     = "flv",
    .priv_class     = &flv_class,
};

其中的flv_read_header是分析 FLV 的header函数,分析代码如下:

flv_read_header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int flv_read_header(AVFormatContext *s)
{
    int offset, flags;

    avio_skip(s->pb, 4); //将flv的头去掉
    flags = avio_r8(s->pb);//读取flv的video和audio flag信息

    s->ctx_flags |= AVFMTCTX_NOHEADER;

    if (flags & FLV_HEADER_FLAG_HASVIDEO) //如果flv中包含video, 创建视频流
        if (!create_stream(s, AVMEDIA_TYPE_VIDEO))
            return AVERROR(ENOMEM);
    if (flags & FLV_HEADER_FLAG_HASAUDIO) //如果flv中包含audio,创建音频流
        if (!create_stream(s, AVMEDIA_TYPE_AUDIO))
            return AVERROR(ENOMEM);

    offset = avio_rb32(s->pb);//获取文件头长度,通常为9
    avio_seek(s->pb, offset, SEEK_SET);//跳过DataOffset字段
    avio_skip(s->pb, 4);//跳过Body中的PreviousTagSize0字段,大小即为4bytes

    s->start_time = 0;

    return 0;
}

FLV中关于tag的处理使用flv_read_packet,代码如下:

flv_read_packet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    FLVContext *flv = s->priv_data;
    int ret, i, size, flags;
    enum FlvTagType type;
    int stream_type=-1;
    int64_t next, pos, meta_pos;
    int64_t dts, pts = AV_NOPTS_VALUE;
    int av_uninit(channels);
    int av_uninit(sample_rate);
    AVStream *st    = NULL;

    /* pkt size is repeated at end. skip it */
    for (;; avio_skip(s->pb, 4)) {
        pos  = avio_tell(s->pb);
        type = (avio_r8(s->pb) & 0x1F);//获取tag的类型,分为三种:0x08:AUDIO,0x09:VIDOE0x12:META
        size = avio_rb24(s->pb);//tag的长度
        dts  = avio_rb24(s->pb);//获取时间戳
        dts |= avio_r8(s->pb) << 24;//获取时间戳扩展
        av_log(s, AV_LOG_TRACE, "type:%d, size:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, dts, avio_tell(s->pb));
        if (avio_feof(s->pb))
            return AVERROR_EOF;
        avio_skip(s->pb, 3); /* stream id, always 0 */
        flags = 0;

        ...

        if (size == 0)
            continue;

        next = size + avio_tell(s->pb);

        if (type == FLV_TAG_TYPE_AUDIO) {
            stream_type = FLV_STREAM_TYPE_AUDIO;
            flags    = avio_r8(s->pb);
            size--;
        } else if (type == FLV_TAG_TYPE_VIDEO) {
            stream_type = FLV_STREAM_TYPE_VIDEO;
            flags    = avio_r8(s->pb);
            size--;
            if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD)
                goto skip;
        } else if (type == FLV_TAG_TYPE_META) {
            stream_type=FLV_STREAM_TYPE_DATA;
            if (size > 13 + 1 + 4) { // Header-type metadata stuff
                int type;
                meta_pos = avio_tell(s->pb);
                type = flv_read_metabody(s, next);
                if (type == 0 && dts == 0 || type < 0 || type == TYPE_UNKNOWN) {
                    goto skip;
                } else if (type == TYPE_ONTEXTDATA) {
                    avpriv_request_sample(s, "OnTextData packet");
                    return flv_data_packet(s, pkt, dts, next);
                } else if (type == TYPE_ONCAPTION) {
                    return flv_data_packet(s, pkt, dts, next);
                }
                avio_seek(s->pb, meta_pos, SEEK_SET);
            }
        } else {
            av_log(s, AV_LOG_DEBUG,
                   "Skipping flv packet: type %d, size %d, flags %d.\n",
                   type, size, flags);