0
点赞
收藏
分享

微信扫一扫

h.264源码解析

#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;
}

举报

相关推荐

0 条评论