ff_h2645_packet_split 代码注释
----------------------------------------
author: hjjdebug
date: 2023年 07月 20日 星期四 15:21:45 CST
----------------------------------------
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中存储原始数据流,长度为len, 分割,分析出各个包到NAL(网络抽象层),但这里的目标用pkt来管理.
就是说:
数据源, buf,length
数据汇, pkt
其它的参数是不重要的,也容易理解.
logctx: 不重要,只是打印log时可以把打印信息打印的更详细一点,说明从哪里打印的.
is_nalff: 不重要, h264都为0
nal_length_size:
codec_id: 不重要,不是264就是265,决定如何分析nal_header
small_padding:不重要, 一般为0.
use_ref: 不重要,一般为0.
1. 数据源结构
type = struct GetByteContext {
const uint8_t *buffer; //数据指针
const uint8_t *buffer_end; //缓冲尾部
const uint8_t *buffer_start;//缓冲开始
}
它的结构变量是bc, 可见bc 有一个start,一个end指针指示缓存,还有一个buffer指针指示当前位置.
2. 数据汇结构
所谓网络抽象单元,就是SPS,PPS,AUD,IDR,非IDR那些元素
type = struct H2645Packet {
H2645NAL *nals; // nals 数组. 就是由网络抽象单元组成的数组
H2645RBSP rbsp; // raw bit stream, 实际存数的地方,它也只是一个头部,实际存数是动态分配的内存
int nb_nals; // 多重意义
int nals_allocated; //分配了多少个nals
unsigned int nal_buffer_size; //分配的nals内存大小
}
它的结构变量是pkt,
pkt 中的概念是H264NALs, 这可以通过2个方面来理解.
第1是H264NALs 数组, 其中nals 指向一大块内存用来储藏NALs, nal_buffer_size, 记录的是内存的大小
nals_allocated 代表已经分配的NALS单元个数. 显然nal_buffer_size = nals_allocated * sizeof(H2645NAL)
nb_nals. 个数(起索引的功能,或其它功能),我们要操作第几个NAL
这里我们补充一下H2645NAL 结构 ,它的大小是112字节, 这样我们就对这个储存分析结果的结构有了一个清晰的认识,
是一个动态分配的NALs 数组.
type = struct H2645NAL {
uint8_t *rbsp_buffer; //指向rbsp的指针,实际存数的地方
int size; //byte 大小,rbsp大小
const uint8_t *data;
int size_bits; //bit 大小
int raw_size; //原始byte大小,含03byte, bsp大小
const uint8_t *raw_data;
GetBitContext gb; //运用这个对象,可以分析bit
int type; //nal 头类型
int temporal_id;
int nuh_layer_id;
int skipped_bytes; //跳过了多少字节
int skipped_bytes_pos_size; //为跳过字节分配了多少内存
int *skipped_bytes_pos; //记录在哪里遇到了跳过byte(03)
int ref_idc;
}
我们看到,每一个nals 是一个大小112字节的结构, 但它包含若干指针,填充这些指针及type,size等属性值就是我们分析的任务.
各个指针及属性在代码中还会有说明
第2: rbsp 是干什么的? 我们copy一下它的结构定义
type = struct H2645RBSP {
uint8_t *rbsp_buffer; //内存分配开始指针
AVBufferRef *rbsp_buffer_ref; //是否是可参考的
int rbsp_buffer_alloc_size; //内存分配的大小
int rbsp_buffer_size; //实际的数据大小
}
这个定义看起来似曾相识了,首先有一个缓存rbsp_buffer,其分配的大小rbsp_buffer_alloc_size, 目前使用的大小rbsp_buffer_size
还有一个变量rbsp_buffer_ref , 是有关内存参考的变量,多数据引用时才会用到,目前可以不用考虑
可见数据源进来,被分析成nals数组, 每个nals 的数据指针,指向的是 rbsp 数据中的某个位置, rbsp 才是真正存储分析数据的地方
有了以上认识,下面可以分析代码了.
能真正理解其中含义,需要反复调试跟踪代码!
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; //由于is_nalff 为假,所以next_avc=length
int64_t padding = small_padding ? 0 : MAX_MBPAIR_SIZE; //由于small_padding为假,所以padding 赋值256K
bytestream2_init(&bc, buf, length); //初始化bc , 这是源, GetByteContext 结构如上述.
alloc_rbsp_buffer(&pkt->rbsp, length + padding, use_ref); //为pkt->rbsp 分配内存,这是汇, pkt结构如上述. 分配的比原始数据还大padding的空间
if (!pkt->rbsp.rbsp_buffer)
return AVERROR(ENOMEM);
pkt->rbsp.rbsp_buffer_size = 0; //继续初始化pkt的rbsp.rbsp_buffer_size=0 及nb_nal=0
pkt->nb_nals = 0;
while (bytestream2_get_bytes_left(&bc) >= 4) { //剩余的源数据比4字节长,我们就分析
H2645NAL *nal;
int extract_length = 0;
int skip_trailing_zeros = 1;
if (bytestream2_tell(&bc) == next_avc) { //剩余数据足够,可是碰到了下一个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) //源指针>next_avc ? 是则打印警告信息
av_log(logctx, AV_LOG_WARNING, "Exceeded next NALFF position, re-syncing.\n");
/* search start code */ //查找下一个开始代码00 00 01, 返回到它的距离(字节数)
buf_index = find_next_start_code(bc.buffer, buf + next_avc);
bytestream2_skip(&bc, buf_index); //源指针向前移动buf_index字节数
if (!bytestream2_get_bytes_left(&bc)) { //源剩下的字节数为0? 返回或打印警告
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;
}
}
//准备增加一个NALS 单元,看看要不要分配内存
if (pkt->nals_allocated < pkt->nb_nals + 1) {
int new_size = pkt->nals_allocated + 1; // 在原来分配的基础上加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)); //把最新的项清0
nal = &pkt->nals[pkt->nb_nals]; //指向最新的项
nal->skipped_bytes_pos_size = FFMIN(1024, extract_length/3+1); // 计算一个数值,1024或更小
nal->skipped_bytes_pos = av_malloc_array(nal->skipped_bytes_pos_size, sizeof(*nal->skipped_bytes_pos));
if (!nal->skipped_bytes_pos) //分配一块内存用来保留NALS块中跳过的字节(当遇到00 00 03 时,要跳过03 字节)就是BSP->RBSP
return AVERROR(ENOMEM);
pkt->nals_allocated = new_size; //更新pkg->nals_allocated
}
nal = &pkt->nals[pkt->nb_nals]; //指向新分配的单元
//函数没有在继续展开,那样分析代码就太长了,需要了解每一个细节可以调试跟入, 但核心框架已在,可帮助理解.
//把源数据bc.buffer,extract_length(这个数可以很大)抽取到目标nal, 存数在pkt->rbsp, 返回消耗的字节数,consumed<=extract_length
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; //skip_trailing_zeros 的判别
nal->size_bits = get_bit_length(nal, skip_trailing_zeros); //获取nal单元的bit数, 前面抽取已经填充了bytes数及指针,这里计算bit数
if (nal->size <= 0 || nal->size_bits <= 0)
continue;
ret = init_get_bits(&nal->gb, nal->data, nal->size_bits); //初始化nal-gb GetBitContext, 这样就能都NALS单元实行bit操作
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);
else
ret = h264_parse_nal_header(nal, logctx); //分析nal单元的头步,可以填充nal type 信息
if (ret < 0) {
av_log(logctx, AV_LOG_WARNING, "Invalid NAL unit %d, skipping.\n",
nal->type);
continue;
}
pkt->nb_nals++; //把pkt->nb_nals 增加一个
}
return 0;
}
有感于它解析数据时采用了NAL数组,nal单元的数据指针指向rbsp,用一个pkt变量把它们管理起来,这样实现分割数据的过程.
(gdb) p h->pkt.nals[0]
$42 = {
rbsp_buffer = 0x7ffff56cc040 <incomplete sequence \360>,
size = 3, //数据大小(汇)为3,不包含头部00 00 01始, 例如此为AUD(access unit delimiter), 3数据为
(gdb) x/3bx pkt->nals[0].data
0x7ffff56cc040: 0x09 0xf0 0x00
data = 0x7ffff56cc040 <incomplete sequence \360>, //数据指针,与rbsp_buffer 数值想等
size_bits = 11, //bit数,去掉尾部0后的长度
raw_size = 3, //原始数据(源)大小为3
raw_data = 0x7ffff57e0004 <incomplete sequence \360>, //指向源数据指针
gb = { //GetBitContext 结构, 已经读走了8个bits, 因而知道nal的type 为9
buffer = 0x7ffff56cc040 <incomplete sequence \360>,
buffer_end = 0x7ffff56cc042 "",
index = 8,
size_in_bits = 11,
size_in_bits_plus8 = 19
},
type = 9,
temporal_id = 0,
nuh_layer_id = 0,
skipped_bytes = 0, //无skip byte
skipped_bytes_pos_size = 1024,
skipped_bytes_pos = 0x555555575a00,
ref_idc = 0
}
(gdb) p h->pkt.nals[1]
$43 = {
rbsp_buffer = 0x7ffff56cc043 "gd",
size = 27, //27个数据,其类型为7, PPS 数据, PPS数据分析在其它函数中进行!!
data = 0x7ffff56cc043 "gd", //指针与rbsp_buffer 相同
size_bits = 201, // bits 201(去掉bytes尾部0)
raw_size = 28, //raw_size比size大1,说明有一个skip byte
raw_data = 0x7ffff57e000a "gd",
gb = { //读了8bits, 得到 nal type=7 SPS(sequence parameter set)
buffer = 0x7ffff56cc043 "gd",
buffer_end = 0x7ffff56cc05d "",
index = 8,
size_in_bits = 201,
size_in_bits_plus8 = 209
},
type = 7,
temporal_id = 0,
nuh_layer_id = 0,
skipped_bytes = 1, //记录skip bytes1
skipped_bytes_pos_size = 1024,
skipped_bytes_pos = 0x555555576b80,
ref_idc = 3 //重要性3
}