音视频入门基础:FLV专题(6)——FFmpeg源码中,解码FLV header的实现

一、引言

在《音视频入门基础:FLV专题(3)------FLV header简介》中对FLV格式的FLV header进行了简介,本文讲述FFmpeg源码中是怎样解码FLV header,拿到里面的信息。执行命令ffmpeg -i XXX.flv时,FFmpeg源码内部会调用flv_probe函数检测该文件是否为FLV格式的文件(具体可以参考:《音视频入门基础:FLV专题(5)------FFmpeg源码中,判断某文件是否为FLV文件的实现》)。然后如果检测出该文件为FLV格式的文件,会调用flv_read_header函数解码FLV header。

二、flv_read_header函数的定义

flv_read_header函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中:

cpp 复制代码
static int flv_read_header(AVFormatContext *s)
{
    int flags;
    FLVContext *flv = s->priv_data;
    int offset;
    int pre_tag_size = 0;

    /* Actual FLV data at 0xe40000 in KUX file */
    if(!strcmp(s->iformat->name, "kux"))
        avio_skip(s->pb, 0xe40000);

    avio_skip(s->pb, 4);
    flags = avio_r8(s->pb);

    flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);

    s->ctx_flags |= AVFMTCTX_NOHEADER;

    offset = avio_rb32(s->pb);
    avio_seek(s->pb, offset, SEEK_SET);

    /* Annex E. The FLV File Format
     * E.3 TheFLVFileBody
     *     Field               Type    Comment
     *     PreviousTagSize0    UI32    Always 0
     * */
    pre_tag_size = avio_rb32(s->pb);
    if (pre_tag_size) {
        av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
    }

    s->start_time = 0;
    flv->sum_flv_tag_size = 0;
    flv->last_keyframe_stream_index = -1;

    return 0;
}

Fmpeg对媒体文件/流进行解复用时,会调用avformat_open_input函数,通过avformat_open_input函数内部的av_probe_input_format3函数来检测该文件是否为FLV格式的文件。如果是,avformat_open_input底层会调用flv_read_header函数来解码FLV header。

所以flv_read_header函数的作用就是解码FLV header。

形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型变量。执行flv_read_header函数后,s->priv_data会得到从FLV header中解码出来的信息。然后在flv_read_header函数外部可以通过语句FLVContext *flv = s->priv_data得到这些信息。FLVContext 结构体声明在libavformat/flvdec.c中:

cpp 复制代码
typedef struct FLVContext {
    const AVClass *class; ///< Class for private options.
    int trust_metadata;   ///< configure streams according onMetaData
    int trust_datasize;   ///< trust data size of FLVTag
    int dump_full_metadata;   ///< Dump full metadata of the onMetadata
    int wrong_dts;        ///< wrong dts due to negative cts
    uint8_t *new_extradata[FLV_STREAM_TYPE_NB];
    int new_extradata_size[FLV_STREAM_TYPE_NB];
    int last_sample_rate;
    int last_channels;
    struct {
        int64_t dts;
        int64_t pos;
    } validate_index[2];
    int validate_next;
    int validate_count;
    int searched_for_end;

    uint8_t resync_buffer[2*RESYNC_BUFFER_SIZE];

    int broken_sizes;
    int64_t sum_flv_tag_size;

    int last_keyframe_stream_index;
    int keyframe_count;
    int64_t video_bit_rate;
    int64_t audio_bit_rate;
    int64_t *keyframe_times;
    int64_t *keyframe_filepositions;
    int missing_streams;
    AVRational framerate;
    int64_t last_ts;
    int64_t time_offset;
    int64_t time_pos;

    FLVMetaVideoColor *metaVideoColor;
    int meta_color_info_flag;
} FLVContext;

返回值:flv_read_header函数固定返回0。

三、flv_read_header函数的内部实现分析

flv_read_header函数中,首先判断该文件是否为KUX文件。kux格式是优酷的一种视频加密格式,为保护版权和经营策略,只能使用优酷播放器播放:

cpp 复制代码
    /* Actual FLV data at 0xe40000 in KUX file */
    if(!strcmp(s->iformat->name, "kux"))
        avio_skip(s->pb, 0xe40000);

AVIOContext文件位置指针(s->pb->buf_ptr)一开始指向FLV header的开头,通过avio_skip函数跳过4个字节,让AVIOContext文件位置指针指向FLV header的第5个字节。关于avio_skip函数用法可以参考:《FFmpeg源码:avio_skip函数分析》:

cpp 复制代码
    avio_skip(s->pb, 4);

读取FLV header的第5个字节,赋值给局部变量flags。从《音视频入门基础:FLV专题(3)------FLV header简介》可以知道,

如果FLV header第5个字节的值为0x04(二进制的0b00000100)表示该FLV文件中仅含有音频不含视频;第5个字节的值为0x01(二进制的0b00000001)表示该FLV文件中仅含有视频不含音频;第5个字节的值为0x05(二进制的0b00000101)表示该FLV文件中既含视频也含音频:

cpp 复制代码
    flags = avio_r8(s->pb);

枚举FLV_HEADER_FLAG_HASVIDEO和FLV_HEADER_FLAG_HASAUDIO声明在libavformat/flv.h中:

cpp 复制代码
enum {
    FLV_HEADER_FLAG_HASVIDEO = 1,
    FLV_HEADER_FLAG_HASAUDIO = 4,
};

所以flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO)

= flags & (1 | 4)

= flags & 5

所以当flv->missing_streams值为0x04表示该FLV文件中仅含有音频不含视频;flv->missing_streams的值为0x01表示该FLV文件中仅含有视频不含音频;flv->missing_streams的值为0x05表示该FLV文件中既含视频也含音频:

cpp 复制代码
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);

读取FLV header的第6到第9个字节,即以字节为单位的整个FLV header的长度,赋值给局部变量offset。关于avio_rb32函数用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》:

cpp 复制代码
    offset = avio_rb32(s->pb);

把AVIOContext的文件位置指针移动到离文件开头offset字节处,即让文件位置指针指向FLV header紧接着的那个字节,也就是PreviousTagSize0。关于avio_seek函数用法可以参考:《FFmpeg源码:avio_seek函数分析》:

cpp 复制代码
    avio_seek(s->pb, offset, SEEK_SET);

读取FLV文件的PreviousTagSize0,赋值给局部变量pre_tag_size。从《音视频入门基础:FLV专题(3)------FLV header简介》可以知道,PreviousTagSize0,占4个字节,值必须为0。所以如果读取到的PreviousTagSize0值不为0,表示格式错误,打印日志:"Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0":

cpp 复制代码
    pre_tag_size = avio_rb32(s->pb);
    if (pre_tag_size) {
        av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
    }

初始化s->start_time,flv->sum_flv_tag_size,flv->last_keyframe_stream_index:

cpp 复制代码
    s->start_time = 0;
    flv->sum_flv_tag_size = 0;
    flv->last_keyframe_stream_index = -1;
相关推荐
爱思德学术3 分钟前
中国计算机学会(CCF)推荐学术会议-B(计算机网络):NOSSDAV 2026
计算机网络·音视频·虚拟现实·多媒体
别动哪条鱼2 小时前
AAC ADTS 帧结构信息
网络·数据结构·ffmpeg·音视频·aac
tokepson12 小时前
关于音频处理工具FFmpeg | 笔记备注
计算机·ffmpeg·技术·记录
零匠学堂202514 小时前
移动学习系统,如何提升企业培训效果?
java·开发语言·spring boot·学习·音视频
Silicore_Emma15 小时前
芯谷科技—D8227 双通道音频功率放大集成电路产品简介与应用推广
单片机·音视频·功率放大器·芯谷科技·便携式音频设备·双通道音频·车载音频系统
生活爱好者!15 小时前
【影视项目】NAS 部署稳定视频订阅源咪咕
服务器·网络·docker·容器·音视频
智算菩萨17 小时前
2025年Sora类视频生成模型架构剖析:时空编码与扩散机制
架构·音视频
Hello.Reader18 小时前
用纯 Go 实现一个 AES-128 加密 m3u8 视频下载器(不依赖 ffmpeg)
golang·ffmpeg·音视频·m3u8
EasyCVR18 小时前
安防监控EasyCVR视频汇聚平台RTSP流播放异常的原因排查
音视频
DisonTangor18 小时前
Step-Audio-R1 首个成功实现测试时计算扩展的音频语言模型
人工智能·语言模型·开源·aigc·音视频