当前位置: 首页 > article >正文

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

一、引言

在《音视频入门基础:FLV专题(7)——Tag header简介》中对Tag header进行了简介,本文讲述FFmpeg源码中是怎样解码FLV文件的Tag header,拿到里面的信息。

二、FFmpeg源码中,解码Tag header的实现

FFmpeg源码中使用flv_read_packet函数来读取每个Tag的信息。该函数的前半部分实现了解码Tag header的功能。该函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中:

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;
    int last = -1;
    int orig_size;
    int enhanced_flv = 0;
    uint32_t video_codec_id = 0;

retry:
    /* pkt size is repeated at end. skip it */
    pos  = avio_tell(s->pb);
    type = (avio_r8(s->pb) & 0x1F);
    orig_size =
    size = avio_rb24(s->pb);
    flv->sum_flv_tag_size += size + 11LL;
    dts  = avio_rb24(s->pb);
    dts |= (unsigned)avio_r8(s->pb) << 24;
    av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, 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) {
        ret = FFERROR_REDO;
        goto leave;
    }

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

    if (type == FLV_TAG_TYPE_AUDIO) {
        //...
    } else if (type == FLV_TAG_TYPE_VIDEO) {
        //...
    }else if (type == FLV_TAG_TYPE_META) {
        //...
    }else{
        //...
    }
    //...
    return ret;
}

flv_read_packet函数中,首先获取Tag header的TagType属性,赋值给局部变量type。关于avio_r8函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》。可以看到Tag header中的Filter属性并没有被保存到FFmpeg的内存中,所以FFmpeg源码内部是不会判断FLV文件是否被加密的:

    type = (avio_r8(s->pb) & 0x1F);

在FFmpeg源码中TagType属性对应的枚举成员有三个:FLV_TAG_TYPE_AUDIO、FLV_TAG_TYPE_VIDEO、FLV_TAG_TYPE_META。如果局部变量type的值为FLV_TAG_TYPE_AUDIO表示该Tag为音频Tag;如果值为FLV_TAG_TYPE_VIDEO表示是视频Tag;如果值为FLV_TAG_TYPE_META表示是脚本Tag:

enum FlvTagType {
    FLV_TAG_TYPE_AUDIO = 0x08,
    FLV_TAG_TYPE_VIDEO = 0x09,
    FLV_TAG_TYPE_META  = 0x12,
};

获取Tag header的DataSize属性,即该Tag以字节为单位的Tag data的长度,赋值给局部变量orig_size和size:

    orig_size =
    size = avio_rb24(s->pb);

从《音视频入门基础:FLV专题(7)——Tag header简介》可以知道,DataSize属性的值等于整个Tag的长度 - 11。所以让DataSize属性的值加上11就是整个Tag的长度。所以flv->sum_flv_tag_size为已被读取的各个Tag加起来的总长度,单位为字节:

    flv->sum_flv_tag_size += size + 11LL;

获取Tag header的Timestamp和TimestampExtended属性,合成1个4字节的解码时间戳,赋值给局部变量dts:

    dts  = avio_rb24(s->pb);
    dts |= (unsigned)avio_r8(s->pb) << 24;

如果已经读取到文件末尾,返回AVERROR_EOF。关于avio_feof函数用法可以参考:《FFmpeg源码:avio_feof函数分析》:

    if (avio_feof(s->pb))
        return AVERROR_EOF;

如果还没有读取到文件末尾,继续往下执行,跳过Tag header的StreamID属性。关于avio_skip函数用法可以参考:《FFmpeg源码:avio_skip函数分析》:

    avio_skip(s->pb, 3); /* stream id, always 0 */

avio_tell(s->pb)是当前读取到的位置相对于文件首的偏移,关于avio_tell函数用法可以参考:《FFmpeg源码:avio_tell函数分析》。这时候已经读取完了该Tag的Tag header了,而从前面我们可以知道,局部变量size存贮该Tag的Tag data的长度。所以size + avio_tell(s->pb)为该Tag对应的PreviousTagSize相对于文件首的偏移(单位为字节):

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

根据该Tag为音频Tag、视频Tag还是脚本Tag,分别执行不同的解码操作:

    if (type == FLV_TAG_TYPE_AUDIO) {
        //...
    } else if (type == FLV_TAG_TYPE_VIDEO) {
        //...
    }else if (type == FLV_TAG_TYPE_META) {
        //...
    }else{
        //...
    }
    //...

三、总结

1.FFmpeg源码中通过flv_read_packet函数的前半部分来解码FLV文件每个Tag的Tag header,根据Tag header的TagType属性来判断该Tag的类型,然后分别执行不同的解码Tag的操作。

2.Tag header中的Filter属性并没有被保存到FFmpeg的内存中,FFmpeg源码内部是不会判断FLV文件是否被加密的。要想处理加密过的FLV文件,得改FFmpeg源码或者自己实现。


http://www.kler.cn/news/329903.html

相关文章:

  • 【重学 MySQL】五十一、更新和删除数据
  • 没有做商标变更,还做不成商标复审!
  • 自动化运维工具 Ansible
  • C++ 隐式内联函数
  • VSCODE驯服日记(四):配置SFML图形环境
  • 波阻抗,是电场矢量的模值/磁场矢量的模值
  • SQL常用语法
  • DpCas 镜头场景分割 Scene Segmentation
  • 基于微信小程序爱心领养小程序设计与实现(源码+定制+开发)
  • MySQL存储和处理XML数据
  • 数据分析-28-交互式数据分析EDA工具和低代码数据科学工具
  • 【rCore OS 开源操作系统】Rust 练习题题解: Structs
  • 探索未来:掌握python-can库,开启AI通信新纪元
  • linux dbus介绍,彻底懂linux bluez dbus
  • JS进阶 2——构造函数、数据常用函数
  • 【Java】—— 集合框架:Collection接口中的方法与迭代器(Iterator)
  • 基于Springboot的在线订餐系统设计与实现(论文+源码)_kaic
  • STM32使用Keil5 在运行过程中不复位进入调试模式
  • Html5知识点介绍
  • SpringCloud-基于Docker和Docker-Compose的项目部署
  • python UNIT3 选择与循环(1)
  • 使用微服务Spring Cloud集成Kafka实现异步通信
  • 【Java基础】Java面试基础知识QA(上)
  • 关于主流电商API接口的测试及返回【douyin电商SKU接口】
  • 螺狮壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)
  • 基于深度学习的图像去噪与去模糊
  • ACL(Access Control List)访问控制列表
  • 彩虹易支付最新版源码及安装教程(修复BUG+新增加订单投诉功能)
  • 推送k8s镜像到阿里云服务器
  • 滚雪球学Oracle[2.5讲]:数据库初始化配置