音视频入门基础: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 抖音【暂未开始,计划开始】…...
 
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
 
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
 
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
