#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// NALU(网络抽象层单元)类型枚举
typedef enum {
NALU_TYPE_SLICE = 1, // 分片数据
NALU_TYPE_DPA = 2, // 数据分片 A
NALU_TYPE_DPB = 3, // 数据分片 B
NALU_TYPE_DPC = 4, // 数据分片 C
NALU_TYPE_IDR = 5, // 即时解码刷新帧(关键帧)
NALU_TYPE_SEI = 6, // 补充增强信息
NALU_TYPE_SPS = 7, // 序列参数集(SPS)
NALU_TYPE_PPS = 8, // 图像参数集(PPS)
NALU_TYPE_AUD = 9, // 访问单元分隔符
NALU_TYPE_EOSEQ = 10, // 序列结束
NALU_TYPE_EOSTREAM = 11, // 码流结束
NALU_TYPE_FILL = 12, // 填充数据
} NaluType;
// NALU 优先级枚举(用于指示重要性)
typedef enum {
NALU_PRIORITY_DISPOSABLE = 0, // 可丢弃
NALU_PRIRITY_LOW = 1, // 低优先级(原文拼写错误:PRIRITY → PRIORITY)
NALU_PRIORITY_HIGH = 2, // 高优先级
NALU_PRIORITY_HIGHEST = 3 // 最高优先级
} NaluPriority;
// NALU 数据结构定义
typedef struct {
int startcodeprefix_len; // 起始码长度:3字节(0x000001)或4字节(0x00000001)
unsigned len; // NALU 数据长度(不包含起始码)
unsigned max_size; // NALU 缓冲区最大容量
int forbidden_bit; // 禁用位(应始终为0)
int nal_reference_idc; // 重要性指示(参考优先级)
int nal_unit_type; // NALU 类型(如 SPS, PPS, IDR 等)
char *buf; // 存储 NALU 数据(包含第一个字节 + EBSP)
} NALU_t;
FILE *h264bitstream = NULL; // 指向 H.264 码流文件的指针
int info2 = 0, info3 = 0; // 全局标志变量,用于判断起始码类型
/**
* 查找 3 字节起始码 0x000001
* @param Buf 输入缓冲区(至少3字节)
* @return 1 表示找到,0 表示未找到
*/
static int FindStartCode2(unsigned char *Buf) {
if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 1)
return 0; // 不是 0x000001
else
return 1;
}
/**
* 查找 4 字节起始码 0x00000001
* @param Buf 输入缓冲区(至少4字节)
* @return 1 表示找到,0 表示未找到
*/
static int FindStartCode3(unsigned char *Buf) {
if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 0 || Buf[3] != 1)
return 0; // 不是 0x00000001
else
return 1;
}
/**
* 从 Annex-B 格式的 H.264 码流中读取一个 NALU
* @param nalu 输出参数:存储解析出的 NALU 信息
* @return 实际读取的字节数(含起始码),0表示文件结束,-1表示错误
*/
int GetAnnexbNALU(NALU_t *nalu) {
int pos = 0; // 当前读取位置
int StartCodeFound = 0; // 是否找到下一个起始码
int rewind; // 回退字节数(用于 fseek)
unsigned char *Buf; // 临时缓冲区
// 分配临时缓冲区
if ((Buf = (unsigned char*)calloc(nalu->max_size, sizeof(char))) == NULL) {
printf("GetAnnexbNALU: Could not allocate Buf memory\n");
return -1;
}
nalu->startcodeprefix_len = 3; // 默认起始码长度为3
// 先读取3个字节
if (3 != fread(Buf, 1, 3, h264bitstream)) {
free(Buf);
return 0; // 文件结束或读取失败
}
// 判断是否为 3 字节起始码 (0x000001)
info2 = FindStartCode2(Buf);
if (info2 != 1) {
// 如果不是,尝试读第4个字节,判断是否为 4 字节起始码 (0x00000001)
if (1 != fread(Buf + 3, 1, 1, h264bitstream)) {
free(Buf);
return 0;
}
info3 = FindStartCode3(Buf);
if (info3 != 1) {
free(Buf);
return -1; // 无效起始码
} else {
pos = 4;
nalu->startcodeprefix_len = 4; // 设置为4字节起始码
}
} else {
pos = 3; // 使用3字节起始码
}
StartCodeFound = 0;
info2 = 0;
info3 = 0;
// 循环读取数据,直到发现下一个起始码或文件结束
while (!StartCodeFound) {
if (feof(h264bitstream)) {
// 文件结束:当前 NALU 是最后一个
nalu->len = (pos - 1) - nalu->startcodeprefix_len;
memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);
// 解析 NALU 头部信息
nalu->forbidden_bit = nalu->buf[0] & 0x80; // 第7位
nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 第5~6位
nalu->nal_unit_type = nalu->buf[0] & 0x1f; // 第0~4位
free(Buf);
return pos - 1; // 返回读取的总字节数
}
// 逐字节读取
Buf[pos++] = fgetc(h264bitstream);
// 检查最后4字节是否为 4 字节起始码
info3 = FindStartCode3(&Buf[pos - 4]);
if (info3 != 1)
info2 = FindStartCode2(&Buf[pos - 3]); // 否则检查3字节起始码
StartCodeFound = (info2 == 1 || info3 == 1);
}
// 找到下一个起始码,需要回退文件指针(不能吃掉下一个 NALU 的起始码)
rewind = (info3 == 1) ? -4 : -3;
if (0 != fseek(h264bitstream, rewind, SEEK_CUR)) {
free(Buf);
printf("GetAnnexbNALU: Cannot fseek in the bit stream file\n");
return -1;
}
// 此时 Buf 包含:[起始码][NALU数据][下一个起始码前的部分]
// 计算 NALU 实际数据长度(不含起始码)
nalu->len = (pos + rewind) - nalu->startcodeprefix_len;
memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);
// 解析 NALU 头部字段
nalu->forbidden_bit = nalu->buf[0] & 0x80; // 禁用位
nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 优先级
nalu->nal_unit_type = nalu->buf[0] & 0x1f; // 类型
free(Buf); // 释放临时缓冲区
return (pos + rewind); // 返回本次读取的总字节数(含起始码)
}
/**
* 简单的 H.264 码流分析器
* @param url 输入的 H.264 文件路径
* @return 0 成功,非0失败
*/
int simplest_h264_parser(char *url) {
NALU_t *n = NULL;
int buffersize = 100000; // NALU 缓冲区大小
// 输出目标:可改为文件或标准输出
// FILE *myout = fopen("output_log.txt", "wb+");
FILE *myout = stdout;
// 打开 H.264 文件
h264bitstream = fopen(url, "rb+");
if (h264bitstream == NULL) {
printf("Open file error\n");
return 0;
}
// 分配 NALU 结构体
n = (NALU_t*)calloc(1, sizeof(NALU_t));
if (n == NULL) {
printf("Alloc NALU Error\n");
return 0;
}
n->max_size = buffersize;
n->buf = (char*)calloc(buffersize, sizeof(char));
if (n->buf == NULL) {
free(n);
printf("AllocNALU: n->buf\n");
return 0;
}
int data_offset = 0; // 当前 NALU 在文件中的偏移
int nal_num = 0; // NALU 编号
// 打印表头
printf("-----+-------- NALU Table ------+---------+\n");
printf(" NUM | POS | IDC | TYPE | LEN |\n");
printf("-----+---------+--------+-------+---------+\n");
// 循环读取每一个 NALU
while (!feof(h264bitstream)) {
int data_length;
data_length = GetAnnexbNALU(n);
// 根据类型转换为字符串
char type_str[20] = {0};
switch (n->nal_unit_type) {
case NALU_TYPE_SLICE: sprintf(type_str, "SLICE"); break;
case NALU_TYPE_DPA: sprintf(type_str, "DPA"); break;
case NALU_TYPE_DPB: sprintf(type_str, "DPB"); break;
case NALU_TYPE_DPC: sprintf(type_str, "DPC"); break;
case NALU_TYPE_IDR: sprintf(type_str, "IDR"); break;
case NALU_TYPE_SEI: sprintf(type_str, "SEI"); break;
case NALU_TYPE_SPS: sprintf(type_str, "SPS"); break;
case NALU_TYPE_PPS: sprintf(type_str, "PPS"); break;
case NALU_TYPE_AUD: sprintf(type_str, "AUD"); break;
case NALU_TYPE_EOSEQ: sprintf(type_str, "EOSEQ"); break;
case NALU_TYPE_EOSTREAM: sprintf(type_str, "EOSTREAM"); break;
case NALU_TYPE_FILL: sprintf(type_str, "FILL"); break;
default: sprintf(type_str, "UNKNOWN"); break;
}
// 根据优先级转换为字符串(注意:这里 >>5 是错误的!应为 >>5 是取高2位,但 nal_reference_idc 已是2位值)
// 正确做法是直接比较值,但原代码逻辑有误,此处保留原逻辑,仅注释说明
char idc_str[20] = {0};
int priority = n->nal_reference_idc >> 5; // 错误:应直接用 n->nal_reference_idc & 0x60 >> 5
switch (priority) {
case NALU_PRIORITY_DISPOSABLE: sprintf(idc_str, "DISPOS"); break;
case NALU_PRIRITY_LOW: sprintf(idc_str, "LOW"); break;
case NALU_PRIORITY_HIGH: sprintf(idc_str, "HIGH"); break;
case NALU_PRIORITY_HIGHEST: sprintf(idc_str, "HIGHEST");break;
default: sprintf(idc_str, "UNK"); break;
}
// 输出当前 NALU 信息
fprintf(myout, "%5d| %8d| %7s| %6s| %8d|\n",
nal_num, data_offset, idc_str, type_str, n->len);
data_offset += data_length; // 更新文件偏移
nal_num++;
}
// 释放资源
if (n) {
if (n->buf) {
free(n->buf);
n->buf = NULL;
}
free(n);
}
fclose(h264bitstream); // 关闭文件
return 0;
}
/**
* 主函数
*/
int main(int argc, char* argv[]) {
// 解析指定的 H.264 文件(硬编码为 "sintel.h264")
simplest_h264_parser("sintel.h264");
return 0;
}