音视频入门基础:H.264专题(16)——FFmpeg源码中,判断某文件是否为H.264裸流文件的实现

news2024/11/25 2:28:27

一、引言

通过FFmpeg命令可以判断出某个文件是否为AnnexB格式的H.264裸流:

54fbae6fa0ce4a01954dd6b5e66ab738.png

所以FFmpeg是怎样判断出某个文件是否为AnnexB格式的H.264裸流呢?它内部其实是通过h264_probe函数来判断的。从文章《FFmpeg源码:av_probe_input_format3函数分析》中我们可以知道:

FFmpeg中实现容器格式检测的函数是av_probe_input_format3函数,其内部通过循环while ((fmt1 = av_demuxer_iterate(&i))) 拿到所有容器格式对应的AVInputFormat结构,然后通过score = fmt1->read_probe(&lpd)语句执行不同容器格式对应的解析函数,根据是否能被解析,以及匹配程度,来判断出这是哪种容器格式。而AnnexB格式的H.264裸流对应的解析函数就是h264_probe函数。

二、h264_probe函数的定义

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

#define MAX_SPS_COUNT          32
#define MAX_PPS_COUNT         256

static int h264_probe(const AVProbeData *p)
{
    uint32_t code = -1;
    int sps = 0, pps = 0, idr = 0, res = 0, sli = 0;
    int i, ret;
    int pps_ids[MAX_PPS_COUNT+1] = {0};
    int sps_ids[MAX_SPS_COUNT+1] = {0};
    unsigned pps_id, sps_id;
    GetBitContext gb;

    for (i = 0; i + 2 < p->buf_size; i++) {
        code = (code << 8) + p->buf[i];
        if ((code & 0xffffff00) == 0x100) {
            int ref_idc = (code >> 5) & 3;
            int type    = code & 0x1F;
            static const int8_t ref_zero[] = {
                 2,  0,  0,  0,  0, -1,  1, -1,
                -1,  1,  1,  1,  1, -1,  2,  2,
                 2,  2,  2,  0,  2,  2,  2,  2,
                 2,  2,  2,  2,  2,  2,  2,  2
            };

            if (code & 0x80) // forbidden_bit
                return 0;

            if (ref_zero[type] == 1 && ref_idc)
                return 0;
            if (ref_zero[type] == -1 && !ref_idc)
                return 0;
            if (ref_zero[type] == 2) {
                if (!(code == 0x100 && !p->buf[i + 1] && !p->buf[i + 2]))
                    res++;
            }

            ret = init_get_bits8(&gb, p->buf + i + 1, p->buf_size - i - 1);
            if (ret < 0)
                return 0;

            switch (type) {
            case 1:
            case 5:
                get_ue_golomb_long(&gb);
                if (get_ue_golomb_long(&gb) > 9U)
                    return 0;
                pps_id = get_ue_golomb_long(&gb);
                if (pps_id > MAX_PPS_COUNT)
                    return 0;
                if (!pps_ids[pps_id])
                    break;

                if (type == 1)
                    sli++;
                else
                    idr++;
                break;
            case 7:
                skip_bits(&gb, 14);
                if (get_bits(&gb, 2))
                    return 0;
                skip_bits(&gb, 8);
                sps_id = get_ue_golomb_long(&gb);
                if (sps_id > MAX_SPS_COUNT)
                    return 0;
                sps_ids[sps_id] = 1;
                sps++;
                break;
            case 8:
                pps_id = get_ue_golomb_long(&gb);
                if (pps_id > MAX_PPS_COUNT)
                    return 0;
                sps_id = get_ue_golomb_long(&gb);
                if (sps_id > MAX_SPS_COUNT)
                    return 0;
                if (!sps_ids[sps_id])
                    break;
                pps_ids[pps_id] = 1;
                pps++;
                break;
            }
        }
    }
    ff_tlog(NULL, "sps:%d pps:%d idr:%d sli:%d res:%d\n", sps, pps, idr, sli, res);

    if (sps && pps && (idr || sli > 3) && res < (sps + pps + idr))
        return AVPROBE_SCORE_EXTENSION + 1;  // 1 more than .mpg

    return 0;
}

其作用就是检测某个文件是否为AnnexB格式的H.264裸流文件。

形参pd:输入型参数,为AVProbeData类型的指针。

AVProbeData结构体声明在libavformat/avformat.h中:

/**
 * This structure contains the data a format has to probe a file.
 */
typedef struct AVProbeData {
    const char *filename;
    unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size;       /**< Size of buf except extra allocated bytes */
    const char *mime_type; /**< mime_type, when known. */
} AVProbeData;

p->filename为:需要被推测格式的文件的路径。

p->buf:指向“存放从路径为p->filename的文件中读取出来的二进制数据”的缓冲区。

p->buf_size:缓冲区p->buf的大小,单位为字节。注:FFmpeg判断某个文件是否为H.264裸流时不会读取完整个H.264裸流文件,只会读取它前面的一部分,比如最开始的2048个字节。只要根据前面的这些字节就足够判断出它的格式了,所以p->buf_size的值一般就是2048。

p->mime_type:一般为NULL,可忽略。

返回值:返回一个类型为整形的分值。返回0表示该文件完全不符合AnnexB格式的H.264裸流文件的格式。返回AVPROBE_SCORE_EXTENSION + 1(也就是51)表示该文件比较符合AnnexB格式的H.264裸流文件的格式,但还需要在av_probe_input_format3函数中执行其它容器格式对应的解析函数来进行对比,最终通过最高分来确定到底是哪种容器格式。

三、h264_probe函数的内部实现原理

h264_probe函数中,首先通过下面语句,让变量code被赋值为十进制的4294967295,也就是十六进制的0xFFFFFFFF。(具体可以参考:《为什么有符号数0XFFFF FFFF代表-1?》):

uint32_t code = -1;

然后通过下面语句初始化局部变量。其中变量sps表示该路H.264码流中sps(Sequence parameter set)的数量;pps表示该路H.264码流中pps(Picture parameter set)的数量;变量idr表示该路H.264码流中IDR SLICE(Coded slice of an IDR picture)的数量;变量sli表示该路H.264码流中非IDR SLICE(Coded slice of a non-IDR picture)的数量:

int sps = 0, pps = 0, idr = 0, res = 0, sli = 0;

检测到0x000001或0x00000001的起始码时,意味读取到了某个NALU的开头,将其NALU Header中的nal_ref_idc和nal_unit_type读取出来,分别存贮到变量ref_idc和变量type中:

    for (i = 0; i + 2 < p->buf_size; i++) {
        code = (code << 8) + p->buf[i];
        if ((code & 0xffffff00) == 0x100) {
            int ref_idc = (code >> 5) & 3;
            int type    = code & 0x1F;
       //...

从文章《音视频入门基础:H.264专题(4)——NALU Header:forbidden_zero_bit、nal_ref_idc、nal_unit_type简介》中,可以知道,NALU Header中的forbidden_zero_bit 的值应为0。所以如果检测到forbidden_zero_bit 的值为1,h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式:

            if (code & 0x80) // forbidden_bit
                return 0;

我们再来看看下面语句是什么意思:

            static const int8_t ref_zero[] = {
                 2,  0,  0,  0,  0, -1,  1, -1,
                -1,  1,  1,  1,  1, -1,  2,  2,
                 2,  2,  2,  0,  2,  2,  2,  2,
                 2,  2,  2,  2,  2,  2,  2,  2
            };

            //...

            if (ref_zero[type] == 1 && ref_idc)
                return 0;
            if (ref_zero[type] == -1 && !ref_idc)
                return 0;
            if (ref_zero[type] == 2) {
                if (!(code == 0x100 && !p->buf[i + 1] && !p->buf[i + 2]))
                    res++;
            }

语句:

if (ref_zero[type] == 1 && ref_idc)
                return 0;

的意思是:根据H.264官方文档《T-REC-H.264-202108-I!!PDF-E.pdf》第65页中的表格,下面表格中的红框里面的NALU重要性低,它们的nal_ref_idc值应为0。如果它们的值大于0,则h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式:

32fe7e3f768a4ca4bfa4a81dddef8b4a.png

语句:

if (ref_zero[type] == -1 && !ref_idc)
                return 0;

的意思是:下面红框里面的NALU重要性高,它们的nal_ref_idc值应为1到3。如果它们的值为0,则h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式:

1d3a3e8e682647adb7322fc7127d7239.png

初始化GetBitContext结构体,使得接下来可以按位读取这路H.264码流中的数据。如果初始化失败,h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式(关于init_get_bits8函数可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》):

ret = init_get_bits8(&gb, p->buf + i + 1, p->buf_size - i - 1);
if (ret < 0)
    return 0;

然后如果上述读取到的NALU的NALU Header中的nal_unit_type为7,表示该NALU为sps,会执行下面语句:

switch (type) {
//...
case 7:
    skip_bits(&gb, 14);
    if (get_bits(&gb, 2))
        return 0;
    skip_bits(&gb, 8);
    sps_id = get_ue_golomb_long(&gb);
    if (sps_id > MAX_SPS_COUNT)
        return 0;
    sps_ids[sps_id] = 1;
    sps++;
    break;
//...
}

上面代码块中,语句:

if (get_bits(&gb, 2))
    return 0;

的意思是:读取sps中的reserved_zero_2bits属性。根据H.264官方文档第44页,reserved_zero_2bits的值应为0,如果它不为0,h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式:

13ccfb2bbec04549800b4e235417f165.png

上面代码块中,语句:

sps_id = get_ue_golomb_long(&gb);
if (sps_id > MAX_SPS_COUNT)
    return 0;

的意思是:读取sps中的seq_parameter_set_id属性。根据H.264官方文档第74页,seq_parameter_set_id属性的取值范围为0 ~ 31(包括0 ~ 31),所以如果读取出来的seq_parameter_set_id大于MAX_SPS_COUNT,也就是大于32,h264_probe函数返回0,表示该文件完全不符合H.264裸流文件的格式:(注:个人认为这部分的FFmpeg源码写得有bug,应该是 if(sps_id >= MAX_SPS_COUNT)才对吧?因为根据官方文档seq_parameter_set_id不能为32!!!):

47a3bfcd5b4f40988d8c665b1a2e6b78.png

h264_probe函数中nal_unit_type为其它值时的处理跟sps的大同小异,这里就不说了。


最后通过下面语句判断:该路H.264码流中,如果存在sps,存在pps,并且存在IDR SLICE或者非IDR SLICE的数量大于3个,则返回AVPROBE_SCORE_EXTENSION + 1(也就是返回51),意味着该文件比较符合AnnexB格式的H.264裸流文件格式:

if (sps && pps && (idr || sli > 3) && res < (sps + pps + idr))
        return AVPROBE_SCORE_EXTENSION + 1;  // 1 more than .mpg

    return 0;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1941864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode STM32嵌入式开发插件记录

要卸载之前搭建的VSCode嵌入式开发环境了&#xff0c;记录一下用的插件。 1.Cortex-Debug https://github.com/Marus/cortex-debug 2.Embedded IDE https://github.com/github0null/eide 3.Keil uVision Assistant https://github.com/jacksonjim/keil-assistant/ 4.RTO…

python-爬虫实例(4):获取b站的章若楠的视频

目录 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;亲人两行泪 获取b站的章若楠的视频 一、话不多说&#xff0c;先上代码 二、爬虫四步走 1.UA伪装 2.获取url 3.发送请求 4.获取响应数据进行解析并保存 总结 前言 道路千万条&#xff0c;安全第一条 爬…

剧本杀小程序搭建,互联网下的游戏新体验,实现新增收!

近几年&#xff0c;桌游备受大众青睐&#xff0c;剧本杀行业更是瞬间曝火&#xff01;拥有强大社交体验与沉浸式游戏体验的剧本杀成为了众多年轻人的新宠&#xff0c;无论是外出游玩还是好友聚会&#xff0c;剧本杀游戏都成为了首选方式。 随着互联网的发展&#xff0c;线上小…

【防火墙】防火墙NAT、智能选路综合实验

实验拓扑 实验要求 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 8&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 9&#xff0c;多出口环境基于带宽比例进行选路…

IO多路复用-select的使用详解【C语言】

1.多进程/线程并发和IO多路复用的对比 IO多路转接也称为IO多路复用&#xff0c;它是一种网络通信的手段&#xff08;机制&#xff09;&#xff0c;通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的&#xff0c;一旦检测到有文件描述符就绪&#xff08; 可以读数据…

【Java】中的List集合

目录 一、什么是List集合二、List的常用方法List的初始化元素操作1.添加元素2.删除元素3.修改元素4.查询元素 三、List集合的遍历1.for循环遍历2.增强for循环3.迭代器遍历 一、什么是List集合 List集合是最常用的一种数据结构之一。它具有动态扩容、元素添加、删除和查询等基础…

MySQL--索引(2)

InnoDB 1.索引类型 主键索引(Primary Key) 数据表的主键列使用的就是主键索引。 一张数据表有只能有一个主键&#xff0c;并且主键不能为 null&#xff0c;不能重复。 在 mysql 的 InnoDB 的表中&#xff0c;当没有显示的指定表的主键时&#xff0c;InnoDB 会自动先检查表中是…

IC秋招RTL代码合集

一 全加器和半加器 全加器 module full_adder1(input Ai, Bi, Ci,output So, Co);assign So Ai ^ Bi ^ Ci ;assign Co (Ai & Bi) | (Ci & (Ai | Bi)); endmodule module full_adder1(input Ai, Bi, Cioutput So, Co);assign {Co, So} Ai Bi Ci ; endm…

Wi-SUN无线通信技术 — 大规模分散式物联网应用首选

引言 在数字化浪潮的推动下&#xff0c;物联网&#xff08;IoT&#xff09;正逐渐渗透到我们生活的方方面面。Wi-SUN技术以其卓越的性能和广泛的应用前景&#xff0c;成为了大规模分散式物联网应用的首选。本文将深入探讨Wi-SUN技术的市场现状、核心优势、实际应用中的案例以及…

AndroidStudio 编辑xml布局文件卡死问题解决

之前项目编写的都是正常&#xff0c;升级AndroidStudio后编辑布局文件就卡死&#xff0c;还以为是AndroidStudio文件。 其实不然&#xff0c;我给整个项目增加了版权声明。所以全部跟新后&#xff0c;布局文件也增加了版权声明。估计AndroidStudio在 解析布局文件时候因为有版…

信号【Linux】

文章目录 信号处理方式&#xff08;信号递达&#xff09;前后台进程 终端按键产生信号kill系统调用接口向进程发信号阻塞信号sigset_tsigprocmasksigpending内核态与用户态&#xff1a;内核空间与用户空间内核如何实现信号的捕捉 1、信号就算没有产生&#xff0c;进程也必须识别…

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …

敲详细的springboot中使用RabbitMQ的源码解析

这里介绍的源码主要是涉及springboot框架下的rabbitmq客户端代码&#xff08;具体在springframework.amqp.rabbit包下&#xff0c;区分一下不由springboot直接接管的spring-rabbit的内容&#xff09;&#xff0c;springboot基于RabbitMQ的Java客户端建立了简便易用的框架。 sp…

jmeter实战(1)- Mac环境安装

一、安装 JDK 这个就不介绍了&#xff0c;本地自行安装 JDK 并且配置好环境变量 二、安装 Jmeter 1. 下载地址 —> 下载链接点击这里 2. 选择合适的版本下载 3. 解压到本地目录 解压后&#xff0c;会得到下面的目录文件&#xff1a; 输入cd bin&#xff0c;进入到bin…

OpenCV 直方图概念,直方图均衡化原理详解

文章目录 直方图相关概念颜色灰度级作用应用场景 C 使用OpenCV绘制直方图单通道直方图关键代码分析&#xff1a;calcHist函数分析使用OpenCV API来绘制直方图 效果图&#xff1a; 彩色三通道直方图效果图&#xff1a; 直方图均衡化概念均衡化作用均衡化效果均衡化数学原理步骤数…

项目实战二 HIS项目

目标&#xff1a; 项目的操作流程&#xff1a; 开发体系 前端开发&#xff1a;负责页面的编写 HTML CSS JavaScript 后端开发&#xff1a;看不到 摸不着的功能 常用开发语言 PHP JAVA Python 框架 &#xff1a; 半成品 做好的功能模块 版本控制 Git 分布式版本控…

vxe-table——实现切换页码时排序状态的回显问题(ant-design+elementUi中table排序不同时回显的bug)——js技能提升

之前写的后台管理系统&#xff0c;都是用的antdelement&#xff0c;table组件中的【排序】问题是有一定的缺陷的。 想要实现的效果&#xff1a; antv——table组件一次只支持一个参数的排序 如下图&#xff1a; 就算是可以自行将排序字段拼接到列表接口的入参中&#xff0c…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.3应用架构

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

vue3 -layui项目-左侧导航菜单栏

1.创建目录结构 进入cmd,先cd到项目目录&#xff08;项目vue3-project&#xff09; cd vue3-project mkdir -p src\\views\\home\\components\\menubar 2.创建组件文件 3.编辑menu-item-content.vue <template><template v-if"item.icon"><lay-ic…

计算机网络八股文(后续更新)

文章目录 一、计算机网络体系结构1、计算机网络的各层协议及作用&#xff1f; 二、物理层三、数据链路层四、网络层五、传输层1、TCP和UDP的区别&#xff1f;2、UDP 和 TCP 对应的应用场景是什么&#xff1f;3、详细介绍一下 TCP 的三次握手机制4、为什么需要三次握手&#xff…