音视频入门基础: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 抖音【暂未开始,计划开始】…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...