C/C++ H264文件解析
C++实现H264文件以及一段H264码流解析,源码如下:
h264Parse.h:
#ifndef _H264PARSE_H_
#define _H264PARSE_H_#include <fstream>class H264Parse
{
public:int open_file(const std::string &filename);/*** @brief 从文件中读取一个nalu,包含起始码* @param buf 存放nalu的缓冲区* @param size 缓冲区大小* @param len nalu的长度* @param n 每次读取多少个字节* @return -1 失败 0 已到文件末尾 1 成功获取到一个nalu*/int read_nalu(uint8_t *buf, uint32_t size, uint32_t &len, uint32_t n);void close_file();// 获取起始码长度static int get_startCode_len(const uint8_t *ptr);static const uint8_t *find_startCode_pos(const uint8_t *pbuf, uint32_t len);/*** @brief 从一段h264码流中分割nalu,包含起始码* @param stream h264码流* @param streamLen 码流大小* @param nalu Pointer to the extracted nalu* @param naluLen nalu的长度* @param record Pointer用于记录状态,第一次分割时把 *record 赋值为NULL* @return -1 失败 0 已分割完 1 成功获取到一个nalu*/static int nalu_tok(const uint8_t *stream, uint32_t streamLen, const uint8_t **nalu,uint32_t &naluLen, const uint8_t **record);private:std::fstream h264File;int read_start_code(uint8_t *buf);int adjust_filePointer_pos(uint32_t totalRead, uint32_t naluLen);
};#endif // _H264PARSE_H_
h264Parse.cpp:
#include "h264Parse.h"
#include <iostream>
#include <cstring>int H264Parse::open_file(const std::string &filename)
{h264File.open(filename, std::ios::in | std::ios::binary);if (!h264File.is_open()){std::cout << "Failed to open the H.264 file." << std::endl;return -1;}return 0;
}int H264Parse::get_startCode_len(const uint8_t *ptr)
{if (ptr[0] == 0x00 && ptr[1] == 0x00){if (ptr[2] == 0x01)return 3;else if (ptr[2] == 0x00 && ptr[3] == 0x01)return 4;}return -1; // 无效的起始码
}// 读取起始码,并返回其长度
int H264Parse::read_start_code(uint8_t *buf)
{// 读取前4个字节来判断起始码长度h264File.read(reinterpret_cast<char *>(buf), 4);if (h264File.gcount() < 4){return -1;}return get_startCode_len(buf);
}// 寻找NALU的起始码位置
const uint8_t *H264Parse::find_startCode_pos(const uint8_t *pbuf, uint32_t len)
{const uint8_t *p = pbuf;if (len < 3)return NULL;for (uint32_t i = 0; i < len - 3; ++i){if ((p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01) ||(p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x00 && p[3] == 0x01)){return p;}p++;}// 检查最后3字节是不是起始码if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01)return p;return NULL;
}// 调整文件指针位置
int H264Parse::adjust_filePointer_pos(uint32_t totalRead, uint32_t naluLen)
{int offset = -(totalRead - naluLen);if (!h264File.eof()){h264File.seekg(offset, std::ios::cur);}else{h264File.clear(); // 达到文件末尾了要先清除 eof 标志h264File.seekg(offset, std::ios::end);}if (h264File.fail()){std::cout << "seekg failed!" << std::endl;return -1;}return 0;
}int H264Parse::read_nalu(uint8_t *buf, uint32_t size, uint32_t &len, uint32_t n)
{uint32_t totalRead = 0;int startCodeLength = read_start_code(buf);if (startCodeLength == -1){printf("read_start_code failed.\n");return -1;}totalRead += 4; // 已经读取了4字节的长度while (true){if (size < totalRead + n){std::cout << "Buffer size is too small: size=" << size<< ", needed=" << totalRead + n << std::endl;return -1;}h264File.read(reinterpret_cast<char *>(buf + totalRead), n);std::streamsize bytesRead = h264File.gcount();if (bytesRead <= 0){std::cout << "Failed to read from file!" << std::endl;return -1;}uint32_t searchStart = (totalRead > 4) ? totalRead - 3 : startCodeLength;const uint8_t *naluEnd = find_startCode_pos(buf + searchStart,bytesRead + (totalRead > 4 ? 3 : 0));totalRead += bytesRead;if (naluEnd != nullptr){len = naluEnd - buf;if (adjust_filePointer_pos(totalRead, len) < 0)return -1;break;}// 是否读取到文件末尾if (h264File.peek() == std::char_traits<char>::eof()){len = totalRead;return 0; // NALU完整读取}}memset(buf + len, 0, size - len); // 清空剩余部分return 1; // 成功读取
}void H264Parse::close_file()
{h264File.close();
}int H264Parse::nalu_tok(const uint8_t *stream, uint32_t streamLen, const uint8_t **nalu,uint32_t &naluLen, const uint8_t **record)
{const uint8_t *current = (record && *record) ? *record : stream;uint32_t offset = static_cast<uint32_t>(current - stream);if (offset >= streamLen){return -1; // 当前记录位置超出缓冲区}int scLen = get_startCode_len(current);if (scLen == -1 || (current + scLen) > (stream + streamLen)){return -1; // 无效的起始码或起始码长度超出缓冲区}// 查找下一个起始码的位置const uint8_t *next_start = find_startCode_pos(current + scLen, streamLen - offset - scLen);if (next_start){*nalu = current;naluLen = static_cast<uint32_t>(next_start - current);*record = next_start;return 1; // 成功获取到一个 NALU}else{// 最后一个 NALU*nalu = current;naluLen = streamLen - offset;*record = NULL; // 重置记录指针return 0; // 分割完毕}
}
测试:
#include <iostream>
#include <vector>
#include "h264Parse.h"void test1()
{int ret;int number = 0;H264Parse h264;uint8_t buf[1024 * 1024];uint32_t len = 0;h264.open_file("/home/tl/work/app/res/output.h264");while ((ret = h264.read_nalu(buf, sizeof(buf), len, 1024 * 2)) != -1){printf("number: %d nalu len: %u\n", number, len - h264.get_startCode_len(buf));number++;if (ret == 0)break;}if (ret == -1){std::cout << "read_nalu failed." << std::endl;}h264.close_file();
}// 辅助函数:打印 NALU 信息
void print_nalu(const uint8_t *nalu, uint32_t len, int index)
{std::cout << "NALU " << index << ": Length = " << len << " bytes, Data = ";for (uint32_t i = 0; i < len; ++i){printf("%02X ", nalu[i]);}std::cout << std::endl;
}void test2()
{// 构造一个模拟的 H.264 码流缓冲区,包含多个 NALU// 起始码格式:0x000001 (3 字节) 和 0x00000001 (4 字节)// NALU 内容:随机填充的字节数据std::vector<uint8_t> buffer;// NALU 1: 3 字节起始码 + 5 字节数据std::vector<uint8_t> nalu1 = {0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x21, 0xA0};buffer.insert(buffer.end(), nalu1.begin(), nalu1.end());// NALU 2: 4 字节起始码 + 6 字节数据std::vector<uint8_t> nalu2 = {0x00, 0x00, 0x00, 0x01, 0x41, 0x9A, 0x5C, 0xD4, 0x00, 0x11};buffer.insert(buffer.end(), nalu2.begin(), nalu2.end());// NALU 3: 3 字节起始码 + 4 字节数据std::vector<uint8_t> nalu3 = {0x00, 0x00, 0x01, 0x06, 0x05, 0xFF, 0xEE};buffer.insert(buffer.end(), nalu3.begin(), nalu3.end());// NALU 4: 3 字节起始码 + 3 字节数据 (测试末尾)std::vector<uint8_t> nalu4 = {0x00, 0x00, 0x01, 0x07, 0xAD, 0xBE};buffer.insert(buffer.end(), nalu4.begin(), nalu4.end());// 输出构建的缓冲区(可选)std::cout << "Constructed H.264 Buffer: ";for (size_t i = 0; i < buffer.size(); ++i){printf("%02X ", buffer[i]);}std::cout << "\n\n";const uint8_t *pnalu = nullptr;uint32_t nale_len = 0;const uint8_t *pRecord = NULL; // 初始时为 NULLint ret;int nalu_index = 1;// 循环分割并打印每个 NALUwhile ((ret = H264Parse::nalu_tok(buffer.data(), buffer.size(), &pnalu, nale_len, &pRecord)) != -1){print_nalu(pnalu, nale_len, nalu_index);nalu_index++;if (ret == 0)break;}if (ret == -1){std::cout << "Error occurred during NALU tokenization." << std::endl;}
}// 主函数
int main()
{test1();// test2();return 0;
}
相关文章:
C/C++ H264文件解析
C实现H264文件以及一段H264码流解析,源码如下: h264Parse.h: #ifndef _H264PARSE_H_ #define _H264PARSE_H_#include <fstream>class H264Parse { public:int open_file(const std::string &filename);/*** brief 从文件中读取一个nalu&…...
【Windows】电脑端口明明没有进程占用但显示端口被占用(动态端口)
TOC 一、问题 重启电脑后,启用某个服务显示1089端口被占用。 查看是哪个进程占用了: netstat -aon | findstr "1089"没有输出,但是换其他端口,是可以看到相关进程的: 现在最简单的方式是给我的服务指定另…...
Redis 持久化 问题
前言 相关系列 《Redis & 目录》(持续更新)《Redis & 持久化 & 源码》(学习过程/多有漏误/仅作参考/不再更新)《Redis & 持久化 & 总结》(学习总结/最新最准/持续更新)《Redis & …...
vivado 配置
配置 配置指的是将特定应用数据加载到 FPGA 器件的内部存储器的进程。 赛灵思 FPGA 配置数据储存在 CMOS 配置锁存 (CCL) 中,因此配置数据很不稳定,且在每次 FPGA 器件上电后都必须重 新加载。 赛灵思 FPGA 器件可通过配置引脚,自行…...
Java如何实现PDF转高质量图片
大家好,我是 V 哥。在Java中,将PDF文件转换为高质量的图片可以使用不同的库,其中最常用的库之一是 Apache PDFBox。通过该库,你可以读取PDF文件,并将每一页转换为图像文件。为了提高图像的质量,你可以指定分…...
itemStyle.normal.label is deprecated, use label instead.
itemStyle.normal.label is deprecated, use label instead. normal’hierarchy in label has been removed since 4.0. All style properties are configured in label directly now. 错误写法: itemStyle: {normal: {// color: #00E0FF, // 设置折线点颜色 labe…...
如何在 Linux VPS 上保护 MySQL 和 MariaDB 数据库
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 有许多在 Linux 和类 Unix 系统上可用的 SQL 数据库语言实现。MySQL 和 MariaDB 是在服务器环境中部署关系型数据库的两个流行选项…...
CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度
文章目录 box-sizing: border-box; 的含义默认盒模型 (content-box)border-box 盒模型 在微信小程序中的应用示例 在微信小程序中,CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度。具体来说, box-sizing: border-box; 会改…...
预训练 BERT 使用 Hugging Face 和 PyTorch 在 AMD GPU 上
Pre-training BERT using Hugging Face & PyTorch on an AMD GPU — ROCm Blogs 2024年1月26日,作者:Vara Lakshmi Bayanagari. 这篇博客解释了如何从头开始使用 Hugging Face 库和 PyTorch 后端在 AMD GPU 上为英文语料(WikiText-103-raw-v1)预训练…...
鸿蒙是必经之路
少了大嘴的发布会,老实讲有点让人昏昏入睡。关于技术本身的东西,放在后面。 我想想来加把油~ 鸿蒙发布后褒贬不一,其中很多人不太看好鸿蒙,一方面是开源性、一方面是南向北向的利益问题。 不说技术的领先点,我只扯扯…...
Java项目实战II基于微信小程序的马拉松报名系统(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 马拉松运动…...
家用wifi的ip地址固定吗?换wifi就是换ip地址吗
在探讨家用WiFi的IP地址是否固定,以及换WiFi是否就意味着换IP地址这两个问题时,我们首先需要明确几个关键概念:IP地址、家用WiFi网络、以及它们之间的相互作用。 一、家用WiFi的IP地址固定性 家用WiFi环境中的IP地址通常涉及两类:…...
codeforces _ 补题
C. Ball in Berland 传送门:Problem - C - Codeforces 题意: 思路:容斥原理 考虑 第 i 对情侣组合 ,男生为 a ,女生为 b ,那么考虑与之匹配的情侣 必须没有 a | b ,一共有 k 对情侣&#x…...
DataSophon集成ApacheImpala的过程
注意: 本次安装操作系统环境为Anolis8.9(Centos7和Centos8应该也一样) DataSophon版本为DDP-1.2.1 整合的安装包我放网盘了: 通过网盘分享的文件:impala-4.4.1.tar.gz等2个文件 链接: https://pan.baidu.com/s/18KfkO_BEFa5gVcc16I-Yew?pwdza4k 提取码: za4k 1…...
深入探讨TCP/IP协议基础
在当今数字化的时代,计算机网络已经成为人们生活和工作中不可或缺的一部分。而 TCP/IP 协议作为计算机网络的核心协议,更是支撑着全球互联网的运行。本文将深入探讨常见的 TCP/IP 协议基础,带你了解计算机网络的奥秘。 一、计算机网络概述 计…...
《Windows PE》7.4 资源表应用
本节我们将通过两个示例程序,演示对PE文件内图标资源的置换与提取。 本节必须掌握的知识点: 更改图标 提取图标资源 7.4.1 更改图标 让我们来做一个实验,替换PE文件中现有的图标。如果手工替换,一定是先找到资源表,…...
【重生之我要苦学C语言】猜数字游戏和关机程序的整合
今天来把学过的猜数字游戏和关机程序来整合一下 如果有不明白的可以看往期的博客 废话不多说,上代码: #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <time.h> #include <stdlib.h> #include <string.h> void…...
基于centos7脚本一键部署gpmall商城
基于centos7脚本一键部署单节点gpmall商城,该商城可单节点,可集群,可高可用集群部署,VMware17,虚拟机IP:192.168.200.100 将软件包解压到/root目录 [rootlocalhost ~]# ls dist …...
Mac book英特尔系列?M系列?两者有什么区别呢
众所周知,Mac book有M系列,搭载的是苹果自研的M芯片,也有着英特尔系列,搭载的是英特尔的处理器,虽然从 2020 年开始,苹果公司逐步推出了自家研发的 M 系列芯片,并逐渐将 MacBook 产品线过渡到 M…...
Python unstructured库详解:partition_pdf函数完整参数深度解析
Python unstructured库详解:partition_pdf函数完整参数深度解析 1. 简介2. 基础文件处理参数2.1 文件输入参数2.2 页面处理参数 3. 文档解析策略3.1 strategy参数详解3.2 策略选择建议 4. 表格处理参数4.1 表格结构推断 5. 语言处理参数5.1 语言设置 6. 图像处理参数…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
