音视频开发30 FFmpeg 视频编码- 流程以及重要API,H264编码原理说明,该章节使用h264编码说明

一.H264编码原理

1 视频为什么需要进行编码压缩

一张为 720x480 的图像,用 YUV420P 的格式来表示,其大小为: 720*480*1.5 约等于
0.5MB
如果是 25 帧, 10 分钟的数据量 0.5M*10*60*25 = 7500MB -> 7GB
视频编码压缩的目的是降低视频数据大小,方便存储和传输

2 为什么压缩的原始数据一般采用YUV格式,而非GRB

历史原因:如果只有Y分量,就是黑白电视机呈现出来的图像,为了兼容电视机

体积减少:UV分量可以减少,但是并不能太影响观看体验,比如YUV420p,就是一组UV只占0.5个Y,因此YUV420p只有YUV444的一半大小。

3 视频压缩原理-数据冗余 了解

编码的目的是为了压缩,各种视频编码算法都是为了让视频体 积变得更小,减少对存储空间和传输带宽的占用。编码的核心 是去除冗余信息,通过以下几种冗余来达到压缩视频的目的:
3.1. 空间冗余 图像相邻像素之间有较强的相关性, 比如一帧 图像划分成多个 16x16 的块之后,相邻的块很多时候都有 比较明显的相似性。
3.2. 时间冗余: 视频序列的相邻前后帧图像之间内容相似,比 如帧率为 25fps 的视频中前后两帧图像相差只有 40ms,前 后两张图像的变化较小,相似性很高。
3.3 视觉冗余: 我们的眼睛对某些细节不敏感,对图像中高频 信息的敏感度小于低频信息的。可以去除图像中的一些高 频信息,人眼看起来跟不去除高频信息差别不大(有损压 缩)。
3.4. 编码冗余(信息熵冗余): 一幅图像中不同像素出现的概 率是不同的。对出现次数比较多的像素,用少的位数来编 码。对出现次数比较少的像素,用多的位数来编码,能够 减少编码的大小。比如哈夫曼编码。

4 图像帧的类型 (I帧、P帧和B帧)

I帧、P帧和B帧是视频压缩领域中的基础概念,用于提升视频压缩效率、视频质量和视频
恢复能力。
I帧(关键帧或帧内帧)仅由帧内预测的宏块组成。
P帧代表 预测帧 ,除帧内空域预测以外,它还可以通过时域预测来进行压缩。P帧通过
使用已经编码的帧进行运动估计。
B帧可以参考在其前后出现的帧,B帧中的 B代表双向 (Bi-Directional)

5 GOP(一组图像,Group of Pictures)和GOP长度

一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。
在视频编码序列中,GOP即Group of picture( 图像组 ),指两个IDR帧之间的距离。
GOP 长度越大 ,视频 压缩效率越高 ,但 视频质量和视频流恢复能力也越差 ,反之亦
然。
直播,如果是一秒25帧,一般gop设置为25, 50(一般是帧率的倍数).
如果不是直播流,B帧一般设置2帧连续B帧,以降低码率。

5.1 GOP之Closed GOP和Open GOP

Closed GOP Open GOP 常见于视频流中,并影响压缩效率、视频容错能力以及 ABR 流的切换能力。
顾名思义, Closed GOP GOP 外部的帧是封闭的。一个属于 Closed GOP 的帧只能参考这个 GOP 之内 的帧。
Open GOP Closed GOP 相反, Open GOP 内部的帧可以参考其他 GOP 中的帧。
在实际开发中,我们使用的Closed GOP的时机较多。
当我们在seek 的时候,假设这时候已经解码到10秒了,这时候,如果我们拖动播放器回到5秒的时候,是需要将 解码器中的 10s 的I 帧清除了。这是因为我们拖动到5秒的时候,有可能刚好是在p帧,p帧需要参考前面的I帧,实际上这时候会参考解码器中的I帧, 因此 每次 seek 都要清空解码器 ,ffmpeg 中对应的方法是  void avcodec_flush_buffers (AVCodecContext *avctx);

5.2 GOP间隔

GOP 越大,编码的 I 帧就会越少。相比而言, P 帧、 B 帧的压缩率更高,因此整个视频的
编码效率就会越高。但是 GOP 太大,也会导致 IDR 帧距离太大,点播场景时进行视频的
seek 操作就会不方便。

6 H264编码原理 了解

对于每一帧图像,是划分为一个个块进行编码,就是我们说的宏块。
宏块大小一般是 16x16 H264 VP8 ), 32x32 H265 VP9 ), 64x64 H265 VP9
AV1 ), 128x128 AV1

6.1 宏块扫描

对于一个 YUV 图像,可以把划分成一个个 16x16 的宏块(以 H264 为例), Y U V 分量的大 小分别是 16x16 8x8 8x8 。这里我们只对 Y 分量进行分析( U V 分量同理)。假设 Y 分量这 16x16 个像素就是一个个数字,采用“之”字方式扫描每一个像素值,则可以得到一个“像素串”。
压缩的目的是使得编码器当前的字符出现连续相同的字符,
比如 1,1,1,1,1,1,1,1 ,我们可以描述为 8 1.
数字越小越容易用更少的 bit 做压缩,比如一连串数字很小 (比如 0 1 2 1 0 )的“像素串” ,因为 0 在二进制中只 占 1 个位, 2 只占 2 个位即可。

6.2 帧内预测

1. 帧内预测 就是在当前编码图像内部已经编码完成的块中找到与将要编码的块 相邻的块。一般就是即将编码块的左边块、上边块、左上角块和右上角块, 通过将这些块与编码块相邻的像素经过多种不同的算法得到多个不同的预测 块。
2. 然后我们再用编码块减去每一个预测块得到一个个残差块。最后,我们取这 些算法得到的残差块中像素的绝对值加起来最小的块为预测块。而得到这个 预测块的算法为帧内预测模式

6.3 残缺块

H.264 整数 DCT 公式推导及蝶形算法分析 - Mr.Rico - 博客园 (cnblogs.com)
https://www.cnblogs.com/xkfz007/archive/2012/07/31/2616791.html

6.4 帧间预测

同理,帧间预测也是一样的。我们在前面已经编码完成的图像中,循环遍历每一个 块,将它作为预测块,用当前的编码块与这个块做差值,得到残差块,取残差块中 像素值的绝对值加起来最小的块为预测块,预测块所在的已经编码的图像称为参考 帧。预测块在参考帧中的坐标值 (x0, y0) 与编码块在编码帧中的坐标值 (x1, y1) 的差 值 (x0 - x1, y0 - y1) 称之为运动矢量。
而在参考帧中去寻找预测块的过程称之为运动搜索。事实上编码过程中真正的运动 搜索不是一个个块去遍历寻找的, 而是有快速的运动搜索算法的
通过预测得到的 残差块的像素值 相比编码块的像素值,去除了大部分空间冗余信息 和时间冗余信息,这样得到的像素值更小。如果把这个残差块做扫描得到的像素串 送去做行程编码,是不是相比直接拿编码块的像素串去做编码更有可能得到更大的 压缩率?

6.5 DCT 变换和量化

我们的目标不只是将像素值变小,而是希望能出现连续的 0 像素
这就需要利用我们人眼的视觉敏感性的特点了。我们刚才说了人眼对高频信息不太敏 感。因为人眼看到的效果可能差别不大,所以我们可以 去除一些高频信息 。这个就是 接下来我们要讨论的 DCT 变换和量化。
6.5.1 DCT变换
6.5.2 量化步长
由于人眼对高频信息不太敏感,如果我们通过一种手段去除掉大部分高频信息,也就是将大部分
高频信息置为 0,但又不太影响人的观感,是不是就可以达到我们最初的目标,即可以得到有一
连串 0 的像素串?这就涉及到量化操作了。
我们让变换块的系数都 同时除以一个值,这个值我们称之为量化步长 ,也就是 QStep(QStep 是 编码器内部的概念,用户一般使用量化参数 QP 这个值, QP 和 QStep 得到的结果就是量化后的 系数。QStep 越大,得到量化后的系数就会越小。同时,相同的 QStep 值,高频系数值相比低频 系数值更小,量化后就更容易变成 0。这样一来,将大部分高频系数变成 0。如下图所示:
6.5.3 量化步长

解码的时候,需要将 QStep 乘以量化后的系数得到变换系数,很明显这个变换系数和原始没有量 化的变换系数是不一样的,这个就是常说的有损编码。
而到底损失多少呢? 其由 QStep 来控制,QStep 越大,损失就越大。QStep 跟 QP 一一对应。从 编码器应用角度来看,QP 值越大,损失就越大 ,从而画面的清晰度就会越低。同时,QP 值越大 系数被量化成 0 的概率就越大,这样编码之后码流大小就会越小,压缩就会越高。
6.5.4 量化步长表

6.6  编码原理总结

为了能够在最后熵编码的时候压缩率更高,对于送到熵编码(以行程编码为例)的“像 素串”,包含的0越多,越能提高压缩率。 为了达到这个目标:
先通过帧内预测或者帧间预测去除空间冗余和时间冗余,从而得到一个像素值相 比编码块小很多的残差块。
然后再通过 DCT 变换将低频和高频信息分离开来得到变换块,然后再对变换块的 系数做量化。
由于高频系数通常比较小,很容易量化为 0,同时人眼对高频信息不太敏感,这样 就得到了一串含有很多个 0,大多数情况下是一串含有连续 0 的“像素串”,并且人 的观感还不会太明显。这样,最后熵编码就能把图像压缩成比较小的数据,以此 达到视频压缩的目的。
这即是视频编码的原理。
QP 值越大,损失就越大,QP值 取值范围1-51。默认值是23,值越小质量越好,0 表示无损,一般我们在 18~28 之间取值。QP越小,编码率越大,视频质量越好,在直播的时候需要的贷款越高。
问题:
直播的时候能不能 动态修改码率?可以的。
怎么修改?在开始的时候,qp值可以在pps中的字段 pic_init_qp_minus中设置.
----意味着,如果我们动态的修改码率,需要重新发送一次 pps
还可以在slice中的字段 slice_qp_delta中设置。
--这里只是猜想如果实做:是否意味着,如果我们不重新发送一次pps,则可以在 slice中设置?slice可以看成是 一张图片分成了很多个 slice。
还可以在 宏块 中的字段 mb_qp_delta 中设置。
--这里猜想如果实做,是否意味着,如果我们不重新发送一次pps,不在 slice中设置,最后在Macro Block中设置,Macro Block 可以看成是 slice 分成了很多个 Macro Block。
上述这些对应的ffmpeg中是那个参数呢? --todo

二 .H264 视频编码实战

1.FFmpeg流程

2. 目的

从本地读取YUV数据编码为h264格式的数据,然后再存⼊到本地,编码后的数据有带startcode

3. 函数说明:

avcodec_find_encoder_by_name :根据指定的编码器名称查找注册的编码器。
avcodec_alloc_context3 :为 AVCodecContext 分配内存。
avcodec_open2 :打开编解码器。
avcodec_send_frame :将 AVFrame ⾮压缩数据给编码器。
avcodec_receive_packet :获取到编码后的 AVPacket 数据。
av_frame_get_buffer : 为⾳频或视频数据分配新的 buffer 。在调⽤这个函数之前,必须在 AVFame 上设 置好以下属性:format( 视频为像素格式,⾳频为样本格式 ) nb_samples( 样本个数,针对⾳频 ) 、 channel_layout(通道类型,针对⾳频 ) width/height( 宽⾼,针对视频)。
av_frame_make_writable :确保 AVFrame 是可写的,尽可能避免数据的复制。
如果 AVFrame 不是是可写的,将分配新的 buffer 和复制数据。
av_image_fill_arrays : 存储⼀帧像素数据存储到 AVFrame 对应的 data buffer
编码出来的 h264 数据可以直接使⽤ ffplay 播放,也可以使⽤ VLC 播放。

av_image_get_buffer_size
int av_image_get_buffer_size ( enum AVPixelFormat pix_fmt, int width, int height, int align);
函数的作⽤是通过指定像素格式、图像宽、图像⾼来计算所需的内存⼤⼩
重点说明⼀个参数 align : 此参数是设定内存对⻬的对⻬数,也就是按多⼤的字节进⾏内存对⻬:
⽐如设置为 1 ,表示按 1 字节对⻬,那么得到的结果就是与实际的内存⼤⼩⼀样。 3
再⽐如设置为 4 ,表示按 4 字节对⻬。也就是内存的起始地址必须是 4 的整倍数。
av_image_alloc
av_image_alloc() 是这样定义的。此函数的功能是按照指定的宽、⾼、像素格式来分配图像内存。
int av_image_alloc ( uint8_t *pointers[ 4 ], int linesizes[ 4 ], int w, int h, enum AVPixelFormat pix_fmt,
int align);
pointers[4] :保存图像通道的地址。如果是 RGB ,则前三个指针分别指向 R,G,B 的内存地址。第四个指
针保留不⽤ linesizes[4] :保存图像每个通道的内存对⻬的步⻓,即⼀⾏的对⻬内存的宽度,此值⼤⼩
等于图像宽度。
w: 要申请内存的图像宽度。
h: 要申请内存的图像⾼度。
pix_fmt: 要申请内存的图像的像素格式。
align: ⽤于内存对⻬的值。
返回值:所申请的内存空间的总⼤⼩。如果是负值,表示申请失败。
av_image_fill_arrays
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4], const uint8_t *src, enum
AVPixelFormat pix_fmt, int width, int height, int align);
av_image_fill_arrays() 函数⾃身不具备内存申请的功能,此函数类似于格式化已经申请的内存,即通过
av_malloc() 函数申请的内存空间,或者 av_frame_get_buffer() 函数申请的内存空间
再者, av_image_fill_arrays() 中参数具体说明:
dst_data[4] [out] 对申请的内存格式化为三个通道后,分别保存其地址
dst_linesize[4]: [out] 格式化的内存的步⻓(即内存对⻬后的宽度 )
*src: [in]av_alloc() 函数申请的内存地址。
pix_fmt: [in] 申请 src 内存时的像素格式
width: [in] 申请 src 内存时指定的宽度
height: [in] 申请 scr 内存时指定的⾼度
align: [in] 申请 src 内存时指定的对⻬字节数。

4 代码

#include <iostream>/// 目的:从本地读取YUV数据编码为h264格式的数据,然后再存⼊到本地,编码后的数据有带startcode。extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/fifo.h"
#include "libavutil/audio_fifo.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include <libavutil/avutil.h>
#include<libavutil/error.h>
#include <libavutil/time.h>
#include <libavutil/imgutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
}using namespace std;#define ERROR_BUF \char errbuf[1024]; \av_strerror(ret, errbuf, sizeof (errbuf));#define CODE(func, code) \if (ret < 0) { \ERROR_BUF; \cout << #func << "error" << errbuf; \code; \}#define END(func) CODE(func, fataError(); return;)
#define RET(func) CODE(func, return ret;)
#define CONTINUE(func) CODE(func, continue;)
#define BREAK(func) CODE(func, break;)int64_t get_time()
{return av_gettime_relative() / 1000;  // 换算成毫秒
}static int encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,FILE *outfile)
{int ret;/* send the frame to the encoder */if (frame)printf("Send frame %lld \n", frame->pts);/* 通过查阅代码,使用x264进行编码时,具体缓存帧是在x264源码进行,* 不会增加avframe对应buffer的reference*/ret = avcodec_send_frame(enc_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending a frame for encoding\n");return -1;}while (ret >= 0){ret = avcodec_receive_packet(enc_ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}if(pkt->flags & AV_PKT_FLAG_KEY)printf("Write packet flags:%d pts:%lld dts:%lld  (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);if(!pkt->flags)printf("Write packet flags:%d pts:%lld dts:%lld (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);fwrite(pkt->data, 1, pkt->size, outfile);}return 0;
}int main()
{cout << "Hello World!" << endl;int ret = 0;//1.编码器相关const AVCodec * aacencoder = nullptr;//2.编码器上下文相关AVCodecContext * aacencodercontext = nullptr;//5 . 文件相关char * in_yuv_file = "D:/AllInformation/qtworkspacenew/08encoder_02_yuvToH264/yuv420p_1280x720.yuv";FILE * infile = nullptr;char * out_h264_file = "D:/AllInformation/qtworkspacenew/08encoder_02_yuvToH264/h264.h264";FILE * outfile = nullptr;// 6. 分配pkt和frameAVPacket * pkt = nullptr;AVFrame * frame = nullptr;//8.int frame_bytes_bufsize = 0;uint8_t *frame_bytes_buf = nullptr;//9.时间相关int64_t begin_time;int64_t end_time;int64_t all_begin_time;int64_t all_end_time;int64_t pts = 0;//1.编码器相关,找到编码器aacencoder = avcodec_find_encoder_by_name("libx264");
//    aacencoder = avcodec_find_encoder(AV_CODEC_ID_H264);if(!aacencoder){ret = -1;cout << " avcodec_find_encoder_by_name(libx264) error " << endl;goto yuvtoh264end;}//    avcodec_find_encoder(AV_CODEC_ID_H264);说明//    ffmpeg -codecs | findstr 264  我们看到 对于encoders 来说,第一个encoder也是libx264,这说明我们的ffmpeg是build 过 h264的,因此通过 avcodec_find_encoder(AV_CODEC_ID_H264) 得到的也是libx264//     DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10//             (decoders: h264 h264_qsv h264_cuvid )//             (encoders: libx264 libx264rgb h264_amf h264_mf h264_nvenc h264_qsv )//2.编码器上下文相关,找到编码器上下文aacencodercontext = avcodec_alloc_context3(aacencoder);if(!aacencodercontext){ret = -2;cout << " avcodec_alloc_context3(aacencoder) error " << endl;goto yuvtoh264end;}//3。设置编码器上下文参数,必须要设置的有 宽和高,视频的时间基准timebase,framerate帧率,I帧间隔gop_size,// 3.1 设置分辨率aacencodercontext->width = 1280;aacencodercontext->height = 720;// 3.2 设置time base ,AVCodecContext中的time_base 是以秒为单位,表示一张图片时间是 1/25 秒aacencodercontext->time_base = (AVRational){1, 25};// 3.3 设置framerate,framerate是帧率,表示一秒钟可以播放多少张画面。 framerate 和 time_base是 有关系aacencodercontext->framerate = (AVRational){25, 1};// 3.4 设置 I帧的间隔 ,也就是GOP间隔,一般是和 framerate值一样,或者是framerate的整数倍,// 3.4.1 gop_size越大,GOP 越大,编码的 I 帧就会越少。相比而言,P 帧、B 帧的压缩率更高,// 因此整个视频的编码效率就会越高。但是 GOP 太大,也会导致 IDR 帧距离太大,点播场景时进行视频的seek 操作就会不方便。aacencodercontext->gop_size = 25;   // I帧间隔//3.5 指定编码器允许使用的最大B帧数量,应该是一个GOP中可以有最多的B帧数量aacencodercontext->max_b_frames = 2; // 如果不想包含B帧则设置为0,一般在做直播的时候,max_b_frames这个值需要设置成0//3.6 指定输入数据的像素格式aacencodercontext->pix_fmt = AV_PIX_FMT_YUV420P;//3.7 设置bitrate,指定编码的比特率,即视频数据每秒使用的比特数,通常以bit/s为单位。该参数的大小直接影响到视频的质量和大小,过高的比特率可能会导致视频过大,而过低的比特率可能会导致视频质量下降。aacencodercontext->bit_rate = 3000000;
//    aacencodercontext->rc_max_rate =    3000000; //最大比特率
//    aacencodercontext->rc_min_rate =    3000000; //最小比特率
//    aacencodercontext->rc_buffer_size = 2000000; //解码器比特流缓冲区大小//如果开启多线程,那么 thread_count 和 thread_type参数要一起使用,而且thread_type的值需要为 FF_THREAD_FRAME//注意的是,thread 会有延迟,测试发现,如果 thread_cout设置为4,那么当有4个frame进来的时候,才会实做,因此有延迟,也是因为这个原因,我们在直播的时候,一般不会使用多线程
//    aacencodercontext->thread_count = 4;  // 开了多线程后也会导致帧输出延迟, 需要缓存thread_count帧后再编程。
//    aacencodercontext->thread_type = FF_THREAD_FRAME; // 并 设置为FF_THREAD_FRAME/* 对于H264 AV_CODEC_FLAG_GLOBAL_HEADER  设置则只包含I帧,此时sps pps需要从codec_ctx->extradata读取*  不设置AV_CODEC_FLAG_GLOBAL_HEADER,则表示每个I帧都带 sps pps sei* 因此在存储本地文件的时候,不能带这个值。但是在流媒体的时候不一定。*/
//    aacencodercontext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 存本地文件时不要去设置这个项//3.7 设置 x264 相关的 参数,那么我们怎么知道x264有哪些参数呢?以及参数的值是多少呢?简单的查,可以用看 libx264.c的 AVOption options,可以看到说明,以及一些默认值。//如果要查看详细的说明,这时候想到的是 使用ffmpeg -h encoder=libx264查看,发现详细的说明: 看注释是编译 x264,然后运行 x264 --fullhelp 可以看到,意思是,你要下载x264源码并build出来 x264.exe,然后执行 x264 --fullhelp 查看说明//这块比较复杂,会有一个blog说明,到底怎么去做。todo//3.7.1 注意,这里设置的x264的 通用相关参数。,设置的参数不管是 fdk自带的aac,还是 libx264,
//    我们通过命令 查看x264的ffmpeg的 ffmpeg -codecs | findstr x264
//     DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
//             (decoders: h264 h264_qsv h264_cuvid )
//             (encoders: libx264 libx264rgb h264_amf h264_mf h264_nvenc h264_qsv )  说明当前编码器默认使用的libx264if (aacencoder->id == AV_CODEC_ID_H264) {// 相关的参数可以参考libx264.c的 AVOption options// ultrafast all encode time:2270ms// medium all encode time:5815ms// veryslow all encode time:19836msret = av_opt_set(aacencodercontext->priv_data, "preset", "medium", 0);if(ret != 0) {printf("av_opt_set preset failed\n");}ret = av_opt_set(aacencodercontext->priv_data, "profile", "main", 0); // 默认是highif(ret != 0) {printf("av_opt_set profile failed\n");}ret = av_opt_set(aacencodercontext->priv_data, "tune","zerolatency",0); // 直播是才使用该设置,zerolatency中文是零延迟的意思
//        ret = av_opt_set(aacencodercontext->priv_data, "tune","film",0); //  画质filmif(ret != 0) {printf("av_opt_set tune failed\n");}}//4. 将编码器 和 编码器上下文 绑定ret = avcodec_open2(aacencodercontext,aacencoder,NULL);if(ret<0){printf("avcodec_open2  failed\n");goto yuvtoh264end;}//打印信息,看是否通过 thread 编码printf("thread_count: %d, thread_type:%d\n", aacencodercontext->thread_count, aacencodercontext->thread_type);//5. 打开输入和输出文件infile = fopen(in_yuv_file, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", in_yuv_file);goto yuvtoh264end;}outfile = fopen(out_h264_file, "wb");if (!outfile) {fprintf(stderr, "Could not open %s\n", out_h264_file);goto yuvtoh264end;}// 6. 分配pkt和framepkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "Could not allocate video frame\n");goto yuvtoh264end;}frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");goto yuvtoh264end;}// 7. 为frame分配bufferframe->format = aacencodercontext->pix_fmt;frame->width  = aacencodercontext->width;frame->height = aacencodercontext->height;ret = av_frame_get_buffer(frame, 0);if (ret < 0) {fprintf(stderr, "Could not allocate the video frame data\n");goto yuvtoh264end;}// 8.计算出每一帧的数据大小-目的是为了分配一帧的大小 像素格式 * 宽 * 高 ,当前值是1382400frame_bytes_bufsize = av_image_get_buffer_size((enum AVPixelFormat)frame->format, frame->width,frame->height, 1);//8.1 av_image_get_buffer_size方法说明 https://blog.csdn.net/e891377/article/details/127034124printf("frame_bytes %d\n", frame_bytes_bufsize);// 9. 一帧的空间。frame_bytes_buf = (uint8_t *)malloc(frame_bytes_bufsize);if(!frame_bytes_buf) {printf("frame_bytes_buf malloc failed\n");return 1;}begin_time = get_time();end_time = begin_time;all_begin_time = get_time();all_end_time = all_begin_time;pts = 0;printf("start enode\n");for (;;) {memset(frame_bytes_buf, 0, frame_bytes_bufsize);size_t read_bytes = fread(frame_bytes_buf, 1, frame_bytes_bufsize, infile);if(read_bytes <= 0) {printf("read file finish\n");break;}/* 确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份目的是新写入的数据和编码器保存的数据不能产生冲突*/int frame_is_writable = 1;if(av_frame_is_writable(frame) == 0) { // 这里只是用来测试printf("the frame can't write, buf:%p\n", frame->buf[0]);if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针printf("ref_count1(frame) = %d\n", av_buffer_get_ref_count(frame->buf[0]));frame_is_writable = 0;}ret = av_frame_make_writable(frame);if(frame_is_writable == 0) {  // 这里只是用来测试printf("av_frame_make_writable, buf:%p\n", frame->buf[0]);if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针printf("ref_count2(frame) = %d\n", av_buffer_get_ref_count(frame->buf[0]));}if(ret != 0) {printf("av_frame_make_writable failed, ret = %d\n", ret);break;}int need_size = av_image_fill_arrays(frame->data, frame->linesize, frame_bytes_buf,(enum AVPixelFormat)frame->format,frame->width, frame->height, 1);//这里是 图片,因此读取的数据和 声音的不一样,每次读取的大小都应该和预设的一样大if(need_size != frame_bytes_bufsize) {printf("av_image_fill_arrays failed, need_size:%d, frame_bytes_bufsize:%d\n",need_size, frame_bytes_bufsize);break;}pts += 40; //pts 是显示时间// 设置ptsframe->pts = pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率begin_time = get_time();ret = encode(aacencodercontext, frame, pkt, outfile);end_time = get_time();printf("encode time:%lldms\n", end_time - begin_time);if(ret < 0) {printf("encode failed\n");break;}}/* 冲刷编码器 */encode(aacencodercontext, NULL, pkt, outfile);all_end_time = get_time();printf("all encode time:%lldms\n", all_end_time - all_begin_time);yuvtoh264end:if(!frame_bytes_buf){free(frame_bytes_buf);}if(!infile){fclose(infile);}if(!outfile){fclose(outfile);}avcodec_free_context(&aacencodercontext);return 0;
}

5 ffmpeg 和 x264的参数对照   

ffmpeg 和 x264的参数对照   ,这里查看这个文档的时候不要使用查看模式,不要编辑模式,不然显示不全,可以按住 ctrl + 鼠标上下滑动就可以看清

x264

 

ffmpeg

 

说明

命令行

字段

命令行

字段

 

qp

qp_constant

cqp

 

cqp

固定量化因子。取值范围0到51。

经常取值在20-40之间,越小质量

越好,要求的码率越高。0表示无损压缩

max-keyint

i_keyint_max

g

gop_size

关键帧的最大间隔帧数

min-keyint

i_keyint_min

 

keyint_min

关键帧的最小间隔帧数

level

i_level_idc

 

level

取值范围10-51。

设置比特流的Level。默认40,即4.0。

用来告诉解码器需要支持的什么级别的

兼容性。只有在你知道自己在做什么的

时候才设置该参数。

frameref

i_frame_reference

 

refs

B和P帧向前预测参考的帧数。取值范

围1-16。

该值不影响解码的速度,但是越大解码

所需的内存越大。这个值在一般情况下

越大效果越好,但是超过6以后效果就

不明显了。

bframes

i_bframe

 

max_b_frames

最大B帧数.

b-adapt

b_bframe_adaptive

 

b_frame_strategy

如果为true,则自动决定什么时候需要

插入B帧,最高达到设置的最大B帧数。

如果设置为false,那么最大的B帧数被

使用。

b-pyramid

b_bframe_pyramid

 

FLAGS2(CODEC_FLAG2_BPYRAMID)

当设置B帧>=2时候,通过开启这个选

项可以获得质量的略微提高,但是没有

任何的速度损失。

 

b_deblocking_filter

 

FLAGS(CODEC_FLAG_LOOP_FILTER)

 

deblock

i_deblocking_filter_alphac0

 

deblockalpha

 

cabac

b_cabac

 

coder_type(FF_CODER_TYPE_AC)

使用CABAC熵编码技术,为引起轻微的

编码和解码的速度损失,但是可以提高

10%-15%的编码质量。

qmin

i_qp_min

 

qmin

最小的量化因子。取值范围1-51。建

议在10-30之间。

qmax

i_qp_max 

 

qmax

最大的量化因子。取值范围1-51。建

议在10-30之间。

qpstep

qp-step

i_qp_step

 

max_qdiff

最大的在帧与帧之间进行切变的量化

因子的变化量。

qcomp

f_qcompress

 

 

 

vbv-maxrate

i_vbv_max_bitrate

b

rc_max_rate

允许的最大码流,x264里面以kbps为

单位,ffmpeg以bps为单位

vbv-bufsize

i_vbv_buffer_size

bufsize

rc_buffer_size 

在指定vbv-maxrate的时候必须设置

该字段。

vbv-init

f_vbv_buffer_init

 

rc_initial_buffer_occupancy

初始的缓存占用量

qcomp

f_qcompress

 

qcompress

量化器压缩比率0-1.越小则比特率

越区域固定,但是越高越使量化器

参数越固定。

direct-pred

direct

i_direct_mv_pred

 

directpred

B帧里面采用的运动侦测的方式。

时间和空间方式大致PSNR和速度

是一致的。设置为auto质量会好一

些,但是速度会下降一些,设置为0

,质量和速度都会下降.可以选择

none, auto, temporal, spatial.

weightb

weight-b

b_weighted_bipred

 

FLAGS2(CODEC_FLAG2_WPRED)

当B帧设置>1时使用

partitions

analyse

inter

 

 

X264_ANALYSE_I4x4

X264_ANALYSE_I8x8

X264_ANALYSE_PSUB16x16

X264_ANALYSE_PSUB8x8

X264_ANALYSE_BSUB16x16

8x8dct

b_transform_8x8

 

FLAGS(CODEC_FLAG2_8X8DCT)

 

me

i_me_method

 

me_method

运动侦测的方式

ME_EPZS

ME_HEX

ME_UMH

ME_FULL

ME_ESA

me-range

merange

i_me_range

 

me_range

运动侦测的半径

subq

subme

i_subpel_refine

 

me_subpel_quality

这个参数控制在运动估算过程中质

量和速度的权衡。Subq=5可以压

缩>10%于subq=1。1-7

mixed-refs

b_mixed_references

 

FLAGS2(CODEC_FLAG2_MIXED_REFS)

允许8*8,16*8运动块独立地选择

参考帧,如果disable,则所有的宏

块必须参考同一帧。

需要frameref > 1

brdo

b_bframe_rdo

 

FLAGS2(CODEC_FLAG2_BRDO)

需要subq>6

bime

b_bidir_me

 

bidir_refine

取值范围:true,false.这个值在没

有B帧的时候失效。在双向预测宏块中

双向运动矢量使用。

trellis

i_trellis

 

trellis

 

deadzone-intra

i_luma_deadzone

 

没有对应值

 

deadzone-inter

i_luma_deadzone

 

没有对应值

 

fast-pskip

b_fast_pskip

 

FLAGS(CODEC_FLAG2_FASTPSKIP)

在P帧内执行早期快速跳跃探测。

这个经常在没有任何损失的前提

下提高了速度。

dct-decimate

b_dct_decimate

 

没有对应值

 

nr

i_noise_reduction

 

noise_reduction

0意味着关闭,对于噪声很大的

内容你需要打开。

范围:0-100000

interlaced

b_interlaced

 

没有对应值

 

global-header

b_repeat_headers

 

FLAGS(CODEC_FLAG_GLOBAL_HEADER)

使得SPS和PPS只在流的开始处

产生一次。有些播放器,如SONY

的PSP需要开启此参数。默认的设

置使得SPS和PPS在每一个IDR帧

开始出都进行重复。

aud

b_aud

 

FLAGS2(CODEC_FLAG2_AUD)

 

threads

i_threads

 

thread_count

将帧切分成块,由不同的线程进行

分别编码。0-4。

0 for auto

rc-eq

psz_rc_eq

 

rc_eq

 

--no-psnr

b_psnr

 

FLAGS(CODEC_FLAG_PSNR)

是否开启PSNR.

--no-ssim

b_ssim

 

没有对应值

 

--progress

b_progress

 

没有对应值

 

--bitrate

i_bitrate

b

bit_rate

编码输出的比特率,并启用

ABR(Average Birtate 模式(i_rc_method),

qblur

f_qblur

 

qblur

 

 

f_complexity_blur

 

complexityblur

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Open3D(C++) 删除点云中重复的点

目录 一、算法原理1、重叠点2、主要函数二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 1、重叠点 原始点云克隆一份   构造重叠区域   合并点云获得重叠点 2、主要…

2024 Parallels Desktop for Mac 功能介绍

Parallels Desktop的简介 Parallels Desktop是一款由Parallels公司开发的桌面虚拟化软件&#xff0c;它允许用户在Mac上运行Windows和其他操作系统。通过强大的技术支持&#xff0c;用户无需重新启动电脑即可在Mac上运行Windows应用程序&#xff0c;实现了真正的无缝切换。 二…

Python变量的命名规则与赋值方式

第二章&#xff1a;Python 基础语法 第一节&#xff1a;变量的命名规则与赋值方式 2.1.1 引言 在编程中&#xff0c;变量是存储数据的基本单元。变量的命名和赋值是编程语言中表达和操作数据的基础。了解和遵循变量命名规则对于编写清晰、可维护的代码至关重要。 2.1.2 变量…

基于Spring Boot的药房信息管理系统

1 项目介绍 1.1 研究的背景及意义 随着社会的飞速进步和药房行业竞争的白热化&#xff0c;传统的手工管理模式已难以适应药房信息管理的现代化需求。在计算机科学技术日臻完善的背景下&#xff0c;药房信息管理者们日益认识到运用计算机技术进行信息管理的迫切性和重要性。计…

【PL理论深化】(13) 变量与环境:文法结构 | 真假表达式:isZero E | let 表达式叠放 | 定义的规则 | 条件语句的使用

&#x1f4ac; 写在前面&#xff1a;从现在开始&#xff0c;让我们正式设计和实现编程语言。首先&#xff0c;让我们扩展在之前定义的整数表达式语言&#xff0c;以便可以使用变量和条件表达式。 目录 0x00 文法结构 0x01 真假表达式&#xff1a;isZero E 0x02 let 表达式叠…

Vite: 高阶特性 Pure ESM

概述 ESM 已经逐步得到各大浏览器厂商以及 Node.js 的原生支持&#xff0c;正在成为主流前端模块化方案。 而 Vite 本身就是借助浏览器原生的 ESM 解析能力( type“module” )实现了开发阶段的 no-bundle &#xff0c;即不用打包也可以构建 Web 应用。不过我们对于原生 ESM 的…

【2024-热-办公软件】ONLYOFFICE8.1版本桌面编辑器测评

在今日快速发展的数字化办公环境中&#xff0c;选择一个功能全面且高效的办公软件是至关重要的。最近&#xff0c;我有幸体验了ONLYOFFICE 8.1版本的桌面编辑器&#xff0c;这款软件不仅提供了强大的编辑功能&#xff0c;还拥有众多改进&#xff0c;让办公更加流畅和高效。在本…

【Linux】进程优先级 | 环境变量

目录 Ⅰ. 进程优先级&#xff08;Process Priority&#xff09; 1. 什么是进程优先级&#xff1f; 2. 查看系统进程 3. 修改进程优先级 4.优先级调度原理 Ⅱ. 进程的切换&#xff08;Process Switch&#xff09; 1. 竞争与独立 2. 并行与并发 3. 进程抢占 4.实现切换…

Python技术笔记汇总(含语法、工具库、数科、爬虫等)

对Python学习方法及入门、语法、数据处理、数据可视化、空间地理信息、爬虫、自动化办公和数据科学的相关内容可以归纳如下&#xff1a; 一、Python学习方法 分解自己的学习目标&#xff1a;可以将学习目标分基础知识&#xff0c;进阶知识&#xff0c;高级应用&#xff0c;实…

医院管理系统带万字文档医院预约挂号管理系统基于spingboot和vue的前后端分离java项目java课程设计java毕业设计

文章目录 仓库管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 仓库管理系统 一、项目演示 医院管理系统 二、项目介绍 基于springbootvue的前后端分离医院管…

LabVIEW遇到无法控制国外设备时怎么办

当使用LabVIEW遇到无法控制国外产品的问题时&#xff0c;解决此类问题需要系统化的分析和处理方法。以下是详细的解决思路和具体办法&#xff0c;以及不同方法的分析和比较&#xff0c;包括寻求代理、国外技术支持、国内用过的人请教等内容。 1. 了解产品的通信接口和协议 思路…

如何利用python画出AHP-SWOT的战略四边形(四象限图)

在企业或产业发展的相关论文分析中&#xff0c;常用到AHP-SWOT法进行定量分析&#xff0c;形成判断矩阵后&#xff0c;如何构造整洁的战略四边形是分析的最后一个环节&#xff0c;本文现将相关代码发布如下&#xff1a; import mpl_toolkits.axisartist as axisartist import …

基于FPGA的温湿度检测

初始化部分就不过多赘述&#xff0c;我会给出对应的文件&#xff0c;我只说明这部分里面涉及到使用的代码部分 1、数据的读取和校验 数据的读取和检验代码如下 always (posedge clk_us)if (data_temp[7:0] data_temp[39:32] data_temp[31:24] data_temp[23:16] data_te…

centos7 xtrabackup mysql 基本测试(5)mysql 建立 测试 数据库及内容

centos7 xtrabackup mysql 基本测试&#xff08;5&#xff09;mysql 建立 测试 数据库及内容 登录 mysql -u etc -p 1234aA~1创建数据库 名字是company show databases ; create database company;在 company里面 创建表employee use company; DROP TABLE IF EXISTS employ…

vue+canvas画布实现网页签名效果

1、签名自定义组件代码示例&#xff1a; qianMing.vue <template><!-- 容器&#xff0c;包含画布和清除按钮 --><div class"signature-pad-container"><!-- 画布元素&#xff0c;用于用户签名 --><canvasref"canvas" <!--…

C++旋转点坐标计算

/// 获取A点绕B点旋转P度后的新坐标/// </summary>/// <param name"Angle">角度</param>/// <param name"CirPoint">圆心坐标</param>/// <param name"MovePoint">移动点的坐标</param>/// <param…

成为画图大师,用图表讲故事

这些问题你是否遇到过: 项目总结会上&#xff0c;如果用数据呈现你做的价值&#xff1f; 完善详尽的数据分析得出了让人信服的结论&#xff0c;如何呈现在BOSS面前? 我们要的不是数据&#xff0c;而是数据告诉我们的事实 数据很重要&#xff0c;但只是原料&#xff0c;所以…

微服务之服务保护策略【持续更新】

文章目录 线程隔离一、滑动窗口算法二、漏桶算法三、令牌桶算法 面试题1、Sentinel 限流和Gateway限流的区别 线程隔离 两种实现方式 线程池隔离&#xff08;Hystix隔离&#xff09;&#xff0c;每个被隔离的业务都要创建一个独立的线程池&#xff0c;线程过多会带来额外的CPU…

大模型笔记1: Longformer环境配置

论文: https://arxiv.org/abs/2004.05150 首先保证电脑上配置了git. git环境配置: https://blog.csdn.net/Andone_hsx/article/details/87937329 3.1、找到git安装路径中bin的位置&#xff0c;如&#xff1a;D:\Program Files\Git\bin 找到git安装路径中git-core的…

Windows应急响应靶机 - Web3

一、靶机介绍 应急响应靶机训练-Web3 前景需要&#xff1a;小苕在省护值守中&#xff0c;在灵机一动情况下把设备停掉了&#xff0c;甲方问&#xff1a;为什么要停设备&#xff1f;小苕说&#xff1a;我第六感告诉我&#xff0c;这机器可能被黑了。 这是他的服务器&#xff…