音视频开发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和frame
    AVPacket * 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 )  说明当前编码器默认使用的libx264
    if (aacencoder->id == AV_CODEC_ID_H264) {
        // 相关的参数可以参考libx264.c的 AVOption options
        // ultrafast all encode time:2270ms
        // medium all encode time:5815ms
        // veryslow all encode time:19836ms
        ret = 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); // 默认是high
        if(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); //  画质film
        if(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和frame
    pkt = 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分配buffer
    frame->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.计算出每一帧的数据大小-目的是为了分配一帧的大小 像素格式 * 宽 * 高 ,当前值是1382400
    frame_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/127034124

    printf("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 是显示时间
        // 设置pts
        frame->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 |   |

相关推荐
玩电脑的辣条哥13 分钟前
如何快速去除视频里面的水印字幕等信息?(内附工具)
ai·音视频·短视频
Susu_afmx17 分钟前
音乐伴奏提取?唱歌剪辑好用的音频人声分离软件!提取步骤很简单!
深度学习·新媒体运营·电脑·音视频·音频
星星月亮02 小时前
iOS Swift5 视频播放 能播放各种编码格式的视频的第三方库
ios·音视频
爱玩游戏的jason4 小时前
如何获取音频伴奏
音视频
写代码的小黑14 小时前
视频添加字幕
python·音视频
Leventure_轩先生14 小时前
[ALSA]从零开始,使用ALSA驱动播放一个音频
开发语言·c++·音视频
安步当歌17 小时前
【FFmpeg】av_read_frame函数
c语言·c++·ffmpeg·视频编解码·video-codec
智光工作室17 小时前
AudioLM音频生成模型
音视频·audiolm
zhqh10018 小时前
ffmpeg + opencv 把摄像头画面保存为mp4文件(Ubuntu24.04)
人工智能·opencv·ffmpeg
JoyceMill19 小时前
Android音频捕捉技术探索与实践
android·音视频