实例分析AnnexB格式h264流startcode

我们知道,h264 流格式有两种:avcC与AnnexB。

avcC 就是在 NALU 前面写上几个字节,这几个字节组成一个整数(大端字节序)这个整数表示了整个 NALU 的长度。在读取的时候,先把这个整数读出来,拿到这个 NALU 的长度,然后按照长度读取整个 NALU,我们不妨把这几个字节叫做NALU Body Length。

AnnexB 就是在一个 NALU 前面加上三个或者四个字节,这些字节的内容是 0 0 0 1 或者 0 0 1。当我们读取一个 H264 流的时候,一旦遇到 0 0 0 1 或者 0 0 1,我们就认为一个新的 NALU 开始了,因此,这些用来做分隔符的字节,一般也被称为 start code。

所以,接下来重点分析下startcode。

startcode的有两种形式

3字节的0x000001和4字节的0x00000001

为什么需要startcode?

主要是为了将相邻两个NALU划分开,让他们有一个界线,方便解码,比如将h264的数据存储在一个文件当中,解码器无法从数据流中分别每个NALU的起始位置。

在编码时,每个NALU前面添加startcode(占4字节0x00000001或者3字节0x000001),这里有人会想到万一中间出现0x000001怎么办呢,h264有个防止竞争的机制,在编码完一个NAL时,如果出现有连续两个0x00字节,就在后面插入一个0x03(解码的时候这个0x03会被丢弃)。

startcode占4字节还是3字节?

实际上startcode只占3字节,4字节的startcode = zero_byte + start_code_prefix_one_3bytes,就是说无论啥时候其实startcode都是3字节,关键就在于zero_byte

  1. 包含SPS,PPS的NALU前面要加zero_byte(4字节)。
  2. 当一帧被分为多个slice时,首个NALU前面要加zero_byte(4字节)。也就是,当一个完整的帧被编为多个slice的时候,除掉第一个NALU,剩下的都用3字节的,其余的都是4字节,这个在后面的实例分析中可以得到验证。

比如给定一组frame:

SPS            (4字节头)
PPS            (4字节头)
SEI            (4字节头)
I0(slice0)4字节头)
I0(slice1)3字节头)
P1(slice0)4字节头)
P1(slice1)3字节头)
P2(slice0)4字节头)
P2(slice1)3字节头)
  • I0(slice0)是序列第一帧(I帧)的第一个slice,是当前Access Unit的首个nalu,所以是4字节头。而I0(slice1)表示第一帧的第二个slice,所以是3字节头。
  • P1(slice0) 、P1(slice1)同理。

h264stream文件实例分析

0x00000000的地址开始是SPS,这时候startcode是0x00000001,4个字节

在这里插入图片描述

0x00000019的地址开始是PPS,这时候startcode是0x00000001,4个字节

在这里插入图片描述

0x00000021的地址开始是SEI,这时候startcode是0x000001,3个字节

在这里插入图片描述

0x00000281的地址开始是第一个I帧的slice 0,这时候startcode是0x000001,3个字节

在这里插入图片描述
0x000002CE的地址开始是第一个I帧的slice 1,这时候startcode是0x00000001,3个字节

在这里插入图片描述

0x00000310的地址开始是接着的P帧的slice 0,这时候startcode是0x0000000001,4个字节

在这里插入图片描述
0x000006A4的地址开始是接着的P帧的slice 1,这时候startcode是0x00000001,3个字节

在这里插入图片描述
0x000008BA的地址开始是接着的下一个P帧的slice 0,这时候startcode是0x0000000001,4个字节

在这里插入图片描述
依次往后分析,每一个完整的帧开始的时候startcode都是4个字节的startcode,每个帧的slice使用3个字节的startcode分隔。

对比ffprobe生成信息

ffprobe生成frame信息文件videoframes.info:

ffprobe -show_frames -select_streams v -of xml 256x144.h264 > videoframes.info

简化这个xml文件内容后如下:

<?xml version="1.0" encoding="UTF-8"?>
<ffprobe>
    <frames>
        <frame pkt_pos="0" pkt_size="784" pict_type="I">
            <side_data_list>
                <side_data side_data_type="H.26[45] User Data Unregistered SEI message"/>
            </side_data_list>
        </frame>
        <frame pkt_pos="784" pkt_size="1450"   pict_type="P" coded_picture_number="1" />
        <frame pkt_pos="2234" pkt_size="2951"  pict_type="P" coded_picture_number="2" />
        <frame pkt_pos="5185" pkt_size="3647"  pict_type="P" coded_picture_number="3" />
        <frame pkt_pos="8832" pkt_size="644"   pict_type="P" coded_picture_number="4" />
        <frame pkt_pos="9476" pkt_size="952"   pict_type="P" coded_picture_number="5" />
        <frame pkt_pos="10428" pkt_size="981"  pict_type="P" coded_picture_number="6" />
        <frame pkt_pos="11409" pkt_size="678"  pict_type="P" coded_picture_number="7" />
        <frame pkt_pos="12087" pkt_size="1003" pict_type="P" coded_picture_number="8" />
        <frame pkt_pos="13090" pkt_size="415"  pict_type="P" coded_picture_number="9" />
        <frame pkt_pos="13505" pkt_size="772"  pict_type="P" coded_picture_number="10"/>
        <frame pkt_pos="14277" pkt_size="799"  pict_type="P" coded_picture_number="11"/>
        <frame pkt_pos="15076" pkt_size="424"  pict_type="P" coded_picture_number="12"/>
        <frame pkt_pos="15500" pkt_size="466"  pict_type="P" coded_picture_number="13"/>
        <frame pkt_pos="15966" pkt_size="745"  pict_type="P" coded_picture_number="14"/>
    </frames>
</ffprobe>

从这个结果对比后面的代码分析,ffprobe拿到的frame 0的信息,packet size是784,刚好是从起始地址到I帧结束的大小,0x00000310换算成10进制就是784,对比流的16进制和后面代码对stream的解析来看,ffprobe给出的信息第一个frame的实际上包含了SPSPPSSEII帧数据,在SPS和PPS前面的startcode是4个字节,而后面的程序解析,frame#0是SPS,frame#1是包含PPS和SEI的I帧。

在这里插入图片描述

代码解析startcode

后面的这段代码解析前面的h264stream文件,然后打印每一个frame的大小,通过输出信息来看,和前面的xml统计信息符合,区别就是Frame 0和Frame 1的输出分别是SPSPPS的4个字节的startcode开始的帧,这个和前面用工具分析的截图完全一致。

Frame 0: 25 bytes
Frame 1: 759 bytes
Frame 2: 1450 bytes
Frame 3: 2951 bytes
Frame 4: 3647 bytes
Frame 5: 644 bytes
Frame 6: 952 bytes
Frame 7: 981 bytes
Frame 8: 678 bytes
Frame 9: 1003 bytes
Frame 10: 415 bytes
Frame 11: 772 bytes
Frame 12: 799 bytes
Frame 13: 424 bytes
Frame 14: 466 bytes
#include <stdint.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define START_CODE_PREFIX_LENGTH 3
#define START_CODE_LENGTH 4

int main(int argc, char *argv[])
{
    FILE *fp = fopen(argv[1], "rb");
    if (!fp) {
        printf("Failed to open file\n");
        return -1;
    }

    // Allocate buffer for reading file
    int buffer_size = 1024 * 1024;
    uint8_t *buffer = (uint8_t *)malloc(buffer_size);

    // Allocate buffer for storing frame data
    int frame_size = buffer_size;
    uint8_t *frame = (uint8_t *)malloc(frame_size);

    int frame_count = 0;
    int bytes_read = 0;
    int frame_start = 0;
    int frame_end = 0;
    int frame_length = 0;
    int start_code_prefix_found = 0;

    while ((bytes_read = fread(buffer, 1, buffer_size, fp)) > 0)
    {
        for (int i = 0; i < bytes_read; i++) {
            if (!start_code_prefix_found) {

                /*
                 * 这里用001来判断的好处是,当发现后面的四个字节是0001的时候,说明frame结
                 * 束,这时候buffer[i]的位置已经是下一个0001的0位置,下次循环进来的时候
                 * buffer指向的位置刚好是001,因为有i++运算,已经去掉了前导0
                 * (leading_zero_8bits)
                 *
                 * 如果是0001,那么经过i++,start_code_prefix_found的位置就是下下一个
                 * startcode的位置了。
                 */

                if (i < bytes_read - START_CODE_PREFIX_LENGTH) {
                    if (buffer[i] == 0x00 &&
                        buffer[i+1] == 0x00 &&
                        buffer[i+2] == 0x01) {
                        start_code_prefix_found = 1;
                        frame_start = i + START_CODE_PREFIX_LENGTH;
                    }
                }
            } else {
                if (i < bytes_read - START_CODE_LENGTH) {
                    if (buffer[i] == 0x00 &&
                        buffer[i+1] == 0x00 &&
                        buffer[i+2] == 0x00 &&
                        buffer[i+3] == 0x01) {

                        start_code_prefix_found = 0;
                        frame_end = i;
                        frame_length = frame_end - frame_start;

                        if (frame_length > frame_size) {
                            frame_size = frame_length;
                            frame = (uint8_t *)realloc(frame, frame_size);
                        }
                        memcpy(frame, buffer + frame_start, frame_length);
                        printf("Frame %d: %d bytes\n", frame_count++, frame_length + START_CODE_LENGTH);
                    }
                } else if (i == bytes_read-1) {
                    frame_length = bytes_read - frame_start;
                    memcpy(frame, buffer + frame_start, frame_length);
                    printf("Frame %d: %d bytes\n", frame_count++, frame_length + START_CODE_LENGTH);
                }
            }
        }
    }

    fclose(fp);
    free(buffer);
    free(frame);

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

django rest framework 学习笔记-实战商城

01项目环境搭建_哔哩哔哩_bilibili 本博客借鉴至大佬的视频学习笔记 # 创建项目 django-admin startproject MyShop# 创建app E:\desktop\my_drf\MyShop>django-admin startapp goodsE:\desktop\my_drf\MyShop>django-admin startapp orderE:\desktop\my_drf\MyShop>…

MongoDB 权限管理

文章目录 前言1. 权限控制1.1 MongoDB 默认角色1.1.1 读写角色1.1.2 管理角色1.1.3 其他角色1.1.4 超级用户角色 1.2 用户管理1.2.1 查看用户1.2.2 创建新用户1.2.3 调整角色1.2.4 删除用户1.2.4 修改密码 前言 上一篇 《MongoDB 单机安装部署》 文章中&#xff0c;为 MongoDB…

MySQL安装教程(详细版)

今天分享的是Win10系统下MySQL的安装教程&#xff0c;打开MySQL官网&#xff0c;按步骤走呀~ 宝们安装MySQL后&#xff0c;需要简单回顾一下关系型数据库的介绍与历史&#xff08;History of DataBase&#xff09; 和 常见关系型数据库产品介绍 呀&#xff0c;后面就会进入正式…

Pytorch学习05_常见的transforms01

案例解释 创建新的py文件 引入transforms模块 from torchvision import transforms 按住”Ctrl“&#xff0c;鼠标左键点击”transforms“&#xff0c;跳转到”__init__.py“ 再次按住”Ctrl“&#xff0c;鼠标点击”.transforms”&#xff0c;跳转到transforms.py中 __call_…

微服务—RabbitMQ高级(业务在各方面的可靠性)

本博客为个人学习笔记&#xff0c;学习网站&#xff1a;2023黑马程序员RabbitMQ入门到实战教程 高级篇章节 目录 生产者可靠性 生产者重连机制 生产者确认机制 介绍 实现 总结与建议 MQ可靠性 数据持久化 LazyQueue 消费者可靠性 消费者确认机制 失败重试机制 失…

Nginx 403 forbidden

1、没有权限问题 Linux系统中如果Nginx没有web目录的操作权限&#xff0c;也会出现403错误。解决办法&#xff1a;修改web目录的读写权限&#xff0c;或者是把Nginx的启动用户改成目录的所属用户&#xff0c;重启Nginx即可解决。(windows 下则用管理员启动nginx即可)。 chmod -…

uniapp上传文件到腾讯云

官方API地址 javaScript_SDK 下载cos npm i cos-js-sdk-v5 --save 生成签名 获取secretId和secretKey let cos new COS({SecretId: *******************************,SecretKey: ******************************,}) 参考文章&#xff1a;腾讯云如何获取secretId和secret…

力扣精选算法100道——提莫攻击(模拟专题)

目录 &#x1f6a9;题目解析 &#x1f6a9;算法原理 &#x1f6a9;实现代码 &#x1f6a9;题目解析 输入&#xff1a;timeSeries [1,4], duration 2 输出&#xff1a;4 解释&#xff1a;提莫攻击对艾希的影响如下&#xff1a; - 第 1 秒&#xff0c;提莫攻击艾希并使其立即…

【Git】:分支管理

分支管理 一.概念二.分支管理基本操作三.分支管理策略1.noff模式2.分支策略 一.概念 在版本回退⾥&#xff0c;你已经知道&#xff0c;每次提交&#xff0c;Git都把它们串成⼀条时间线&#xff0c;这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前&#xff0c;只有⼀条时间线&…

搜索中关于稀疏检索和稠密向量检索的召回效果比较

不同检索方式说明 最近在做搜索召回提升相关的研究工作。对比了稀疏检索和稠密向量检索的效果。其中使用的搜索引擎为elasticsearch8.x版本。稀疏检索包括BM25的检索方式&#xff0c;以及es官方在8.8之后版本提供的稀疏向量模型的方式。稠密向量检索&#xff0c;是指借助机器学…

tqdm,Python进度条库快速上手

前言 在编程中&#xff0c;尤其是处理长时间运行的任务时&#xff0c;了解程序的进度是非常重要的。Python中有一个非常实用的库&#xff0c;名为 tqdm &#xff0c;它能够以极简的方式帮助我们快速创建进度条。本文将向初学者介绍如何在Python中使用 tqdm 库来制作进度条。 …

2、电源管理入门之开机详解

目录 1. 硬件上电 2. ATF运行 3. Linux启动 3.1 内核启动start_kernel 3.2 平台启动setup_arch 3.4 DTS初始化psci_dt_init 3.5 系统rest创建kernel_init线程 3.6 SMP初始化smp_init 3.7 PSCI接口psci_cpu_on 3.8 SMC返回secondary_entry 系统开机牵扯到:“我是…

Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

python3 flask 实现对config.yaml文件的内容的增删改查,并重启服务

config.yaml配置文件内容 功能就是userpass下的用户名和密码做增删改查&#xff0c;并重启hy2服务 auth:type: userpassuserpass:csdn: csdnlisten: :443 masquerade:proxy:rewriteHost: trueurl: https://www.bing.com/type: proxy tls:cert: /root/hyst*****马赛克******er…

JS文本加密方法探究

在前端开发中&#xff0c;有时候我们需要对敏感文本进行简单的加密&#xff0c;以提高安全性。本文将介绍一种基于 JavaScript 实现的文本加密方法&#xff0c;使用了 Base64、Unicode 和 ROT13 编码。 示例代码 function encodeText(text) {// Base64编码var base64Encoded …

苍穹外卖——第一天nginx

放到全是英文路径的打不开 到安装路径进入cmd&#xff0c;输入nginx -t nginx: the configuration file E:\Astudy\nginx-1.20.2/conf/nginx.conf syntax is ok nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbid…

linux部署jenkins,支持jdk1.8

无废话&#xff0c;纯干活安装指令 本文前提条件需安装jdk8&#xff0c;安装参考&#xff1a;Linux配置jdk环境 下载资源 # 创建安装目录 mkdir -p /data/jenkins && cd /data/jenkins# 下载jenkins的war包&#xff0c;v2.346.x支持jdk1.8&#xff0c;高于这个版本的…

板块一 Servlet编程:第六节 HttpSession对象全解 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第六节 HttpSession对象全解 一、什么是HttpSessionSession的本质 二、创建Seesion及常用方法三、Session域对象四、Session对象的销毁 在上一节中&#xff0c;我们学习了Servlet五大对象里的第三个Cookie对象&#xff0c;但Cookie是有大小限制和…

ajax 如何从服务器上获取数据?

在Web开发中&#xff0c;AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;是一种常用的技术&#xff0c;用于在不重新加载整个页面的情况下&#xff0c;从服务器获取数据并更新网页的某一部分。使用AJAX&#xff0c;你可以创建异步请求&#xff0c;从而提供更快的…

AI:133-基于深度学习的工业质检自动化

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…