当前位置: 首页 > news >正文

FFMPEG H264

一、H264压缩编码

1.1 H264 中的 I 帧、P帧和 B帧

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

1.2 其他概念

GOP(图像组):一个IDR帧到下一个IDR帧之间间隔了多少个帧

IDR:一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是I帧图像。I帧不用参考任何帧,但之后的P帧和B帧是有可能参考这个I帧之前的帧。IDR帧不允许后面PB参考前面的帧。IDR的核心作用是为了序列出现重大错误后重新同步,不用参考IDR之前的图像的数据来解码

1.3 NLU

视频流至少发从一帧SPS和一帧PPS来告诉客户端视频流信息

H.264原始码流(裸流)是由⼀个接⼀个NALU组成。它的功能分为两层,VCL (视频编码层) 和
NAL (⽹络提取层)。VCL:负责压缩视频;NAL:把VCL压缩的视频封装成NLU帧

1、NLU帧包括三个部分

[StartCode] [NALU Header] [NALU Payload]

StartCode必须是 "00 00 00 01" 或 "00 00 01"

NALU Header是NALU的头部

RBSP是I、P、B帧的数据。

NLU帧的结束也是根据00 00 00 01或00 00 01来判断的

3字节的0x000001只有⼀种场合下使⽤,就是⼀个完整的帧被编为多个slice(⽚)的时
候,包含这些sliceNALU 使⽤3字节起始码。其余场合都是4字节0x00000001的。前面的I帧就是分别存储在两个NALU中,这两个NALU的开头就是00 00 01

2、NLU Header第一个字节

其中:
T为负荷数据类型,占5bit
nal_unit_type:这个NALU单元的类型,112H.264使⽤,2431H.264以外的应⽤
使⽤
R为重要性指示位,占2bit
nal_ref_idc.:取00~11,似乎指示这个NALU的重要性,00NALU解码器可以丢弃它⽽不
影响图像的回放,03,取值越⼤,表示当前NAL越重要,需要优先受到保护。如果当前
NAL是属于参考帧的⽚,或是序列参数集,或是图像参数集这些重要的单位时,本句法元
素必需⼤于0
最后的F为禁⽌位,占1bit
forbidden_zero_bit: 在 H.264 规范中规定了这⼀位必须为 0.

3、NLU的类型

0x00 00 00 01 67
67
⼆进制:0110 0111   
00111 = 7(⼗进制)
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( )
5
IDR图像的编码条带() 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
序列参数集扩展9 seq_parameter_set_extension_rbsp( )
14...18
保留
19
未分割的辅助编码图像的编码条带 slice_layer_without_partitioning_rbsp( )
20...23
保留
24...31
未指定

1.4 H264 annexb模式

H264有两种封装

⼀种是Annex B模式,传统模式,有startcodeSPSPPS是在ES
⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcodeSPSPPS以及其它信息
被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度。很多解码器只⽀持Annex B这种模式,因此需要将mp4做转换:ffmpeg中⽤ h264_mp4toannexb_filter可以做转换
实现:
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext *bsf_ctx = NULL;
// 2 初始化过滤器上下⽂
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解码器属性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);

1. Annex B内部数据,对应的了前面讲解的内容

[4字节起始码] + [SPS NALU数据] 
[4字节起始码] + [PPS NALU数据] 
[3字节起始码] + [I帧NALU数据] 
[3字节起始码] + [P帧NALU数据] 
[3字节起始码] + [B帧NALU数据] 
...(后续NALU以此类推)

输出的文件是H.264视频文件

2. MP4直接的码流对应下图右边,开头记录的是NALU帧的长度,都是4字节

二、MP4->H.264代码

#include <iostream>extern "C"{#include <libavutil/log.h>#include <libavformat/avio.h>#include <libavformat/avformat.h>#include <libavcodec/bsf.h> 
}using namespace std;static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}int main(int argc, char **argv) {string inputFile = "believe.mp4";string outputFile = "believe.h264"; FILE * outfp = fopen(outputFile.c_str(),"wb");printf("in:%s out:%s\n", inputFile.c_str(), outputFile.c_str());// 创建AVFormatContext上下文AVFormatContext * ifmt_ctx = avformat_alloc_context();if (!ifmt_ctx) {printf("[error] Could not allocate context.\n");return -1;}int ret = avformat_open_input(&ifmt_ctx, inputFile.c_str(), NULL, NULL);if (ret != 0) {printf("[error] avformat_open_input: %s\n", av_get_err(ret));return -1;}ret = avformat_find_stream_info(ifmt_ctx, NULL);if (ret < 0) {printf("[error] avformat_find_stream_info: %s\n", av_get_err(ret));avformat_close_input(&ifmt_ctx);return -1;}// 查找出哪个码流是video/audio/subtitlesint videoindex = -1;videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(videoindex == -1) {printf("[error] Didn't find a video stream.\n");avformat_close_input(&ifmt_ctx);return -1;}// 创建AVPacket包AVPacket * pkt = av_packet_alloc();av_init_packet(pkt);// 1 获取一个名为"h264_mp4toannexb"的比特流过滤器// 用于将MP4容器中的H.264视频流转换为Annex B格式的H.264流const AVBitStreamFilter * bsfilter = av_bsf_get_by_name("h264_mp4toannexb");AVBSFContext * bsf_ctx = NULL;// 2 初始化过滤器上下文av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;// 3 复制视频流的编解码参数到过滤器上下文avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);av_bsf_init(bsf_ctx);int file_end = 0;while (0 == file_end) {if((ret = av_read_frame(ifmt_ctx, pkt)) < 0) {// 没有更多包可读file_end = 1;printf("read file end: ret:%d\n", ret);}if(ret == 0 && pkt->stream_index == videoindex) {int input_size = pkt->size;int out_pkt_count = 0;if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间{av_packet_unref(pkt);   // 你不用了就把资源释放掉continue;       // 继续送}av_packet_unref(pkt);   // 释放资源while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) {out_pkt_count++;// printf("fwrite size:%d\n", pkt->size);size_t size = fwrite(pkt->data, 1, pkt->size, outfp);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);}// 注释掉前面代码,可以直接保存mp4流// size_t size = fwrite(pkt->data, 1, pkt->size, outfp);// if(size != pkt->size)// {//     printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);// }// av_packet_unref(pkt);}else {if(ret == 0) av_packet_unref(pkt); // 释放内存}}if (outfp) fclose(outfp);if (bsf_ctx) av_bsf_free(&bsf_ctx);if (pkt) av_packet_free(&pkt);if (ifmt_ctx) avformat_close_input(&ifmt_ctx);printf("finish\n");return 0;
}

http://www.xdnf.cn/news/1448839.html

相关文章:

  • OpenLayers常用控件 -- 章节一:地图缩放控件详解教程
  • 如何通过level2千档盘口分析挂单意图
  • JavaScript的输出语句
  • 三阶Bezier曲线,已知曲线上一点到曲线起点的距离为L,计算这个点的参数u的方法
  • 专题四_前缀和_一维前缀和
  • 【OC】属性关键字
  • vtk资料整理
  • Linux arm64 PTE contiguous bit
  • linux可以直接用指针操作物理地址吗?
  • torch学习 自用
  • python类的内置属性
  • AI重塑SaaS:从被动工具到智能角色的技术演进路径
  • 【面试题】OOV(未登录词)问题如何解决?
  • Leetcode_202.快乐数_三种方法解决(普通方法解决,哈希表解决,循环链表的性质解决_快慢指针)
  • 简述:普瑞时空数据建库软件(国土变更建库)之一(变更预检查部分规则)
  • PyTorch 中训练语言模型过程
  • 利用 Java 爬虫获取淘宝商品详情 API 接口
  • 嵌入式学习day41-硬件(2)
  • ansible总结2
  • 代码随想录算法训练营第一天 | 704.二分查找 27. 移除元素 977.有序数组的平方
  • python中`__annotations__` 和 `inspect` 模块区别??
  • 两个子进程之间使用命名pipe
  • 从月薪5K到年薪60W!API自动化测试如何让你突破职业瓶颈
  • K8S 部署 NFS Dynamic Provisioning(动态存储供应)
  • 【STM32】STM32F103系列USB大坑 二
  • 具身智能让人形机器人 “活” 起来:懂语言、能感知、会行动,智能进化再提速
  • 使用langgraph创建工作流系列4:人机回环
  • 面试复习题-Flutter
  • 论文介绍:“DUSt3R”,让 3D 视觉从“繁琐”走向“直观”
  • Swift 解法详解:LeetCode 370《区间加法》