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

=================================================================

音视频入门基础:H.264专题系列文章:

音视频入门基础:H.264专题(1)——H.264官方文档下载

音视频入门基础:H.264专题(2)——使用FFmpeg命令生成H.264裸流文件

音视频入门基础:H.264专题(3)——EBSP, RBSP和SODB

音视频入门基础:H.264专题(4)——NALU Header:forbidden_zero_bit、nal_ref_idc、nal_unit_type简介

音视频入门基础:H.264专题(5)——FFmpeg源码中 解析NALU Header的函数分析

音视频入门基础:H.264专题(6)——FFmpeg源码:从H.264码流中提取NALU Header、EBSP、RBSP和SODB

音视频入门基础:H.264专题(7)——FFmpeg源码中 指数哥伦布编码的解码实现

音视频入门基础:H.264专题(8)——H.264官方文档的描述符

音视频入门基础:H.264专题(9)——SPS简介

音视频入门基础:H.264专题(10)——FFmpeg源码中,存放SPS属性的结构体和解码SPS的函数分析

音视频入门基础:H.264专题(11)——计算视频分辨率的公式

音视频入门基础:H.264专题(12)——FFmpeg源码中通过SPS属性计算视频分辨率的实现

音视频入门基础:H.264专题(13)——FFmpeg源码中通过SPS属性获取视频色彩格式的实现

音视频入门基础:H.264专题(14)——计算视频帧率的公式

音视频入门基础:H.264专题(15)——FFmpeg源码中通过SPS属性获取视频帧率的实现

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

音视频入门基础:H.264专题(17)——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.rhkb.cn/news/384109.html

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

相关文章

Redis (常用数据结构和命令)

目录 简介 概述 特点 数据结构 常用命令 通用命令 keys del exists expire 与 ttl String 命令 SET 和GET: MSET和MGET INCR和INCRBY和DECY SETNX SETEX Redis 命令 Key 的层级结构 key层级关系 &#xff1a; Hash命令 HSET和HGET HMSET和HMGET HGETALL H…

免杀笔记 -->API的整理Shellcode加密(过DeFender)

最近更新频率明显下降我懒&#xff0c;那么今天就来记录一下我们的一些常用的API的整理以及ShellCode的加密。 1.WinAPI整理 问我为什么要整理&#xff1f; 就是用起来的时候要左翻右翻 &#xff1a;&#xff1a; 烦死了 1.VirtualAlloc VirtualAlloc(NULL,sizeof(buf),MEM_…

FastAPI(七十五)实战开发《在线课程学习系统》接口开发-- 创建课程

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 上次我们分享了&#xff0c;FastAPI&#xff08;七十四&#xff09;实战开发《在线课程学习系统》接口开发-- 删除留言 从本篇文章开始&#xff0c;…

Potree在web端显示大型点云模型文件

一、克隆项目代码&#xff08;准备好上网工具&#xff0c;得先有node.js npm 环境&#xff09; git clone https://github.com/potree/potree.git二、依赖安装&#xff08;换淘宝镜像能快一些&#xff09; cd potree npm install三、运行 npm start四、使用样例 打开浏览器…

python黑马笔记

运算符&#xff1a; 算术运算符&#xff1a; 加 - 减 * 乘 / 除 // 整除 % 取余 ** 求平方 除法计算得出的结果都是小数 赋值运算符&#xff1a; 标准赋值&#xff1a; 复合赋值&#xff1a; 、 - 、 * 、 / 、// 、 ** 字符串&#xff1a; 字符串拓展内容&#xf…

Vue 3 实现左侧列表点击跳转滚动到右侧对应区域的功能

使用 Vue 3 实现左侧列表点击跳转到右侧对应区域的功能 1. 引言 在这篇博客中&#xff0c;我们将展示如何使用 Vue 3 实现一个简单的页面布局&#xff0c;其中左侧是一个列表&#xff0c;点击列表项时&#xff0c;右侧会平滑滚动到对应的内容区域。这种布局在很多应用场景中都…

力扣高频SQL 50 题(基础版)第三题

文章目录 力扣高频SQL 50 题&#xff08;基础版&#xff09;第三题1148.文章浏览题目说明思路分析实现过程准备数据实现方式结果截图 力扣高频SQL 50 题&#xff08;基础版&#xff09;第三题 1148.文章浏览 题目说明 Views 表&#xff1a; ---------------------- | Colu…

Internet Download Manager(IDM)2024中文版本有哪些新功能?6.42版本功能介绍

1. Internet Download Manager&#xff08;IDM&#xff09;是一款功能强大的下载管理器&#xff0c;支持所有流行的浏览器&#xff0c;并可提升下载速度高达5倍。 2. IDM具有智能下载逻辑加速器&#xff0c;可以设置文件下载优先级、分块下载等&#xff0c;提高下载效率。 IDM…

数据结构(5.3_4)——线索二叉树的概念

普通二叉树找某结点前驱和后继的方法 中序线索二叉树 n个结点的二叉树&#xff0c;有n1个空链域!可用来记录前驱&#xff0c;后继的信息 中序线索二叉树的存储结构 //线索二叉树结点 typedef struct ThreadNode {ElemType data;struct BiTNode* lchild, * rchild;int ltag,…

Facebook的隐私之战:用户数据保护的挑战与未来

在数字化时代&#xff0c;隐私问题成为了公众关注的焦点&#xff0c;而作为全球最大的社交网络平台之一&#xff0c;Facebook&#xff08;现已更名为Meta&#xff09;在用户数据保护方面面临着巨大的挑战。从数据泄露到隐私政策的变化&#xff0c;Facebook的隐私之战不仅关乎其…

前端三大主流框架Vue React Angular有何不同?

前端主流框架&#xff0c;Vue React Angular&#xff0c;大家可能都经常在使用&#xff0c;Vue React&#xff0c;国内用的较多&#xff0c;Angualr相对用的少一点。但是大家有思考过这三大框架的不同吗&#xff1f; 一、项目的选型上 中小型项目&#xff1a;Vue2、React居多…

规范:前后端接口规范

1、前言 随着互联网的高速发展&#xff0c;前端页面的展示、交互体验越来越灵活、炫丽&#xff0c;响应体验也要求越来越高&#xff0c;后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻&#xff0c;从而导致前后端研发各自专注于自己擅长的领域深耕细作。 然…

SpringMVC基础

SpringMVC ssm&#xff1a;mybatisSpringSpringMVC MVC三层架构 1、什么是MVC MVC是模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;的简写&#xff0c;是一种软件设计规范 是将业务逻辑、数据、显示分离…

应用层自定义协议以及序列化和反序列化

文章目录 应用层自定义协议以及序列化和反序列化1、应用层自定义协议1.1、应用层1.2、协议 2、序列化和反序列化3、TCP 为什么支持全双工4、jsoncpp基础4.1、序列化4.2、反序列化 5、实现网络版计算器6、手写序列化和反序列化 应用层自定义协议以及序列化和反序列化 1、应用层…

Anconda 快速常用命令简洁版

目的&#xff1a;简单清楚的使用基本的conda 命令 可能需求 查看项目中的虚拟环境及依赖是否满足需求操作新环境来满足项目或者论文的实现 Anconda 常用命令 conda 查看基础命令1. 进入Anaconda 环境2. 查看版本3.查看有哪些虚拟环境4.激活虚拟环境5. 进入虚拟环境查看6. 退出…

基于STM32瑞士军刀--【FreeRTOS开发】学习笔记(二)|| 堆 / 栈

堆和栈 1. 堆 堆就是空闲的一块内存&#xff0c;可以通过malloc申请一小块内存&#xff0c;用完之后使用再free释放回去。管理堆需要用到链表操作。 比如需要分配100字节&#xff0c;实际所占108字节&#xff0c;因为为了方便后期的free&#xff0c;这一小块需要有个头部记录…

IDEA缓存和索引

IDEA缓存和索引 —2020年06月10日 IntelliJ IDEA首次加载项目的时候。都会创建索引&#xff0c;而创建索引的时间根项目的文件多少成正比。 IntelliJ IDEA的缓存和索引主要是用来加快文件查询&#xff0c;从而加快各种查找、代码提示等操作的速度。 某些特殊情况下&#xf…

Go语言入门

目录 前言——碎碎念 环境安装 配置环境变量 变量的定义 数据类型 数字型 字符与字符串 数据类型转换 运算符 算术运算符 关系运算符 逻辑运算符 位运算符&#xff08;二进制&#xff09; 赋值运算符 其他运算符&#xff08;指针&#xff09; 键盘的输入与输出…

使用 Python创建照片文件复制和压缩工具

在这篇博客中&#xff0c;我们将探索如何使用 wxPython 创建一个 GUI 工具&#xff0c;用于选择文件夹中的照片文件、显示预览、选择并复制文件到指定目录&#xff0c;以及将选中的照片压缩到一个 ZIP 文件中。这个工具不仅功能强大&#xff0c;而且提供了用户友好的界面。 C:\…

3.多租户调研1

https://gitee.com/xiaoqiangBUG/hello-ruoyi-cloud.git 1.mybatis plus 的插件 TenantLineInnerInterceptor 是 MyBatis Plus 框架中的一个拦截器&#xff0c;它用于实现多租户系统的数据隔离。在多租户应用中&#xff0c;不同的租户应该只能访问到自己的数据&#xff0c;而…