音视频入门基础: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;
相关推荐
潜创微科技7 小时前
IT9201+IT66021:便携 KVM 一站式方案,音视控三合一免驱即插即用
嵌入式硬件·音视频
音视频牛哥11 小时前
大牛直播SDK(SmartMediaKit)鸿蒙NEXT RTSP/RTMP低延迟播放器集成与实践指南
音视频·harmonyos·大牛直播sdk·鸿蒙rtmp播放器·鸿蒙rtsp播放器·鸿蒙next rtsp播放器·鸿蒙next rtmp播放器
阳光开朗男孩13 小时前
FFmpeg从入门到精通-1.2.ffmpeg编码支持与定制
ffmpeg
时空自由民.15 小时前
优化ESP32 ADF 音频问题
音视频
ZC跨境爬虫18 小时前
跟着 MDN 学 HTML day_41:(DOMParser 接口详解)
前端·javascript·ui·html·音视频
阳光开朗男孩19 小时前
FFmpeg从入门到精通-2.ffmpeg常用命令
ffmpeg
老王谈企服20 小时前
实在Agent智能体视频生成节点实战:多模型调度、Jinja模板与动态参数,打造自动化视频生产线
人工智能·自动化·音视频
MonkeyKing715520 小时前
iOS音频时钟、时钟同步与音频时间戳原理详解
ios·objective-c·音视频
ZC跨境爬虫20 小时前
跟着 MDN 学 HTML day_43:(DocumentFragment 接口详解)
前端·javascript·vue.js·ui·html·音视频
节点云科20 小时前
谷歌 Gemini Omni 深度解析:原生视频模型的技术突破与行业影响
人工智能·音视频