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

一、引言

通过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         256static 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_bitreturn 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++;elseidr++;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 .mpgreturn 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_bitreturn 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 .mpgreturn 0;

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

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

相关文章

winfrom 文件自动生成

数据页面展示 添加定时器执行每个表数据的生成计划 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.IO; using System.Windows.Forms; using …

【STM32嵌入式系统设计与开发---拓展】——1_11_1串口通信(USART)

这里写目录标题 1、一般我们都怎么进行通信的&#xff1f;&#xff08;1&#xff09;串行通行&#xff08;Serial Communication&#xff09;&#xff08;2&#xff09;并行通信&#xff08;3&#xff09;无线网络通信&#xff08;4&#xff09;网络通信&#xff08;5&#xff…

Django [实战] 通过表单上传文件

在Django中上传文件通常涉及到以下几个步骤&#xff1a; 设置你的模型以包含一个FileField或ImageField&#xff08;如果上传的是图片&#xff09;。创建一个表单&#xff0c;该表单包含一个Form或ModelForm&#xff0c;其中包含一个FileField。在你的视图中处理上传的文件。在…

Modbus转BACnet/IP网关快速对接Modbus协议设备与BA系统

摘要 在智能建筑和工业自动化领域&#xff0c;Modbus和BACnet/IP协议的集成应用越来越普遍。BA&#xff08;Building Automation&#xff0c;楼宇自动化&#xff09;系统作为现代建筑的核心&#xff0c;需要高效地处理来自不同协议的设备数据&#xff0c;负责监控和管理建筑内…

可以免费合并pdf的软件 合并pdf文件的软件免费 合并pdf的软件免费

在数字化办公的今天&#xff0c;pdf格式因其稳定性和跨平台兼容性被广泛使用。然而&#xff0c;当我们需要将多个 pdf 文件合并为一个时&#xff0c;却往往感到力不从心。本文将为你介绍几款强大的pdf文件合并软件&#xff0c;让你轻松管理文档。 方法一、使用pdf转换器 步骤1…

easyExcel和poi的版本对应

easypoi3.0.5对应的poi版本_easypoi和poi版本对应-CSDN博客 https://github.com/alibaba/easyexcel/blob/v3.2.0/pom.xml 解决 java.lang.NoClassDefFoundError: org/apache/poi/POIXMLTypeLoader 报错-CSDN博客 参考这个文档解决的- 引入最佳版本是3.15版本 java.lang.NoClas…

微服务:网关

网关 网关,即网络的关口,当一个网络传输到另一个网络时就需要经过网关来实现 数据的路由和转发 以及 数据安全的校验 网关技术实现 SpringCloudGateWay: 基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强 SpringCloudGateWay 依赖 <!--网关--><depe…

JAVA毕业设计152—基于Java+Springboot+vue+小程序的个人健康管理系统小程序(源代码+数据库+15000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue小程序的个人健康管理系统小程序(源代码数据库15000字论文)152 一、系统介绍 本项目前后端分离带小程序(可以改为ssm版本)&#xff0c;分为用户、管理员两种…

Ubuntu下载jdk:cannot execute binary file

虚拟机上Ubuntu系统安装jdk且配置环境之后&#xff0c;java -version显示cannot execute binary file&#xff0c;多番查阅推测是由于系统和jdk版本不兼容的原因。 uname -m查看系统版本位i686&#xff0c;是32位的&#xff0c;和64位的jdk版本不兼容。因此&#xff0c;下载32位…

[css3] 如何设置边框颜色渐变

div {border: 4px solid;border-image: linear-gradient(to right, #8f41e9, #578aef) 1; }参考&#xff1a; 5种CSS实现渐变色边框&#xff08;Gradient borders方法的汇总

如何通过smtp设置使ONLYOFFICE协作空间服务器可以发送注册邀请邮件

什么是ONLYOFFICE协作空间 ONLYOFFICE协作空间&#xff0c;是Ascensio System SIA公司出品的&#xff0c;基于Web的&#xff0c;开源的&#xff0c;跨平台的&#xff0c;在线文档编辑和协作的解决方案。在线Office包含了最基本的办公三件套&#xff1a;文档编辑器、幻灯片编辑…

FPGA实验1:简单逻辑电路

一、实验目的及要求 学习Create-SOPC实验平台的使用方法&#xff1b;熟悉Quartus II 软件平台和使用 VHDL 语言设计电路的方法&#xff1b;学习简单逻辑电路的设计、仿真和硬件测试。 二、实验原理 运用Quartus II 集成环境下的VHDL文本设计方法设计半加器&#xff0c;进行波…

LInux工具(2)

目录 1.关于底行模式的一个设置 1.1设置行号 1.2取消行号 2.简单vim配置 2.1简单认识 2.2配置选项 2.3其他说明 3.库的引入 3.1背景知识 3.2对应指令 3.3相关介绍 3.4.o文件和库的链接 3.5静态库的安装和测试 3.6动静态库对比 1.关于底行模式的一个设置 1.1设置行…

黑马点评-Postman卡住sending Requst原因解决

不知道为什么&#xff0c;用这个c1e1d5的token就会一直卡死&#xff0c;但是换了一个token就解决了&#xff0c;目前不知道为什么 解决了&#xff0c;原来是这个请求下面的函数发生了死循环&#xff01;&#xff01;太瓜皮了我超&#xff01; 把num写成了count&#xff0c;导…

函数(递归)

递归&#xff1a;程序调用自身编程技巧称为递归。 在学习递归前需要粗略的了解一下内存&#xff0c;内存分为三类&#xff0c;分别是栈区、堆区和静态区。对于栈区来说&#xff0c;每调用一次函数都会为本次函数开辟一块空间&#xff0c;然而栈区也是有空间限制的&#xff0c;随…

Golang | Leetcode Golang题解之第242题有效的字母异位词

题目&#xff1a; 题解&#xff1a; func isAnagram(s, t string) bool {if len(s) ! len(t) {return false}cnt : map[rune]int{}for _, ch : range s {cnt[ch]}for _, ch : range t {cnt[ch]--if cnt[ch] < 0 {return false}}return true }

Temporal-Kit 及 Ebsynth-流程

https://www.youtube.com/watch?vBL77HVIviJM 预处理 Ebsynth-流程

全国区块链职业技能大赛第八套区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 医疗健康平台中涉及到医院、医生、患者等参与方,他们需要在区块链医疗健康平台中完成账户注册、身份上链、挂号就诊、查询病例等多种业务活动。通过对业务活动的功能分析,可以更好的服务系统的开发流程。基于医疗健康平台系统架构,以…

【SpringBoot配置文件application.yaml】笔记

详细内容见官方文档Common Application Properties 使用application.yaml进行简单配置 第一步&#xff1a;创建WebDemo第二步&#xff1a;创建application.yaml配置文件注意&#xff1a; 第三步&#xff1a;验证自己创建的yaml文件是否生效测试&#xff1a;思考&#xff1a;如…

MT7628指定分区备份固件

为了避免升级过程突然断电&#xff0c;或者其他不良操作导致的路由器“变砖”。在MT7628使用过程中&#xff0c;我们可以对固件进行备份。 MT7628原厂SDK有关于双备份的选项&#xff0c;选择对应选项后&#xff0c;可对固件进行备份。下面以SKYLAB的SKW92A模组为例进行测试说明…