最简单的 H.264 视频码流解析程序

news2024/10/10 12:23:26

最简单的 H.264 视频码流解析程序

  • 最简单的 H.264 视频码流解析程序
    • 原理
    • 源程序
    • 运行结果
    • 下载链接
    • 参考

最简单的 H.264 视频码流解析程序

参考雷霄骅博士的文章:视音频数据处理入门:H.264视频码流解析

本文中的程序是一个H.264码流解析程序。该程序可以从H.264码流中分析得到它的基本单元NALU,并且可以简单解析NALU首部的字段。通过修改该程序可以实现不同的H.264码流处理功能。

原理

在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。

H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示。

在这里插入图片描述

其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。

NALU = NALU header(一组对应于视频编码的NALU头部信息,占1 bit) + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。

NAL Header 的组成为:forbidden_zero_bit(1 bit) + nal_ref_idc(2 bit) + nal_unit_type(5 bit)。

  • forbidden_zero_bit:禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
  • nal_ref_idc:重要性指示,标志该NAL单元的重要性,值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。
  • nal_unit_type:NALU类型取值。
    句法表中的 C 字段表示该句法元素的分类,这是为片区服务。

在这里插入图片描述

RBSP = 原始数据比特流(String Of Data Bits,SODB)+ RBSP trailing bits(填充位)

SODB 的长度不一定是8的倍数,故需要补齐。

拓展概念:扩展字节序列载荷。
在RBSP基础上填加了仿校验字节(0X03)。
它的原因是:在NALU加到Annexb上时,需要填加每组NALU之前的开始码StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示ox000001.为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。也称为脱壳操作。

H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。本文的程序即实现了上述的两个步骤。

源程序

整个程序位于simplest_h264_parser()函数中,如下所示。

/**
* 最简单的 H.264 视频码流解析程序
* Simplest H.264 Parser
*
* 原程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本项目是一个 H.264 码流分析程序,可以分离并解析 NALU。
*
* This project is a H.264 stream analysis program.
* It can parse H.264 bitstream and analysis NALU of stream.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 解决报错:fopen() 函数不安全
#pragma warning(disable:4996)

// NALU 类型取值,对应 NAL Header 的 nal_unit_type,占 5 bit
typedef enum {
	NALU_TYPE_SLICE = 1, // 非 IDR 帧中不采用数据划分的片
	NALU_TYPE_DPA = 2, // 非 IDR 帧中 A 类数据划分片
	NALU_TYPE_DPB = 3, // 非 IDR 帧中 B 类数据划分片
	NALU_TYPE_DPC = 4, // 非 IDR 帧中 C 类数据划分片
	NALU_TYPE_IDR = 5, // IDR(Instantaneous Decoding Refresh,即时解码刷新) 帧,它是一个 GOP 的开头,作用是立刻刷新,使错误不致传播
	NALU_TYPE_SEI = 6, // Supplemental enhancement information:附加增强信息,包含了视频画面定时等信息,一般放在主编码图像数据之前,在某些应用中,它可以被省略
	NALU_TYPE_SPS = 7, // Sequence Parameter Sets,序列参数集,保存了⼀组编码视频序列的全局参数
	NALU_TYPE_PPS = 8, // Picture Parameter Sets,图像参数集,对应的是⼀个序列中某⼀幅图像或者某⼏幅图像的参数
	NALU_TYPE_AUD = 9, // 分界符
	NALU_TYPE_EOSEQ = 10, // 序列结束
	NALU_TYPE_EOSTREAM = 11, // 码流结束
	NALU_TYPE_FILL = 12, // 填充
} NaluType;

// NALU 优先级,对应 NAL Header 的 nal_ref_idc,占 2 bit。取值越⼤,表示当前 NAL 越重要,需要优先受到保护
typedef enum {
	NALU_PRIORITY_DISPOSABLE = 0,
	NALU_PRIORITY_LOW = 1,
	NALU_PRIORITY_HIGH = 2,
	NALU_PRIORITY_HIGHEST = 3
} NaluPriority;

// NALU 单元
typedef struct
{
	// NAL Header,1 Byte
	int forbidden_bit; // 禁止位,1 bit
	int nal_reference_idc; // 优先级,2 bit
	int nal_unit_type; // 类型,5 bit
	// 原始字节序列负荷(RBSP,Raw Byte Sequence Payload)
	int startcodeprefix_len; // StartCode 的长度,4 for parameter sets and first slice in picture, 3 for everything else (suggested)
	unsigned len; // Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
	unsigned max_size; // Nal Unit Buffer size

	char *buf; // contains the first byte followed by the EBSP
} NALU_t;

FILE *h264bitstream = NULL; // the bit stream file

int info2 = 0, info3 = 0;

// 找到 3 字节的 StartCode
static int FindStartCode2(unsigned char *Buf)
{
	if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 1)
		return 0; // 0x000001
	else
		return 1;
}

// 找到 4 字节的 StartCode
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;
}

int GetAnnexbNALU(NALU_t *nalu)
{
	int pos = 0;
	int StartCodeFound, rewind;
	unsigned char *Buf;

	// 给 Buf 分配 nalu->max_size 的空间
	if ((Buf = (unsigned char*)calloc(nalu->max_size, sizeof(char))) == NULL)
		printf("GetAnnexbNALU: Could not allocate Buf memory.\n");

	nalu->startcodeprefix_len = 3;

	// 若输入的 H.264 流文件没有 3 bits,返回
	if (3 != fread(Buf, 1, 3, h264bitstream))
	{
		free(Buf);
		return 0;
	}
	info2 = FindStartCode2(Buf);
	if (info2 != 1) // StartCode 的长度不是 3 字节
	{
		if (1 != fread(Buf + 3, 1, 1, h264bitstream))
		{
			free(Buf);
			return 0;
		}
		info3 = FindStartCode3(Buf);
		if (info3 != 1) // StartCode 的长度不是 3 或者 4 字节,错误
		{
			free(Buf);
			return -1;
		}
		else // StartCode 的长度为 4 字节
		{
			pos = 4;
			nalu->startcodeprefix_len = 4;
		}
	}
	else // StartCode 的长度为 3 字节
	{
		nalu->startcodeprefix_len = 3;
		pos = 3;
	}

	StartCodeFound = 0;
	info2 = 0;
	info3 = 0;

	while (!StartCodeFound)
	{
		if (feof(h264bitstream))
		{
			nalu->len = (pos - 1) - nalu->startcodeprefix_len;
			memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);
			nalu->forbidden_bit = nalu->buf[0] & 0x80; // 1 bit
			nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit
			nalu->nal_unit_type = (nalu->buf[0]) & 0x1f; // 5 bit
			free(Buf);
			return pos - 1;
		}
		Buf[pos++] = fgetc(h264bitstream);
		// 读 Buf 的最后 4 字节,看看有没有 StartCode
		info3 = FindStartCode3(&Buf[pos - 4]);
		if (info3 != 1)
		{
			// 再读 Buf 的最后 3 字节,看看有没有 StartCode
			info2 = FindStartCode2(&Buf[pos - 3]);
		}
		StartCodeFound = (info2 == 1 || info3 == 1);
	}

	// Here, we have found another start code, and read length of startcode bytes more than we should have.
	// Hence, go back in the file.
	rewind = (info3 == 1) ? -4 : -3; // 回退的字节数

	if (0 != fseek(h264bitstream, rewind, SEEK_CUR))
	{
		free(Buf);
		printf("GetAnnexbNALU: Can not fseek in the bit stream file.\n");
	}

	// Here the Start code, the complete NALU, and the next start code is in the Buf.  
	// The size of Buf is pos, pos+rewind are the number of bytes excluding the next start code,
	// and (pos+rewind)-startcodeprefix_len is the size of the NALU excluding the start code.

	nalu->len = (pos + rewind) - nalu->startcodeprefix_len;
	// nalu->buf 只存储 NALU 单元,不含 StartCode
	memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);
	// NAL Header
	nalu->forbidden_bit = nalu->buf[0] & 0x80; // 1 bit
	nalu->nal_reference_idc = nalu->buf[0] & 0x60; // 2 bit
	nalu->nal_unit_type = (nalu->buf[0]) & 0x1f; // 5 bit

	free(Buf);

	return (pos + rewind); // 返回 StartCode + 一个 NALU 的长度
}

/**
* Analysis H.264 Bitstream
* @param url Location of input H.264 bitstream file.
*/
int simplest_h264_parser(const char *url)
{

	NALU_t *n;
	int buffersize = 100000;

	FILE *myout = stdout;
	// FILE *myout = fopen("output_log.txt", "wb+");

	h264bitstream = fopen(url, "rb+");
	if (h264bitstream == NULL)
	{
		printf("Open file error.\n");
		return 0;
	}

	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("Alloc NALU: n->buf error.\n");
		return 0;
	}

	int data_offset = 0;
	int nal_num = 0;
	printf("-----+---------+------ NALU Table ---+--------+---------+\n");
	printf(" NUM |   POS   | FORBIDDEN |   IDC   |  TYPE  |   LEN   |\n");
	printf("-----+---------+-----------+---------+--------+---------+\n");

	while (!feof(h264bitstream))
	{
		int nalu_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;
		}
		char idc_str[20] = { 0 };
		switch (n->nal_reference_idc >> 5)
		{
		case NALU_PRIORITY_DISPOSABLE: sprintf(idc_str, "DISPOS"); break;
		case NALU_PRIORITY_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(type_str, "unknown"); break;
		}

		fprintf(myout, "%5d| %8d|%11d|%9s|%8s|%9d|\n", nal_num, data_offset, n->forbidden_bit, idc_str, type_str, n->len);

		data_offset = data_offset + nalu_length;

		nal_num++;
	}

	// Free
	if (n)
	{
		if (n->buf)
		{
			free(n->buf);
			n->buf = NULL;
		}
		free(n);
	}
	return 0;
}

int main()
{
	simplest_h264_parser("sintel.h264");

	system("pause");
	return 0;
}

运行结果

本程序的输入为一个H.264原始码流(裸流)的文件路径,输出为该码流的NALU统计数据,如下图所示。

在这里插入图片描述

下载链接

GitHub:UestcXiye / Simplest-H.264-Parser

CSDN:Simplest H.264 Parser.zip

参考

  1. https://blog.csdn.net/leixiaohua1020/article/details/50534369
  2. https://blog.csdn.net/u010512264/article/details/82083467
  3. https://blog.csdn.net/stpeace/article/details/8221945
  4. https://blog.csdn.net/threewells_14/article/details/1508657
  5. https://blog.csdn.net/qq_29350001/article/details/78226286
  6. https://blog.csdn.net/jammg/article/details/52357245

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1568036.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

​IAA+IAP混合变现趋势下,出海手游广告玩法解析 | TopOn出海干货

3月23日&#xff0c;TopOn 携手罗斯基及汇量科技旗下一站式跨渠道智能投放工具XMP联合主办的“2023游戏出海新机会”上海站线下沙龙成功举办。 本次活动邀请到多位业内知名公司、平台的负责人&#xff0c;分别从海外投放增长策略、产品融合玩法方向、商业混合变现模式、三方安…

C++万物起源:类与对象(三)拷贝构造、赋值重载

目录 一、拷贝构造函数 1.1拷贝构造函数的概念与特征 1.2拷贝构造的实现 1.3默认构造函数 1.4拷贝构造函数典型调用场景 二、赋值运算符重载 2.1赋值运算符重载的格式 一、拷贝构造函数 1.1拷贝构造函数的概念与特征 在c语言语法中&#xff0c;我们可以将一个变量赋值给…

OSPF中配置静态路由备份 实验简述

OSPF中配置静态路由备份 实验简述 静态路由备份是一种网络路由的备份机制&#xff0c;用于提高网络的可靠性和冗余性。而静态路由备份则是在主路由失效时&#xff0c;自动切换到备用路由&#xff0c;以确保网络的连通性。 主路由默认优先级为60&#xff0c;备份路由设置优先级1…

Doris实践——信贷系统日志分析场景的实践应用

目录 前言 一、早期架构演进 1.1 架构1.0 基于Kettle MySQL离线数仓 1.2 架构2.0 基于 Presto / Trino统一查询 二、基于Doris的新一代架构 三、新数仓架构搭建经验 3.1 并发查询加速 3.2 数仓底座建设 四、Doris助力信DolphinScheduler 和 Shell 贷业务场景落地 4.…

前端学习<四>JavaScript基础——03-常量和变量

常量&#xff08;字面量&#xff09;&#xff1a;数字和字符串 常量也称之为“字面量”&#xff0c;是固定值&#xff0c;不可改变。看见什么&#xff0c;它就是什么。 常量有下面这几种&#xff1a; 数字常量&#xff08;数值常量&#xff09; 字符串常量 布尔常量 自定义…

C#编写MQTT客户端软件

主要参考C#MQTT编程06--MQTT服务器和客户端(winform版)_c#mqttserver-CSDN博客 但由于使用的.NET版本和MQTT库版本存在差异&#xff0c;因此有些不同。 MQTT协议内容在此不做描述&#xff0c;仅介绍VS使用C#的实现过程。本次使用VS2015&#xff0c;.netframwork4.6。 C#语言本身…

从零到一:基于 K3s 快速搭建本地化 kubeflow AI 机器学习平台

背景 Kubeflow 是一种开源的 Kubernetes 原生框架&#xff0c;可用于开发、管理和运行机器学习工作负载&#xff0c;支持诸如 PyTorch、TensorFlow 等众多优秀的机器学习框架&#xff0c;本文介绍如何在 Mac 上搭建本地化的 kubeflow 机器学习平台。 注意&#xff1a;本文以 …

STM32单片机智能电表交流电压电流程序设计(电流 电压互感器TV1005M+TA1005M)

资料下载地址&#xff1a;STM32单片机智能电表交流电压电流程序设计(电流 电压互感器TV1005MTA1005M) 1、摘要 5、基于STM32F103单片机智能电表交流电压电流设计 本设计由STM32单片机核心板电路交流电压电流检测模块电路WIFI模块电路指示灯电路组成。 1、通过电压互感器TV100…

【Canavs与艺术】绘制蓝白绶带大卫之星勋章

【图例】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>用Canvas绘制蓝白绶带大卫之星勋章</title><style type&quo…

计算机网络—TCP协议详解:特性、应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;マリンブルーの庭園—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 3:34 &#x1f504; ◀️…

我愿把这个网站成为全球最强AI网站!弄100多个AI伺候你??

家人们&#xff0c;你们猜我发现了什么牛逼的AI网站&#xff1f;&#xff1f; 直接上图&#xff1a; 这个网站&#xff0c;聚合了国内外100多个顶尖的AI&#xff0c;包括了OpenAI家的GPT3.5、GPT4、GPT4V、GPT4.5系列&#xff0c;以及Anthropic家的Claude3 Opus、Claude3 Sone…

Spark-Scala语言实战(11)

在之前的文章中&#xff0c;我们学习了如何在spark中使用RDD中的cartesian,subtract最终两种方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scal…

如何系统地自学Python

1、如何系统地自学Python 小白的话可以快速过一下某马&#xff0c;某谷。 主要关注Python有什么集合&#xff0c;里面的集合怎么使用 然后再找一个Python爬虫实战视频&#xff0c;先跟着视频敲一遍代码&#xff0c;然后再尝试自己做一遍 然后再找一个Python服务开发视频&am…

redis乱码\xac\xed\x00\x05t\x00H解决

发现数据库乱码&#xff1a; 这数据库是来自rdids队列list实现的一个简单队列&#xff0c;停止使用该list的服务&#xff0c;查看里面的值&#xff0c;发现 乱码\xac\xed\x00\x05t\x00H&#xff0c;如下图&#xff1a; 很明发送数据端的问题&#xff0c;检查代码&#xff1a; …

如何保持数据一致性

如何保持数据一致性 数据库和缓存&#xff08;比如&#xff1a;redis&#xff09;双写数据一致性问题&#xff0c;是一个跟开发语言无关的公共问题。尤其在高并发的场景下&#xff0c;这个问题变得更加严重。 问题描述&#xff1a; 1.在高并发的场景中&#xff0c;针对同一个…

【python】python新闻内容zhua取分析词云可视化(源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

Redis的基础操作

目录 一、Redis命令工具 1.redis-cli 命令行工具 2.redis-benchmark测试工具 3.Redis数据库五大类型 1、String 2、List 3、Hash&#xff08;散列类型&#xff09; 4、set无序集合 5、sorted set 二、Redis数据库常用命令 1、set与get的使用 2.查看数据库中键的情况…

深度学习:神经网络模型的剪枝和压缩简述

深度学习的神经网路的剪枝和压缩&#xff0c;大致的简述&#xff0c; 主要采用&#xff1a; network slimming&#xff0c;瘦身网络... 深度学习网络&#xff0c;压缩的主要方式&#xff1a; 1.剪枝&#xff0c;nerwork pruing&#xff0c; 2.稀疏表示&#xff0c;sparse rep…

基于向量数据库搭建自己的搜索引擎

前言【基于chatbot】 厌倦了商业搜索引擎搜索引擎没完没了的广告&#xff0c;很多时候&#xff0c;只是需要精准高效地检索信息&#xff0c;而不是和商业广告“斗智斗勇”。以前主要是借助爬虫工具&#xff0c;而随着技术的进步&#xff0c;现在有了更多更方便的解决方案&…

2024-HW --->SSRF

这不是马上准备就要护网了嘛&#xff0c;如火如荼的报名ing&#xff01;&#xff01;&#xff01;那么小编就来查缺补漏一下以前的web漏洞&#xff0c;也顺便去收录一波poc&#xff01;&#xff01;&#xff01;&#xff01; 今天讲的主人公呢就是SSRF&#xff0c;以前学的时候…