音视频入门基础: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;
相关推荐
川冰ICE9 小时前
⑮ AI音乐与音频:工具详解与创作流程
人工智能·音视频
oort12310 小时前
VLStream:全开源决策式AI视频平台,赋能企业构建自主可控、降本增效的智能视觉应用介绍
大数据·开发语言·人工智能·开源·音视频·数据库架构
wangchensong11 小时前
[特殊字符] 用了一段时间 LockBox,说说我的真实感受
音视频·pdf加密·视频加密
Hommy8812 小时前
【剪映小助手】音频处理接口
前端·音视频·剪映小助手·视频剪辑自动化
若兰幽竹13 小时前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十二) | 多媒体 | AVPlayer嵌入教学视频——让智慧屏真正“活”起来
音视频·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房·harmonyos6.1
chenying99817913 小时前
扩散模型语音克隆:参考音频注入的五种方式
人工智能·音视频·语音合成
2023自学中15 小时前
imx6ull开发板 移植 ffmpeg 4.2.11 + x264 视频编码库
linux·ffmpeg·音视频·嵌入式·开发板
向宇it17 小时前
【AI视频】生成AI短剧、漫剧
人工智能·ai·音视频·动画·ai视频·短剧
一抹烟霞18 小时前
# 视频隐空间基础
人工智能·音视频
jbk331118 小时前
画面重构,字幕配音原创,彻底改变视频指纹暗水印,剪映二次视频创作关键技术教程分享
人工智能·音视频·剪辑软件·剪映自动化软件