音视频入门基础:H.264专题(6)——FFmpeg源码:从H.264码流中提取NALU Header、EBSP、RBSP和SODB
=================================================================
音视频入门基础: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官方文档的描述符
=================================================================
一、引言
FFmpeg源码中 通过ff_h2645_packet_split这个函数将一个个NALU的NALU Header、EBSP、RBSP和SODB从H.264/H.265码流中提取出来,本文以H.264为例对该函数进行讲解。
二、ff_h2645_packet_split函数的声明
ff_h2645_packet_split函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3)的头文件libavcodec/h2645_parse.h中:
/*** Split an input packet into NAL units.** If data == raw_data holds true for a NAL unit of the returned pkt, then* said NAL unit does not contain any emulation_prevention_three_byte and* the data is contained in the input buffer pointed to by buf.* Otherwise, the unescaped data is part of the rbsp_buffer described by the* packet's H2645RBSP.** If the packet's rbsp_buffer_ref is not NULL, the underlying AVBuffer must* own rbsp_buffer. If not and rbsp_buffer is not NULL, use_ref must be 0.* If use_ref is set, rbsp_buffer will be reference-counted and owned by* the underlying AVBuffer of rbsp_buffer_ref.*/
int ff_h2645_packet_split(H2645Packet *pkt, const uint8_t *buf, int length,void *logctx, int is_nalff, int nal_length_size,enum AVCodecID codec_id, int small_padding, int use_ref);
该函数的作用是:将形参buf指向的H.264码流中的一个个NALU提取出来,解析NALU Header,分别将每个NALU的NALU Header中的属性,EBSP、RBSP和SODB存贮到形参pkt指向的内存中。
形参pkt:输出型参数。为H2645Packet *类型。
H2645Packet结构体声明在libavcodec/h2645_parse.h中:
/* an input packet split into unescaped NAL units */
typedef struct H2645Packet {H2645NAL *nals;H2645RBSP rbsp;int nb_nals;int nals_allocated;unsigned nal_buffer_size;
} H2645Packet;
执行ff_h2645_packet_split函数后,指针pkt->nals会指向一个H2645NAL类型的数组。该数组的每个元素都会存放从H.264码流中提取出来的NALU信息。比如pkt->nals[0]存放从H.264码流中提取出来的第一个NALU的信息,pkt->nals[1]存放提取出来的第二个NALU的信息,以此类推。
H2645NAL结构体声明在libavcodec/h2645_parse.h:
typedef struct H2645NAL {const uint8_t *data;int size;/*** Size, in bits, of just the data, excluding the stop bit and any trailing* padding. I.e. what HEVC calls SODB.*/int size_bits;int raw_size;const uint8_t *raw_data;GetBitContext gb;/*** NAL unit type*/int type;/*** H.264 only, nal_ref_idc*/int ref_idc;/*** HEVC only, nuh_temporal_id_plus_1 - 1*/int temporal_id;/** HEVC only, identifier of layer to which nal unit belongs*/int nuh_layer_id;int skipped_bytes;int skipped_bytes_pos_size;int *skipped_bytes_pos;
} H2645NAL;
我们记pkt->nals指向的数组的某个元素的下标为“subscript”(数组的下标都是从0开始,所以pkt->nals[subscript]表示它是第“subscript+1”个元素),则执行函数ff_h2645_packet_split后:
pkt->nals[subscript]->data变为:指向某个缓冲区的指针。该缓冲区存放 从H.264码流中提取出来的第“subscript+1”个NALU的“NALU Header + RBSP”。
pkt->nals[subscript]->size变为:pkt->nals[subscript]->data指向的缓冲区的大小,单位为字节。
pkt->nals[subscript]->size_bits变为:该NALU “NALU Header + SODB的位数”,单位为bit(1个字节等于8位)。
pkt->nals[subscript]->raw_data变为:指向某个缓冲区的指针。该缓冲区存放提取出来的第“subscript+1”个NALU的“NALU Header + EBSP”。
pkt->nals[subscript]->raw_size变为:pkt->nals[subscript]->raw_data指向的缓冲区的大小,单位为字节。
pkt->nals[subscript]->type变为:该NALU“NALU Header中的nal_unit_type”。
pkt->nals[subscript]->ref_idc变为:该NALU“NALU Header中的nal_ref_idc”。
pkt->nals[subscript]->gb.buffer的值等于:pkt->nals[subscript]->data。
pkt->nals[subscript]->gb.buffer_end变为:指向该NALU的RBSP的最后一个字节。
pkt->nals[subscript]->gb.index变为:8。表示读取完了该NALU的第一个字节(NALU Header,8位)
pkt->nals[subscript]->gb.size_in_bit的值等于:pkt->nals[subscript]->size_bits。
pkt->nals[subscript]->gb.size_in_bits_plus8的值等于:pkt->nals[subscript]->gb.size_in_bit + 8。
pkt->nb_nals为:这段H.264码流中NALU的个数。
形参buf:输入型参数。指向缓冲区的指针,该缓冲区存放“包含startcode的H.264码流”。
形参length:输入型参数。形参buf指向的缓冲区的长度,单位为字节。
形参logctx:输入型参数。用来输出日志,可忽略。
形参is_nalff:输入型参数。值一般为0,可忽略。
形参nal_length_size:输入型参数。值一般为0,可忽略。
codec_id:输入型参数。解码器的id。对于H.264码流,其值就是“AV_CODEC_ID_H264”。
small_padding:输入型参数。值一般为0或1,可忽略。
use_ref:输入型参数。值一般为0,可忽略。
返回值:提取NALU Header、EBSP、RBSP和SODB成功返回0。返回非0值表示失败。
三、ff_h2645_packet_split函数的定义
ff_h2645_packet_split函数定义在libavcodec/h2645_parse.c中:
int ff_h2645_packet_split(H2645Packet *pkt, const uint8_t *buf, int length,void *logctx, int is_nalff, int nal_length_size,enum AVCodecID codec_id, int small_padding, int use_ref)
{GetByteContext bc;int consumed, ret = 0;int next_avc = is_nalff ? 0 : length;int64_t padding = small_padding ? 0 : MAX_MBPAIR_SIZE;bytestream2_init(&bc, buf, length);alloc_rbsp_buffer(&pkt->rbsp, length + padding, use_ref);if (!pkt->rbsp.rbsp_buffer)return AVERROR(ENOMEM);pkt->rbsp.rbsp_buffer_size = 0;pkt->nb_nals = 0;while (bytestream2_get_bytes_left(&bc) >= 4) {H2645NAL *nal;int extract_length = 0;int skip_trailing_zeros = 1;if (bytestream2_tell(&bc) == next_avc) {int i = 0;extract_length = get_nalsize(nal_length_size,bc.buffer, bytestream2_get_bytes_left(&bc), &i, logctx);if (extract_length < 0)return extract_length;bytestream2_skip(&bc, nal_length_size);next_avc = bytestream2_tell(&bc) + extract_length;} else {int buf_index;if (bytestream2_tell(&bc) > next_avc)av_log(logctx, AV_LOG_WARNING, "Exceeded next NALFF position, re-syncing.\n");/* search start code */buf_index = find_next_start_code(bc.buffer, buf + next_avc);bytestream2_skip(&bc, buf_index);if (!bytestream2_get_bytes_left(&bc)) {if (pkt->nb_nals > 0) {// No more start codes: we discarded some irrelevant// bytes at the end of the packet.return 0;} else {av_log(logctx, AV_LOG_ERROR, "No start code is found.\n");return AVERROR_INVALIDDATA;}}extract_length = FFMIN(bytestream2_get_bytes_left(&bc), next_avc - bytestream2_tell(&bc));if (bytestream2_tell(&bc) >= next_avc) {/* skip to the start of the next NAL */bytestream2_skip(&bc, next_avc - bytestream2_tell(&bc));continue;}}if (pkt->nals_allocated < pkt->nb_nals + 1) {int new_size = pkt->nals_allocated + 1;void *tmp;if (new_size >= INT_MAX / sizeof(*pkt->nals))return AVERROR(ENOMEM);tmp = av_fast_realloc(pkt->nals, &pkt->nal_buffer_size, new_size * sizeof(*pkt->nals));if (!tmp)return AVERROR(ENOMEM);pkt->nals = tmp;memset(pkt->nals + pkt->nals_allocated, 0, sizeof(*pkt->nals));nal = &pkt->nals[pkt->nb_nals];nal->skipped_bytes_pos_size = FFMIN(1024, extract_length/3+1); // initial buffer sizenal->skipped_bytes_pos = av_malloc_array(nal->skipped_bytes_pos_size, sizeof(*nal->skipped_bytes_pos));if (!nal->skipped_bytes_pos)return AVERROR(ENOMEM);pkt->nals_allocated = new_size;}nal = &pkt->nals[pkt->nb_nals];consumed = ff_h2645_extract_rbsp(bc.buffer, extract_length, &pkt->rbsp, nal, small_padding);if (consumed < 0)return consumed;if (is_nalff && (extract_length != consumed) && extract_length)av_log(logctx, AV_LOG_DEBUG,"NALFF: Consumed only %d bytes instead of %d\n",consumed, extract_length);bytestream2_skip(&bc, consumed);/* see commit 3566042a0 */if (bytestream2_get_bytes_left(&bc) >= 4 &&bytestream2_peek_be32(&bc) == 0x000001E0)skip_trailing_zeros = 0;nal->size_bits = get_bit_length(nal, skip_trailing_zeros);if (nal->size <= 0 || nal->size_bits <= 0)continue;ret = init_get_bits(&nal->gb, nal->data, nal->size_bits);if (ret < 0)return ret;/* Reset type in case it contains a stale value from a previously parsed NAL */nal->type = 0;if (codec_id == AV_CODEC_ID_HEVC)ret = hevc_parse_nal_header(nal, logctx);elseret = h264_parse_nal_header(nal, logctx);if (ret < 0) {av_log(logctx, AV_LOG_WARNING, "Invalid NAL unit %d, skipping.\n",nal->type);continue;}pkt->nb_nals++;}return 0;
}
四、ff_h2645_packet_split函数的内部实现原理
ff_h2645_packet_split函数中首先通过:
bytestream2_init(&bc, buf, length);
初始化GetByteContext结构体变量bc,让bc.buffer指向“包含起始码的H.264码流”的开头(首地址)。(关于bytestream2_init函数和相关函数的用法可以参考:《FFmpeg字节操作相关的源码:GetByteContext结构体,bytestream2_init、bytestream2_get_bytes_left、bytestream2_tell函数分析》)
然后通过:
while (bytestream2_get_bytes_left(&bc) >= 4){
//...
}
判断如果距离读取完H.264码流还剩超过4个字节,则执行大括号循环体中的内容
如果没读取完这段H.264码流,执行else{//...}里面的内容:
if (bytestream2_tell(&bc) == next_avc) {
//...
}else{
//...
}
然后通过:
/* search start code */
buf_index = find_next_start_code(bc.buffer, buf + next_avc);
bytestream2_skip(&bc, buf_index);
找到这段H.264码流中值为0x000001或0x00000001的起始码的位置,让bc.buffer指向“这段H.264码流去掉第一个起始码后的位置”。
如果此时已经到了这段H.264码流的末尾,并且这段H.264码流中存在其它起始码,返回0。如果到了这段H.264码流的末尾时也没发现它里面包含任何起始码,说明这段H.264码流是无效的,返回AVERROR_INVALIDDATA:
if (!bytestream2_get_bytes_left(&bc)) {if (pkt->nb_nals > 0) {// No more start codes: we discarded some irrelevant// bytes at the end of the packet.return 0;} else {av_log(logctx, AV_LOG_ERROR, "No start code is found.\n");return AVERROR_INVALIDDATA;}
}
继续往下执行,通过:
consumed = ff_h2645_extract_rbsp(bc.buffer, extract_length, &pkt->rbsp, nal, small_padding);
拿到这段H.264码流中的第一个NALU的“NALU Header + RBSP”和“NALU Header + EBSP”。关于ff_h2645_extract_rbsp函数可以参考《FFmpeg源码:ff_h2645_extract_rbsp函数分析》
通过:
bytestream2_skip(&bc, consumed);
让bc.buffer指向 下一个NALU的开始位置。
通过:
nal->size_bits = get_bit_length(nal, skip_trailing_zeros);
拿到NALU Header + SODB的位数,单位为比特。关于get_bit_length可以参考《FFmpeg源码:get_bit_length函数分析》
通过:
ret = h264_parse_nal_header(nal, logctx);
将NALU Header解析出来。关于h264_parse_nal_header函数的用法可以参考《音视频入门基础:H.264专题(5)——FFmpeg源码中 解析NALU Header的函数分析》
该H.264码流中的NALU统计数量加1:
pkt->nb_nals++;
然后继续通过while循环来读取下一个NALU,直到读取完该H.264码流为止:
while (bytestream2_get_bytes_left(&bc) >= 4) {
//...
}
相关文章:

音视频入门基础:H.264专题(6)——FFmpeg源码:从H.264码流中提取NALU Header、EBSP、RBSP和SODB
音视频入门基础:H.264专题系列文章: 音视频入门基础:H.264专题(1)——H.264官方文档下载 音视频入门基础:H.264专题(2)——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…...

STM32实现按键单击、双击、长按、连按功能,使用状态机,无延时,不阻塞
常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。使用定时器设计状态机可以规…...

C#之Delta并联机械手的视觉同步分拣
本文导读 前面两节课程我们介绍了怎么建立Delta并联机械手的正逆解以及如何通过视觉进行匹配定位。本节课程给大家分享如何通过C#语言开发正运动Delta并联机械手传送带同步的视觉分拣。 VPLC711硬件介绍 VPLC711是正运动推出的一款基于x86平台和Windows操作系统的高性能机器…...

01:Linux的基本命令
Linux的基本命令 1、常识1.1、Linux的隐藏文件1.2、绝对路径与相对路径 2、基本命令2.1、ls2.2、cd2.3、pwd / mkdir / mv / touch / cp / rm / cat / rmdir2.4、ln2.5、man2.6、apt-get 本教程是使用的是Ubuntu14.04版本。 1、常识 1.1、Linux的隐藏文件 在Linux中…...

GNSS 载波、测距码和导航电文的关系简介
1、GNSS 载波、测距码和导航电文 在卫星导航系统中,载波、测距码和导航电文是构成GPS信号的三个基本组成部分,它们共同工作以实现精确的卫星定位和导航功能。以下是对这三个组成部分的详细介绍: 1. 载波(Carrier)&…...

deepE 定位系统卡顿问题实战(一) ----------- 锁造成的阻塞问题
deepE介绍 deepE是一个开源的用于端侧(自动驾驶车,机器人)等环境的系统问题与性能分析工具。基于ebpf功能实现 deepE项目地址 欢迎star 测试程序 #include <iostream> #include <thread> #include <mutex>static std::mutex lock;void func1() {int l…...

YOLOv5改进 | 主干网络 | ODConv + ConvNeXt 增强目标特征提取能力
秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录: 《YOLOv5入门 …...

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍
TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍 添加 一个PLC,设置PLC的IP地址,如下图所示, 添加全局DB块,新建几个变量,如下图所示, 在数据块中添加了 tag1 …… tag6 ,共 6 个浮点数类型的变量,用来接收通过 WinCC 从 Excel 文件中读取的数据。 添加 HMI…...

第5篇 区块链的技术架构:节点、网络和数据结构
区块链技术听起来很高大上,但其实它的核心架构并不难理解。今天我们就用一些简单的例子和有趣的比喻,来聊聊区块链的技术架构:节点、网络和数据结构。 节点:区块链的“细胞” 想象一下,区块链就像是一个大型的组织&a…...

vue长列表,虚拟滚动
1.新建子组件,将数据传递过去(几万条数据的数组,一次性展示多少条,每条数据的行高). <template><div class"vitualScroll"><sub-scroll :dataList"dataList" :rowCount"20" :rowHeight"2…...

【实战场景】记一次UAT jvm故障排查经历
【实战场景】记一次UAT jvm故障排查经历 开篇词:干货篇:1.查看系统资源使用情况2.将十进制进程号转成十六进制3.使用jstack工具监视进程的垃圾回收情况4.输出指定线程的堆内存信息5.观察日志6.本地环境复现 总结篇:我是杰叔叔,一名…...

线性代数--行列式1
本篇来自对线性代数第一篇的行列式的一个总结。 主要是行列式中有些关键点和注意事项,便于之后的考研复习使用。 首先,对于普通的二阶和三阶行列式,我们可以直接对其进行拆开,展开。 而对于n阶行列式 其行列式的值等于它的任意…...

tensorflow神经网络
训练一个图像识别模型,使用TensorFlow,需要以下步骤。 1. 安装所需的库 首先,确保安装了TensorFlow和其他所需的库。 pip install tensorflow numpy matplotlib2. 数据准备 需要收集和准备训练数据。每个类别应有足够多的样本图像。假设有…...

Python基础001
Python输出语句 print输出字符串 print("中国四大名著:","西游记|","三国演义|","红楼梦|","水浒传") print(6) print(1 1)Python输入语句 input函数 input() input("我的名字是:") p…...

【udp报文】udp报文未自动分片,报文过长被拦截问题定位
问题现象 某局点出现一个奇怪的现象,客户端给服务端发送消息,服务端仅能收到小部分消息,大部分消息从客户端发出后,服务端都未收到。 问题定位 初步分析 根据现象初步分析,有可能是网络原因导致消息到服务端不可达&a…...

某网页gpt的JS逆向
原网页网址 (base64) 在线解码 aHR0cHM6Ly9jbGF1ZGUzLmZyZWUyZ3B0Lnh5ei8 逆向效果图 调用代码(复制即用) 把倒数第三行换成下面的base64解码 aHR0cHM6Ly9jbGF1ZGUzLmZyZWUyZ3B0Lnh5ei9hcGkvZ2VuZXJhdGU import hashlib import time import reques…...

【python脚本】批量检测sql延时注入
文章目录 前言批量检测sql延时注入工作原理脚本演示 前言 SQL延时注入是一种在Web应用程序中利用SQL注入漏洞的技术,当传统的基于错误信息或数据回显的注入方法不可行时,例如当Web应用进行了安全配置,不显示任何错误信息或敏感数据时&#x…...

在C++中如何理解const关键字的不同用法(如const变量、const成员函数、const对象等)
在C中,const关键字是一个非常重要的修饰符,它用于指明变量、函数参数、成员函数或对象的内容是不可变的。理解const的不同用法对于编写高质量、易维护的C代码至关重要。下面详细解释const在几种不同上下文中的用法和含义。 1. const变量 当变量被声明为…...

JavaSEJava8 时间日期API + 使用心得
文章目录 1. LocalDate2. LocalTime3. LocalDateTime3.1创建 LocalDateTime3.2 LocalDateTime获取方法 4. LocalDateTime转换方法4.1 LocalDateTime增加或者减少时间的方法4.2 LocalDateTime修改方法 5. Period6. Duration7. 格式转换7.1 时间日期转换为字符串7.2 字符串转换为…...

【亲测解决】Python时间问题
微信公众号:leetcode_algos_life,代码随想随记 小红书:412408155 CSDN:https://blog.csdn.net/woai8339?typeblog ,代码随想随记 GitHub: https://github.com/riverind 抖音【暂未开始,计划开始】…...

Linux屏幕驱动开发调试笔记
引言 首先了解下什么是MIPI-DSI: MIPI-DSI是一种应用于显示技术的串行接口,兼容DPI(显示像素接口,Display Pixel Interface)、DBI(显示总线接口,Display Bus Interface)和DCS(显示命令集,Display Command Set)&#…...

Nginx Http缓存的必要性!启发式缓存有什么弊端?
👀 Nginx Http缓存的必要性!启发式缓存有什么弊端? 简介启发式缓存引发的问题nginx缓存配置 简介 我们在使用React或者Vue开发项目中会使用hash、chunkhash、contenthash来给静态资源文件进行命名。这带来的好处便是当我们部署完项目后&…...

【RT摩拳擦掌】RT云端测试之百度天工物接入构建(设备型)
【RT摩拳擦掌】RT云端测试之百度天工物接入构建(设备型) 一, 文档介绍二, 物接入IOT Hub物影子构建2.1 创建设备型项目2.2 创建物模型2.3 创建物影子 三, MQTT fx客户端连接云端3.1 MQTT fx配置3.2 MQTT fx订阅3.3 MQT…...

Mysql和ES使用汇总
一、mysql和ES在业务上的配合使用 一般使用时使用ES 中存储全文检索的关键字与获取的商品详情的id,通过ES查询获取查询商品的列表中展示的数据,通过展示id 操作去获取展示商品的所有信息。mysql根据id去查询数据库数据是很快的; 为什么ES一般…...

Android中使用performClick触发点击事件
Android中使用performClick触发点击事件 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨在Android开发中如何使用 performClick() 方法来触发点击…...

重生之我要学后端01--后端语言选择和对应框架选择
编程语言 后端开发通常需要掌握至少一种编程语言。以下几种语言在后端开发中非常流行: Java:广泛用于企业级应用程序。Python:因其易学性和强大的库支持(如Django和Flask)而受欢迎。Node.js(JavaScript&a…...

C语言 | Leetcode C语言题解之第206题反转链表
题目: 题解: struct ListNode* reverseList(struct ListNode* head) {if (head NULL || head->next NULL) {return head;}struct ListNode* newHead reverseList(head->next);head->next->next head;head->next NULL;return newHea…...

Flink Window DEMO 学习
该文档演示了fink windows的操作DEMO 环境准备: kafka本地运行:kafka部署自动生成名字代码:随机名自动生成随机IP代码:随机IPFlink 1.18 测试数据 自动向kafka推送数据 import cn.hutool.core.date.DateUtil; import com.alibab…...

library source does not match the bytecode for class SpringApplication
library source does not match the bytecode for class SpringApplication 问题描述:springboot源码点进去然后download source后提示标题内容。spring版本5.2.8.RELEASE,springboot版本2.7.18 解决方法:把spring版本改为与boot版本对应的6.…...

Linux基础指令介绍与详解——原理学习
前言:本节内容标题虽然为指令,但是并不只是讲指令, 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法, 很抱歉, 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的,…...