H264 NALU分析以及提取h264实战

news2024/11/27 4:17:07

1.H264简介

H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。在ITU的标准⾥称 为H.264,在MPEG的标准⾥是MPEG-4的⼀个组成部分–MPEG-4 Part 10,⼜叫Advanced Video Codec,因此常常称为MPEG-4 AVC或直接叫AVC。

2.H264编码原理以及H264中的I帧、P帧和B

对于视频⽂件来说,视频由单张图⽚帧所组成,⽐如每秒25帧,但是图⽚帧的像素块之间存在 相似性,因此视频帧图像可以进⾏图像压缩;H264采⽤了16*16的分块⼤⼩对,视频帧图像进⾏相似⽐较和压缩编码。

H26使⽤帧内压缩帧间压缩的⽅式提⾼编码压缩率;H264采⽤了独特的I帧、P帧和B帧策略来实现,连续帧之间的压缩;

在这里插入图片描述

  • I帧:帧内编码帧 ,I 帧通常是每个 GOP(MPEG 所使⽤的⼀种视频压缩技术) 的第⼀个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是⼀个图像经过压缩后的产物。 ⾃身可以通过视频解压算法解压成⼀张单独的完整的图⽚

  • P帧:前向预测编码帧,通过充分将低于图像序列中前⾯已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。

    需要参考其前⾯的⼀个I frame 或者P frame来⽣成⼀张完整 的图⽚

  • B帧:双向预测帧,既考虑与源图像序列前⾯已编码帧,也顾及源图像序列后⾯ 已编码帧之间的时间冗余信息来压缩传输数据量的编码图像, 也叫双向预测帧。要参考其前⼀个I或者P帧及其后⾯的⼀个P帧来⽣成⼀张完 整的图⽚

一般是说,I帧的压缩率是7,p帧的压缩率是20,b帧的压缩率是50

3.H264编码结构解析

H264除了实现了对视频的压缩处理之外,为了⽅便⽹络传输,提供了对应的视频编码和分⽚策略;在H264中将其称为组(GOP, group of pictures)、⽚ (slice)、宏块(Macroblock)这些⼀起组成了H264的码流分层结构;H264将其组织成为序列(GOP)、图⽚(pictrue)、⽚(Slice)、宏块(Macroblock)、⼦块(subblock)五个层次。

GOP (图像组)主要⽤作形容⼀个IDR帧 到下⼀个IDR帧之间的间隔了多少个帧:

在这里插入图片描述

H264将视频分为连续的帧进⾏传输,在连续的帧之间使⽤I帧、P帧和B帧。同时对于帧内⽽ ⾔,将图像分块为⽚、宏块和字块进⾏分⽚传输;通过这个过程实现对视频⽂件的压缩包装。

IDR(Instantaneous Decoding Refresh,即时解码刷新) ⼀个序列的第⼀个图像叫做 IDR 图像(⽴即刷新图像),IDR 图像都是 I 帧图像,I和IDR帧都使⽤帧内预测。I帧不⽤参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之 前的帧的。IDR就不允许这样。

IDR帧核⼼作⽤是:是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀ 个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤ IDR之前的图像的数据来解码。

4.NALU(Network Abstract Layer Unit)

在这里插入图片描述

SPS:序列参数集,SPS中保存了⼀组编码视频序列(Coded video sequence)的全局参数。

PPS:图像参数集,对应的是⼀个序列中某⼀幅图像或者某⼏幅图像的参数。

I帧:帧内编码帧,可独⽴解码⽣成完整的图⽚。

P帧**😗* 前向预测编码帧,需要参考其前⾯的⼀个I 或者B 来⽣成⼀张完整的图⽚。

B帧**😗* 双向预测内插编码帧,则要参考其前⼀个I或者P帧及其后⾯的⼀个P帧来⽣成⼀张完整的 图⽚。

发I帧之前,⾄少要发⼀次SPS和PPS。

5.NALU结构

H.264原始码流(裸流)是由⼀个接⼀个NALU组成,它的功能分为两层,VCL(视频编码层)和 NAL(⽹络提取层)

  • VCL:包括核⼼压缩引擎和块,宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹络进⾏⾼效的编码
  • NAL:负责将VCL产⽣的⽐特字符串适配到各种各样的⽹络和多元环境中,覆盖了所有⽚级以上的语法级别

在VCL进⾏数据传输或存储之前,这些编码的VCL数据,被映射或封装进NAL单元。 ⼀个NALU = ⼀组对应于视频编码的NALU头部信息 + ⼀个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)

NALU结构单元的主体结构如下所示;⼀个原始的H.264 NALU单元通常由**[StartCode] [NALU** **Header] [NALU Payload]**三部分组成,其中 Start Code ⽤于标示这是⼀个NALU 单元的开始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个NAL header + RBSP;

在这里插入图片描述

注意:对于FFmpeg解复⽤后,MP4⽂件读取出来的packet是不带startcode,但TS⽂件读取出来 的packet带了startcode

每个NAL单元是⼀个⼀定语法元素的可变⻓字节字符串,包括包含⼀个字节的头信息(⽤来表 示数据类型),以及若⼲整数字节的负荷数据。

H.264标准指出,当数据流是储存在介质上时,在每个NALU 前添加起始码:0x0000010x00000001,⽤来指示⼀个NALU 的起始和终⽌位置:

  • 在这样的机制下,在码流中检测起始码,作为⼀个NALU得起始标识,当检测到下⼀个起始码时,当前NALU结束。
  • 3字节的0x000001只有⼀种场合下使⽤,就是⼀个完整的帧被编为多个slice(⽚)的时 候,包含这些slice的NALU 使⽤3字节起始码。其余场合都是4字节0x00000001的。
nal_unit_typeNAL 单元和 RBSP 语法结构的内容
0未指定
1⼀个⾮IDR图像的编码条带 slice_layer_without_partitioning_rbsp
2编码条带数据分割块A slice_data_partition_a_layer_rbsp
3编码条带数据分割块B slice_data_partition_b_layer_rbsp
4编码条带数据分割块C slice_data_partition_c_layer_rbsp
5IDR图像的编码条带(⽚) slice_layer_without_partitioning_rbsp
6辅助增强信息 (SEI) sei_rbsp
7序列参数集 seq_parameter_set_rbsp
8图像参数集 pic_parameter_set_rbsp
9访问单元分隔符 access_unit_delimiter_rbsp
10序列结尾 end_of_seq_rbsp
11流结尾 end_of_stream_rbsp
12填充数据 filler_data_rbsp
13序列参数集扩展 seq_parameter_set_extension_rbsp
14…18保留
19未分割的辅助编码图像的编码条带 slice_layer_without_partitioning_rbsp
20…23保留
24…31未指定

6.H264 annexb模式

H264有两种封装:

  • ⼀种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中
  • ⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度 很多解码器只⽀持annexb这种模式,因此需要将mp4做转换:在ffmpeg中⽤h264_mp4toannexb_filter可以做转换

7.从视频中抽取h264

 AVFormatContext *av_format_context =NULL;
    int video_index=-1;
    AVPacket *pkt = NULL;
    int  ret=-1;
    int file_end = 0;  //文件是否读取结束

    if(argc<3){
        printf("添加必须的输入文件\n");
        return -1;
    }

    FILE * out_fd=fopen(argv[2],"wb");  //声明文件句柄 并打开文件
    printf("输入文件:%s,输出文件%s\n",argv[1],argv[2]);
    //分配解复用器的内存 使用avformat_close_input 释放
    av_format_context =avformat_alloc_context();
    if(!av_format_context){
        printf("error can not alloc format context\n");
        return -1;
    }

    //根据URL 打开码流 并选择匹配的解复用器
    ret=avformat_open_input(&av_format_context,argv[1],NULL,NULL);
    if(ret<0){
        printf("error avformat_open_input: %s\n", av_get_err(ret));
        avformat_close_input(&av_format_context);
        return -1;
    }

    //读取媒体文件部分数据包  获取码流信息
    ret=avformat_find_stream_info(av_format_context,NULL);
    if(ret<0){
        printf("error avformat_find_stream_info: %s\n", av_get_err(ret));
        avformat_close_input(&av_format_context);
        return -1;
    }
 // 查找出哪个码流是video/audio/subtitles
    video_index=av_find_best_stream(av_format_context,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if(video_index == -1){
        printf("error Do not find a video stream.\n");
        avformat_close_input(&av_format_context);
        return -1;
    }

    //分配数据包
    pkt=av_packet_alloc();
    av_init_packet(pkt);
  • avformat_open_input:打开视频文件,解复用
  • avformat_find_stream_info:读取媒体文件部分数据包 获取码流信息
  • av_find_best_stream:查找出哪个码流是video/audio/subtitles
   //获取相应的比特流过滤器
    //FLV/MP4/MKV 等结构中,h264需要h264_mp4toannexb处理。添加sps/pps等信息
    //FLV封装时,可以把多个NALU放在一个Video TAG 中,结构为4B NALU长度+NALU1+4B NALU长度+NALU2+...
    //需要做的处理把4B长度换成00000001或者000001
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext *bsf_cts = NULL;

    //比特流过滤器上下文分配内存
    av_bsf_alloc(bsfilter,&bsf_cts);
    //添加解码器属性
    avcodec_parameters_copy(bsf_cts->par_in,av_format_context->streams[video_index]->codecpar);
    //初始化比特流过滤器
    av_bsf_init(bsf_cts);

    file_end=0;
    while(0 == file_end){
        if((ret=av_read_frame(av_format_context,pkt))<0){
            //没有更多的数据包了
            file_end = 1;
            printf("read file end: ret:%d\n", ret);
        }
        if(ret==0 && pkt->stream_index==video_index){
#if 1
            int input_size = pkt->size;
            int out_pkt_count= 0;

            if(av_bsf_send_packet(bsf_cts,pkt)!=0){ // bitstreamfilter内部去维护内存空间
                av_packet_unref(pkt);
                continue;
            }
            av_packet_unref(pkt);
            while (av_bsf_receive_packet(bsf_cts,pkt)==0) {
                out_pkt_count++;
//                printf("fwrite size:%d\n", pkt->size);
                size_t size=fwrite(pkt->data,1,pkt->size,out_fd);
                if(size!=pkt->size){
                    printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
                }
                av_packet_unref(pkt);
            }

            if(out_pkt_count>=2){
                printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n",
                       input_size, out_pkt_count);
            }
#else      // TS流可以直接写入
            size_t size= fwrite(pkt->data,1,pkt->size,out_fd);
            if(size != pkt->size)
            {
                printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
            }
            av_packet_unref(pkt);
#endif
        }else{
            if(ret==0)
                av_packet_unref(pkt); //不是视频流直接释放内存
        }
    }

    if(out_fd){
        fclose(out_fd);
    }
    if(bsf_cts){
        av_bsf_free(&bsf_cts);
    }

    if(pkt){
        av_packet_free(&pkt);
    }

    if(av_format_context){
        avformat_close_input(&av_format_context);
    }

    printf("------finish-----------\n");
  • av_bsf_get_by_name(“h264_mp4toannexb”):得到比特流过滤器
  • av_bsf_send_packet:将数据包发送给比特流过滤器进行处理
  • av_bsf_receive_packet:拿到比特流过滤器处理的结果,主要是添加PPS、SPS等信息
  • size_t size=fwrite(pkt->data,1,pkt->size,out_fd):将处理之后的数据写入文件

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

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

相关文章

Java学习(22)调试初步——debug入门

程序调试-debug 调试的作用 让程序员能看清程序每一步的效果&#xff0c;在需要查看结果的时候&#xff0c;使用debug查看实际结果是否与预期结果一致。案例 (1) 设置断点 鼠标左键双击设置/取消&#xff0c;或者右键选择Toggle Breakpoint。 (2) 执行调试 Run→\to→Debug&a…

快速了解chrony服务器

目录 一、chrony服务器 二、chrony安装与配置 三、时间服务器 四、配置时间服务器 一、chrony服务器 Chrony是一个开源自由的网络时间协议 NTP 的客户端和服务器软软件。它能让计算机保持系统时钟与时钟服务器&#xff08;NTP&#xff09;同步&#xff0c;因此让你的计算机…

操作系统期末复习题-不挂科

写在前面&#xff1a; 本题目由博主收集而成&#xff0c;如有侵权请告知删除&#xff01; 一、选择题 DMA 控制方式是在&#xff08; B&#xff09;之间建立一条直接数据通路。 A. I/O 设备与 CPU B. I/O 设备与主存 C. CPU 与主存 D. I/O 设备之间 以下 I/O 控制方式中&…

javaee之springMVC3

ssm整合说明 ssm整合之搭建环境 现在先去数据里面&#xff0c;创建好库&#xff0c;创建好表 然后在pom.xml中引入各种需要的jar包 pom.xml <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0"…

webpack起步

webpack 1&#xff0c;起步 先安装node.js 再用npm安装webpack全局 运用commonjs模块化语法&#xff0c;用全局命令进行打包来初步了解一下 命令为&#xff1a; webpack 入口main路径 出口bundle路径 很麻烦所以要配置一下webpack入口出口配置。 2&#xff0c;配置 webpa…

SpringCloud Netflix复习之Ribbon

文章目录写作背景SpringCloud Ribbon是什么&#xff0c;干了什么事情Ribbon组件的核心功能Ribbon内置了哪些负载均衡算法上手实战在SpringCloud里Ribbon实战从源码角度看下Ribbon实现原理SpringCloud与Ribbon整合的原理LoadBalancerInterceptor拦截器改变了RestTemplate什么行为…

第三章:OAuth协议流程

应用场景 1、 原生app授权&#xff1a;app登录请求后台接口&#xff0c;为了安全认证&#xff0c;所有请求都带token信息&#xff0c;如果登录验证、 请求后台数据。 2、前后端分离单页面应用&#xff1a;前后端分离框架&#xff0c;前端请求后台数据&#xff0c;需要进行oauth…

Codeforces Round #842 (Div. 2) C. Elemental Decompress

翻译&#xff1a;您将得到一个由&#x1d45b;个整数组成的数组&#x1d44e;。找到两个排列组合&#x1d45d;长度和&#x1d45e;&#x1d45b;这样马克斯(&#x1d45d;&#x1d456;,&#x1d45e;&#x1d456;)&#x1d44e;&#x1d456;所有1≤&#x1d456;≤&#x1…

Spring Boot学习篇(七)

Spring Boot学习篇(七) 1.thymeleaf模板引擎使用篇(一) 1.1 准备工作 1.1.1 在pom.xml中导入所需要的依赖 a thymeleaf模板引擎所需要的依赖 <!--thymeleaf模板引擎,前后端不分离的时候用 普通的html格式--> <dependency><groupId>org.springframework.…

计算机网络实验---Wireshark 实验

数据链路层 实作一/熟悉 Ethernet 帧结构 实作二/了解子网内/外通信时的 MAC 地址 实作三/掌握 ARP 解析过程 网络层 实作一 /熟悉 IP 包结构 实作二 IP 包的分段与重组 实作三 考察 TTL 事件 传输层 实作一 熟悉 TCP 和 UDP 段结构 实作二 分析 TCP 建立和释放连接 应用层 …

数据结构基本介绍

1.what is Data Structure? A data structure is a memory used to store and organize dataIt is also used for processing, retrieving, and storing data 2. what is classification of Data Structure? 2.1 what is Linear data structure? Data structure in which…

你还不懂递归么?不允许你不懂,进来折腾下自己吧

举例数组 const arr [ {id: "1175310929766055936", pid: "", name: "总裁办" },---返回空数组, {id: "1175311213774962688", pid: "", name: "行政部" },---返回空数组, {id: "1175311267684352000&qu…

随想录一刷Day27——回溯算法

文章目录Day27_回溯算法13. 子集 II14. 491.递增子序列15. 全排列全排列 IIDay27_回溯算法 13. 子集 II 90. 子集 II class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startI…

[SwiftUI 开发] Widget 小组件

小组件简述 小组件可以在主屏幕上实现内容展示和功能跳转。 系统会向小组件获取时间线&#xff0c;根据当前时间对时间线上的数据进行展示。点击正在展示的视觉元素可以跳转到APP内&#xff0c;实现对应的功能。 小组件是一个独立于 App 环境(即 App Extension)&#xff0c;小…

谷粒学院——第二十一章、spring security详细

一、Spring Security介绍 1、Spring Security简介 Spring 是非常流行和成功的 Java 应用开发框架&#xff0c;Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架&#xff0c;提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全…

年度征文|2022年「博客之星」,花落谁家? 大家来竞猜吧

一年一度的「博客之星」大赛如火如荼地进行着&#xff0c;大家都忙着评分、发帖、回帖.....今天发现我在分组的排名只有40多名&#xff0c;基本上算是放弃了。但是&#xff0c;看到本文的朋友&#xff0c;还是可以帮我拉拉票的&#xff01;请点开链接给个五星评分&#xff1a; …

CentOS服务器署Springboot的java项目最简单操作步骤

CentOS服务器署Springboot的java项目最简单操作步骤 准备工作 1.首先本地有一个能跑起来正常的 java 项目的 jar 包; 2.有一个前端项目, 可以仅是一个 index.html 文件; 3.需要备案好的域名 (可选, 否则只能 ip 访问) 4.购买阿里云或者腾讯云等等任意 CentOS 服务器一个 1. 获…

(机器学习深度学习常用库、框架|Pytorch篇)第(待定)节:卷积神经网络CNN中一些经典网络结构写法

文章目录一&#xff1a;LeNet-5二&#xff1a;AlexNet三&#xff1a;VGG四&#xff1a;ResNet五&#xff1a;MobileNetV1六&#xff1a;InceptionNet一&#xff1a;LeNet-5 LeNet-5&#xff1a;LeNet-5是一个较简单的卷积神经网络。下图显示了其结构&#xff1a;输入的二维图像…

ansible通过多种方法配置yum源仓库

目录 1.挂载本地光盘到/mnt ​2.配置yum源仓库文件通过多种方式实现 仓库1 &#xff1a;Name: RH294_BaseDescription&#xff1a; RH294 base softwareBase urt: file:///mnt/BaseOS不需要验证钦件包 GPG 签名启用此软件仓库 ​编辑仓库 2:Name: RH294_StreamDescription &…

第四十二篇 nextTick

在前面封装swiper组件当中&#xff0c;通过许多种方式方法&#xff0c;其一从mounted初始化过早转到updated后出现初始化重复&#xff0c;再者通过设置key值和使用v-if控制swiper组件&#xff0c;然后通过Vue.diretive自定义指令的方式来封装swiper组件&#xff0c;那么本篇的n…