【音视频】FFmpeg 硬件(NVDIA)编码H264

发布于:2025-06-03 ⋅ 阅读:(25) ⋅ 点赞:(0)

FFmpeg 与x264的关系

ffmpeg软编码是使⽤x264开源项⽬,也就是说ffmpeg软编码H264最终是调⽤了x264开源项⽬,所以我们要先理解ffmpeg和x264的调⽤关系,这⾥我们主要关注x264_init。对于x264的参数都在

ffmpeg\libavcodec \libx264.c
x264\common\base.c
  • ffmpeg中采⽤H264,H265标准编码时,可能需要设置profile、preset和tune。我们先分析这三个参数的意义。然后再继续分析码率、帧率等的设置。

  • ch264编码原理复杂,参数较多。很多编码为了实现框架以及⽅便调⽤者使⽤,都封装了⼏种现有的编码模型,只需要根据编码速度的要求和视频质量的要求选择模型,并修改部分视频参数即可编码,x264也不例外。我们只需要通过preset和tune即可设置选择现有的编码模型。

preset值简单释义

preset值 释义
ultrafast 极快
superfast 超快
veryfast 非常快
faster 更快
fast
medium
slow
slower 更慢
veryslow 非常慢
placebo 超慢

可以通过ffmpeg命令行来查询更多libx264的编码器设置

ffmpeg -h encoder="libx264" >libx264.log

在这里插入图片描述

preset质量分析

通常,⽐特率越⾼,编码所需的时间越多。下⾯图显示了同⼀1080p数据源通过设置不同preset的编码时间:

在这里插入图片描述

  • 从中到慢,所需时间增加了约40%。 相反,变慢会导致所需时间增加⼤约100%(将花费两倍的时间)。
  • 与中等速度相⽐,veryslow需要原始编码时间的280%,⽽质量较慢的速度只有很少的改进。
  • 使⽤快速可节省⼤约10%的编码时间,快则可节省25%。 超快将节省55%的成本,但质量要低得多。

ffmpe的设置

ret = av_opt_set(enc_ctx->priv_data, "preset", "veryfast", 0);
  • 在avcodec_open2调⽤后,最终调⽤ffmpeg的libx264.c的X264_init,然后调⽤x264开源项⽬的x264_param_default_preset函数。

preset对应的源码分析

  • x264_param_default_preset通过x264_param_apply_preset设置编码参数。
static int x264_param_apply_preset( x264_param_t *param, const char *preset )
{
    char *end;
    int i = strtol( preset, &end, 10 );
    if( *end == 0 && i >= 0 && i < sizeof(x264_preset_names)/sizeof(*x264_preset_names)-1 )
        preset = x264_preset_names[i];
    if( !strcasecmp( preset, "ultrafast" ) )
    {
        param->i_frame_reference = 1;
        param->i_scenecut_threshold = 0;
        param->b_deblocking_filter = 0;
        param->b_cabac = 0;
        param->i_bframe = 0;
        param->analyse.intra = 0;
        param->analyse.inter = 0;
        param->analyse.b_transform_8x8 = 0;
        param->analyse.i_me_method = X264_ME_DIA;
        param->analyse.i_subpel_refine = 0;
        param->rc.i_aq_mode = 0;
        param->analyse.b_mixed_references = 0;
        param->analyse.i_trellis = 0;
        param->i_bframe_adaptive = X264_B_ADAPT_NONE;
        param->rc.b_mb_tree = 0;
        param->analyse.i_weighted_pred = X264_WEIGHTP_NONE;
        param->analyse.b_weighted_bipred = 0;
        param->rc.i_lookahead = 0;
    }
    else if( !strcasecmp( preset, "superfast" ) )
    {
        param->analyse.inter = X264_ANALYSE_I8x8|X264_ANALYSE_I4x4;
        param->analyse.i_me_method = X264_ME_DIA;
        param->analyse.i_subpel_refine = 1;
        param->i_frame_reference = 1;
        param->analyse.b_mixed_references = 0;
        param->analyse.i_trellis = 0;
        param->rc.b_mb_tree = 0;
        param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
        param->rc.i_lookahead = 0;
    }
    else if( !strcasecmp( preset, "veryfast" ) )
    {
        param->analyse.i_me_method = X264_ME_HEX;
        param->analyse.i_subpel_refine = 2;
        param->i_frame_reference = 1;
        param->analyse.b_mixed_references = 0;
        param->analyse.i_trellis = 0;
        param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
        param->rc.i_lookahead = 10;
    }
    else if( !strcasecmp( preset, "faster" ) )
    {
        param->analyse.b_mixed_references = 0;
        param->i_frame_reference = 2;
        param->analyse.i_subpel_refine = 4;
        param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
        param->rc.i_lookahead = 20;
    }
    else if( !strcasecmp( preset, "fast" ) )
    {
        param->i_frame_reference = 2;
        param->analyse.i_subpel_refine = 6;
        param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
        param->rc.i_lookahead = 30;
    }
    else if( !strcasecmp( preset, "medium" ) )
    {
        /* Default is medium
         *默认参考 set_param_default();
         */
    }
    else if( !strcasecmp( preset, "slow" ) )
    {
        param->analyse.i_me_method = X264_ME_UMH;
        param->analyse.i_subpel_refine = 8;
        param->i_frame_reference = 5;
        param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
        param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
        param->rc.i_lookahead = 50;
    }
    else if( !strcasecmp( preset, "slower" ) )
    {
        param->analyse.i_me_method = X264_ME_UMH;
        param->analyse.i_subpel_refine = 9;
        param->i_frame_reference = 8;
        param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
        param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
        param->analyse.inter |= X264_ANALYSE_PSUB8x8;
        param->analyse.i_trellis = 2;
        param->rc.i_lookahead = 60;
    }
    else if( !strcasecmp( preset, "veryslow" ) )
    {
        param->analyse.i_me_method = X264_ME_UMH;
        param->analyse.i_subpel_refine = 10;
        param->analyse.i_me_range = 24;
        param->i_frame_reference = 16;
        param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
        param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
        param->analyse.inter |= X264_ANALYSE_PSUB8x8;
        param->analyse.i_trellis = 2;
        param->i_bframe = 8;
        param->rc.i_lookahead = 60;
    }
    else if( !strcasecmp( preset, "placebo" ) )
    {
        param->analyse.i_me_method = X264_ME_TESA;
        param->analyse.i_subpel_refine = 11;
        param->analyse.i_me_range = 24;
        param->i_frame_reference = 16;
        param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
        param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
        param->analyse.inter |= X264_ANALYSE_PSUB8x8;
        param->analyse.b_fast_pskip = 0;
        param->analyse.i_trellis = 2;
        param->i_bframe = 16;
        param->rc.i_lookahead = 60;
    }
    else
    {
        x264_log( NULL, X264_LOG_ERROR, "invalid preset '%s'\n", preset );
        return -1;
    }
    return 0;
}

从这⾥⼤体可以看出来:

  1. b帧数量降低编码速度;
  2. 参考帧越多编码速度越慢
  3. 运动估计算法越复杂,编码越耗时。
  4. 越slow的编码算法,使⽤的编码算法越复杂。

⽐如:选⽤ultrafast会关闭b帧,但是同等质量也会增⼤码率。veryfast 之后会启⽤各种保护质量的算法, 很⼤程度上降低编码速度。

ultrafast->veryslow:

  1. 编码复杂度增加;
  2. 编码质量增加;
  3. 编码时⻓增加;
  4. 编码cpu占⽐增肌;
    也就是说,如果要通过降低码率但⼜要保证编码质量,则需要提⾼编码的复杂度,但会占⽤更多的cpu时
    间。

tune

tune值简单释义

tune值 释义
film 电影、真人类型
animation 动画
grain 需要保留大量的grain时用
stillimage 静态图像编码时使用
psnr 为提高psnr做了优化的参数
ssim 为提高ssim做了优化的参数
fastdecode 可以快速解码的参数
zerolatency 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码

主要配合视频类型和视觉优化的参数况。如果视频的内容符合其中⼀个可⽤的调整值⼜或者有其中需要,则可以使⽤此选项,否则建议不使⽤(如tune grain是为⾼⽐特率的编码⽽设计的)。

ffmpe的设置

ret = av_opt_set(enc_ctx->priv_data, "tune","zerolatency",0);

profile

H.264有四种画质级别,分别是baseline, extended, main, high:

profile取值 含义 释义
baseline 基本画质 支持I/P 帧,只支持无交错(Progressive)和CAVLC
extended 进阶画质 支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC(用的少)
main 主流画质 提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),也支持CAVLC 和CABAC 的支持
high 高级画质 在main Profile 的基础上增加了8x8内部预测、自定义量化、无损视频编码和更多的YUV 格式

在这里插入图片描述

Nvdia解码H264实现

前言

原代码使用的是nvdia进行解码,但是我的显卡是AMD的,因此不支持这个解码器,因此该代码转载别人写的代码,具体的测试结果仅供参考!

编码器 预设 编码时间 比特率 线程数 线程类型
libx264 ultrafast 306ms 1027.18kbps 未指定 未指定
h264_nvenc ultrafast 278ms 906.10kbps 未指定 未指定
libx264 superfast 381ms 1084.67kbps 未指定 未指定
h264_nvenc superfast 226ms 906.10kbps 未指定 未指定
libx264 faster 800ms 928.86kbps 未指定 未指定
h264_nvenc faster 219ms 906.10kbps 未指定 未指定
h264_nvenc fast 223ms 923.33kbps 未指定 未指定
libx264 medium 1075ms 928.75kbps 未指定 未指定
h264_nvenc medium 279ms 906.10kbps 1 3
libx264 slow 1852ms 928.24kbps 0 0
h264_nvenc slow 456ms 921.65kbps 1 3
libx264 slower 3017ms 925.25kbps 未指定 未指定
h264_nvenc slower 276ms 906.10kbps 1 3
libx264 veryslow 4748ms 920.88kbps 未指定 未指定
h264_nvenc veryslow 289ms 906.10kbps 未指定 未指定
libx264 placebo 17834ms 923.30kbps 未指定 未指定
h264_nvenc placebo 282ms 906.10kbps 1 3

除了查看nvdia的SDK代码,还可以通过ffmpeg命令行查询对应解码器可用的参数设置

ffmpeg -h encoder="h264_nvenc" > h264_nvenc.log

在这里插入图片描述

实现流程

准备文件
  • 在build路径下准备yuv文件

在这里插入图片描述

  • 添加对应参数,读取yuv文件,编码为h264,
  • 后面的1表示生成对应的h264文件,如果为0表示不生成

在这里插入图片描述

设置预设值
  • 我们对比了libx264和h264_nvenc不同预设的编码时间
  • 对于部分libx264的预设,h264_nvenc不支持,具体的预设还需要查询文档

ultrafast

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "ultrafast", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "ultrafast", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "ultrafast", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "ultrafast", write_enable); // nvenc不支持

superfast

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "superfast", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "superfast", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "superfast", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "superfast", write_enable);  // nvenc不支持

faster

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "faster", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "faster", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "faster", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "faster", write_enable);   // nvenc不支持

fast

printf("\n\n--fast--------\n");
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "fast", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "fast", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "fast", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "fast", write_enable);

medium

printf("\n\n--medium--------\n");
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "medium", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "medium", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "medium", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "medium", write_enable);

slow

printf("\n\n--slow--------\n");
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slow", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "slow", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slow", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "slow", write_enable);

slower

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slower", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "slower", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slower", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "slower", write_enable);   // nvenc不支持

veryslow

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "veryslow", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "veryslow", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "veryslow", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "veryslow", write_enable);  // nvenc不支持

placebo

sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "placebo", "libx264");
video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "placebo", write_enable);
sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "placebo", "h264_nvenc");
video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "placebo", write_enable);// nvenc不支持
video_encode_preset_test函数讲解
  • video_encode_preset_test函数主要是查找对应的编码器(libx264h264_nvenc),然后将预设的参数对编码器进行设置
  • 其中视频设置为1280x720分辨率,帧率为25fps
/* 查找指定的编码器 */
codec = avcodec_find_encoder_by_name(codec_name);
if (!codec) {
	fprintf(stderr, "Codec '%s' not found\n", codec_name);
	exit(1);
}

codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
	fprintf(stderr, "Could not allocate video codec context\n");
	exit(1);
}

VideoConfgig *video_config = video_encode_new_default_config(codec_name, 1280, 720, 25);
video_config->preset = alloc_and_copy_string(preset);

video_encode_config(video_config, codec_ctx);  // 设置

ret = avcodec_open2(codec_ctx, codec,NULL);
if (ret < 0) {
	fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
	exit(1);
}
  • video_encode_new_default_config函数主要就是设置视频的参数,如分辨率,fps,时间基等
  • 上面说到,nvenc的源码内部将时间基设置为视频的fps,因此这里的时间基设置为1/fps
VideoConfgig *video_encode_new_default_config(const char *codec_name, int width, int height, int fps)
{
    VideoConfgig *video_config = malloc(sizeof(VideoConfgig));
    if(!video_config)
    {
        printf("malloc VideoConfgig failed\n");
        return NULL;
    }
    memset(video_config, 0, sizeof(VideoConfgig));
    video_config->gop_size = fps;
    video_config->width = width;
    video_config->height = height;
    if(strcmp(codec_name, "h264_nvenc") == 0)
        video_config->time_base = (AVRational){1, fps};   // 注意匹配, 为什么这个参数影响硬件编码呢?因为nvenc帧率设置实际是用的这个代码
    else
        video_config->time_base = (AVRational){1, 1000};  // 其他的我们用timebase 1000为单位
    video_config->framerate = (AVRational){fps, 1};
    video_config->pix_fmt = AV_PIX_FMT_YUV420P;
    video_config->max_b_frames = 0;
    video_config->bit_rate = 1*1024*1024;// ffmpeg的单位就是bps
    return video_config;
}
  • 具体的VideoConfigig结构体如下,主要是编码器和视频的一些参数配置
// H264编码设置的参数
// 宽高
// 帧率
// gop
// b帧数量
typedef struct video_config
{
    int width;  // 宽
    int height; // 高
    AVRational framerate; // 帧率
    int gop_size;  // I帧间隔
    int max_b_frames;
    enum AVPixelFormat pix_fmt; // 像素格式
    AVRational time_base; // timebase
    char *preset;
    char *tune;
    char *profile;
    int64_t rc_max_rate;  // 最大码率
    int64_t rc_min_rate;  // 最低码率
    int rc_buffer_size; // decoder bitstream buffer size
    int qmin; // minimum quantizer
    int qmax; // maximum quantizer
    int flags; // AV_CODEC_FLAG_*.比如AV_CODEC_FLAG_GLOBAL_HEADER
    int64_t bit_rate;
}VideoConfgig;
  • 打开输入文件,和输出文件,进行读入yuv和编码后写入H264
// 打开输入和输出文件
infile = fopen(in_yuv_file, "rb");
if (!infile) {
	fprintf(stderr, "Could not open %s\n", in_yuv_file);
	exit(1);
}
outfile = fopen(out_h264_file, "wb");
if (!outfile) {
	fprintf(stderr, "Could not open %s\n", out_h264_file);
	exit(1);
}
  • 分配对应的AVFrameAVPacket内存
// 分配pkt和frame
pkt = av_packet_alloc();
if (!pkt) {
	fprintf(stderr, "Could not allocate video frame\n");
	exit(1);
}
frame = av_frame_alloc();
if (!frame) {
	fprintf(stderr, "Could not allocate video frame\n");
	exit(1);
}

// 为frame分配buffer
frame->format = codec_ctx->pix_fmt;
frame->width  = codec_ctx->width;
frame->height = codec_ctx->height;
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
	fprintf(stderr, "Could not allocate the video frame data\n");
	exit(1);
}
// 计算出每一帧的数据 像素格式 * 宽 * 高
// 1382400
int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,
										   frame->height, 1);
//    printf("frame_bytes %d\n", frame_bytes);
uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
if(!yuv_buf) {
	printf("yuv_buf malloc failed\n");
	return 1;
}
  • 编码的pts,按照指定的时间基进行累加即可
  • 因为libx264h264_nvenc的时间基不同,这里做了不同的处理
  • 这里的软件编码和硬件编码的流程一样,不需要而外分配硬件上下文,但是软件解码和硬件解码的流程稍有不同
int64_t begin_time = get_time();        // 起始时间
int64_t all_begin_time = get_time();
int64_t all_end_time = all_begin_time;
int64_t cur_pts = 0;

for (;;) {
	memset(yuv_buf, 0, frame_bytes);
	size_t read_bytes = fread(yuv_buf, 1, frame_bytes, infile);
	if(read_bytes <= 0) {
//            printf("read file finish\n");
		break;
	}
	frame_count++; // 帧数加一
	/* 确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份
		目的是新写入的数据和编码器保存的数据不能产生冲突
	*/
	ret = av_frame_make_writable(frame);
	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, yuv_buf,
										 frame->format,
										 frame->width, frame->height, 1);
	if(need_size != frame_bytes) {
		printf("av_image_fill_arrays failed, need_size:%d, frame_bytes:%d\n",
			   need_size, frame_bytes);
		break;
	}
	if(strcmp(codec_name, "h264_nvenc") == 0)
		cur_pts += 1;  // 25/25
	else
		cur_pts += 40; // 1000/25

	// 设置pts
	frame->pts = cur_pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
	begin_time = get_time();
	ret = encode(codec_ctx, frame, pkt, outfile, &bytes_count, write_enable);
	if(ret < 0) {
		printf("encode failed\n");
		break;
	}
}
冲刷编码器
  • 编码的最后,需要传入NULL到编码器中,将编码器缓存的剩余帧刷出来
  • 最后需要关闭对应的文件和释放对应申请的内存
/* 冲刷编码器 */
encode(codec_ctx, NULL, pkt, outfile, &bytes_count, write_enable);
all_end_time = get_time();
float bps = (bytes_count * 8.0) /(frame_count/25.0)/1024; // 按25帧每秒计算
printf("%s %s encode time:%lldms\n", codec_name, preset, all_end_time - all_begin_time);
printf("%s %s encode bps:%0.2fkbps\n", codec_name, preset, bps);
// 关闭文件
fclose(infile);
fclose(outfile);

// 释放内存
if(yuv_buf) {
	free(yuv_buf);
}

av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
if(video_config) {
	if(video_config->preset)
		free(video_config->preset);
	free(video_config);
}

完整代码


/**
* @projectName   08-09-encode_video_libx264_nvenc
* @brief         主要测试
*               (1)libx264对于不同preset的编码质量和编码耗时;
*               (2)h264_nvenc硬件编码不同preset的编码质量和编码耗时;
*               (3)libx264和h264_nvenc的编码耗时对比
* @author        Liao Qingfu
* @date          2020-04-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>
#include <libavutil/time.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

/*
一定要注意编码器timebase的设置,它和码率的计算有对应的关系,因为他需要这个时间关系

*/

// H264编码设置的参数
// 宽高
// 帧率
// gop
// b帧数量
typedef struct video_config
{
    int width;  // 宽
    int height; // 高
    AVRational framerate; // 帧率
    int gop_size;  // I帧间隔
    int max_b_frames;
    enum AVPixelFormat pix_fmt; // 像素格式
    AVRational time_base; // timebase
    char *preset;
    char *tune;
    char *profile;
    int64_t rc_max_rate;  // 最大码率
    int64_t rc_min_rate;  // 最低码率
    int rc_buffer_size; // decoder bitstream buffer size
    int qmin; // minimum quantizer
    int qmax; // maximum quantizer
    int flags; // AV_CODEC_FLAG_*.比如AV_CODEC_FLAG_GLOBAL_HEADER
    int64_t bit_rate;
}VideoConfgig;

int64_t get_time()
{
    return av_gettime_relative() / 1000;  // 换算成毫秒
}
VideoConfgig *video_encode_new_default_config(const char *codec_name, int width, int height, int fps)
{
    VideoConfgig *video_config = malloc(sizeof(VideoConfgig));
    if(!video_config)
    {
        printf("malloc VideoConfgig failed\n");
        return NULL;
    }
    memset(video_config, 0, sizeof(VideoConfgig));
    video_config->gop_size = fps;
    video_config->width = width;
    video_config->height = height;
    if(strcmp(codec_name, "h264_nvenc") == 0)
        video_config->time_base = (AVRational){1, fps};   // 注意匹配, 为什么这个参数影响硬件编码呢?因为nvenc帧率设置实际是用的这个代码
    else
        video_config->time_base = (AVRational){1, 1000};  // 其他的我们用timebase 1000为单位
    video_config->framerate = (AVRational){fps, 1};
    video_config->pix_fmt = AV_PIX_FMT_YUV420P;
    video_config->max_b_frames = 0;
    video_config->bit_rate = 1*1024*1024;// ffmpeg的单位就是bps
    return video_config;
}

// 配置参数
static int video_encode_config(VideoConfgig *video_config, AVCodecContext *enc_ctx)
{
    int ret = 0;
    if(!video_config || !enc_ctx) {
        return -1;
    }
    /* 设置分辨率*/
    enc_ctx->width = video_config->width;
    enc_ctx->height = video_config->height;
    /* 设置time base */

    enc_ctx->time_base = video_config->time_base;
    enc_ctx->framerate = video_config->framerate;
    /* 设置I帧间隔
     * 如果frame->pict_type设置为AV_PICTURE_TYPE_I, 则忽略gop_size的设置,一直当做I帧进行编码
     */
    enc_ctx->gop_size = video_config->gop_size;
    enc_ctx->max_b_frames = video_config->max_b_frames;
    enc_ctx->pix_fmt = video_config->pix_fmt;
    // refs 可以设置参考帧的最大数量,这里主要是对h264_nvenc, 单路设置的时候对其编码时间没有影响,单纯设置只是对sps pps影响
//    enc_ctx->refs = 1;

    //
#if 1
    // 相关的参数可以参考libx264.c的 AVOption options
    // ultrafast all encode time:2270ms
    // medium all encode time:5815ms
    // veryslow all encode time:19836ms
    if(video_config->preset) {
        ret = av_opt_set(enc_ctx->priv_data, "preset", video_config->preset, 0);
        if(ret != 0) {
            printf("av_opt_set preset failed\n");
        }
    }
//    if(video_config->tune)
    {
        ret = av_opt_set(enc_ctx->priv_data, "tune","zerolatency", 0); // 直播是才使用该设置
        if(ret != 0) {
            printf("av_opt_set tune failed\n");
        }
    }

    if(video_config->profile) {
        ret = av_opt_set(enc_ctx->priv_data, "profile", "main", 0); // 默认是high
        if(ret != 0) {
            printf("av_opt_set profile failed\n");
        }
    }

#endif

    // 设置码率 
    enc_ctx->bit_rate = video_config->bit_rate;
//    enc_ctx->bit_rate_tolerance = enc_ctx->bit_rate*2; // 码率误差,允许的误差越大,视频越小


    return 0;
}

static int encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
                  FILE *outfile, int *bytes_count, int write_enable)
{
    int ret;

    /* send the frame to the encoder */

//    if(frame)
//        printf("Send frame %3"PRId64"\n", frame->pts);
    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;
        }
//        printf("pkt  pts:%3"PRId64", dts: %3"PRId64"\n",pkt->pts, pkt->dts);
        *bytes_count += pkt->size;
        if(write_enable)
            fwrite(pkt->data, 1, pkt->size, outfile);
    }
    return 0;
}

char *alloc_and_copy_string(char *str)
{
    if(!str) {
        printf("str is null\n");
        return NULL;
    }
    char *alloc_str = malloc(strlen(str) + 1);
    strcpy(alloc_str, str);
}

int video_encode_preset_test(const char *codec_name, char *in_yuv_file, char *out_h264_file, char *preset, int write_enable)
{
    FILE *infile = NULL;
    FILE *outfile = NULL;


    const AVCodec *codec = NULL;
    AVCodecContext *codec_ctx= NULL;
    AVFrame *frame = NULL;
    AVPacket *pkt = NULL;
    int ret = 0;
    int bytes_count = 0;
    int frame_count = 0;


    /* 查找指定的编码器 */
    codec = avcodec_find_encoder_by_name(codec_name);
    if (!codec) {
        fprintf(stderr, "Codec '%s' not found\n", codec_name);
        exit(1);
    }

    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    VideoConfgig *video_config = video_encode_new_default_config(codec_name, 1280, 720, 25);
    video_config->preset = alloc_and_copy_string(preset);

    video_encode_config(video_config, codec_ctx);  // 设置
    /* 将codec_ctx和codec进行绑定 */
    ret = avcodec_open2(codec_ctx, codec,NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
        exit(1);
    }
    printf("thread_count: %d, thread_type:%d\n", codec_ctx->thread_count, codec_ctx->thread_type);
    // 打开输入和输出文件
    infile = fopen(in_yuv_file, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", in_yuv_file);
        exit(1);
    }
    outfile = fopen(out_h264_file, "wb");
    if (!outfile) {
        fprintf(stderr, "Could not open %s\n", out_h264_file);
        exit(1);
    }

    // 分配pkt和frame
    pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    // 为frame分配buffer
    frame->format = codec_ctx->pix_fmt;
    frame->width  = codec_ctx->width;
    frame->height = codec_ctx->height;
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate the video frame data\n");
        exit(1);
    }
    // 计算出每一帧的数据 像素格式 * 宽 * 高
    // 1382400
    int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,
                                               frame->height, 1);
//    printf("frame_bytes %d\n", frame_bytes);
    uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
    if(!yuv_buf) {
        printf("yuv_buf malloc failed\n");
        return 1;
    }
    int64_t begin_time = get_time();        // 起始时间
    int64_t all_begin_time = get_time();
    int64_t all_end_time = all_begin_time;
    int64_t cur_pts = 0;

    for (;;) {
        memset(yuv_buf, 0, frame_bytes);
        size_t read_bytes = fread(yuv_buf, 1, frame_bytes, infile);
        if(read_bytes <= 0) {
//            printf("read file finish\n");
            break;
        }
        frame_count++; // 帧数加一
        /* 确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份
            目的是新写入的数据和编码器保存的数据不能产生冲突
        */
        ret = av_frame_make_writable(frame);
        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, yuv_buf,
                                             frame->format,
                                             frame->width, frame->height, 1);
        if(need_size != frame_bytes) {
            printf("av_image_fill_arrays failed, need_size:%d, frame_bytes:%d\n",
                   need_size, frame_bytes);
            break;
        }
        if(strcmp(codec_name, "h264_nvenc") == 0)
            cur_pts += 1;  // 25/25
        else
            cur_pts += 40; // 1000/25

        // 设置pts
        frame->pts = cur_pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
        begin_time = get_time();
        ret = encode(codec_ctx, frame, pkt, outfile, &bytes_count, write_enable);
        if(ret < 0) {
            printf("encode failed\n");
            break;
        }
    }

    /* 冲刷编码器 */
    encode(codec_ctx, NULL, pkt, outfile, &bytes_count, write_enable);
    all_end_time = get_time();
    float bps = (bytes_count * 8.0) /(frame_count/25.0)/1024; // 按25帧每秒计算
    printf("%s %s encode time:%lldms\n", codec_name, preset, all_end_time - all_begin_time);
    printf("%s %s encode bps:%0.2fkbps\n", codec_name, preset, bps);
    // 关闭文件
    fclose(infile);
    fclose(outfile);

    // 释放内存
    if(yuv_buf) {
        free(yuv_buf);
    }

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codec_ctx);
    if(video_config) {
        if(video_config->preset)
            free(video_config->preset);
        free(video_config);
    }

    return 0;
}
/**
 * @brief 提取测试文件:ffmpeg -i big_buck_bunny_720p_10mb.mp4 -t 10 -r 25 -pix_fmt yuv420p 1280x720_25fps_10s.yuv
 *       输入参数,输入yuv文件的路径,比如 1280x720_25fps_10s.yuv 1280x720_25fps_10s 1
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char **argv)
{
    char *in_yuv_file = NULL;
    char out_h264_file[128]={0};
    int write_enable = 1;
    if (argc < 4) {
        fprintf(stderr, "Usage: %s <input_file out_file write_enable>, argc:%d\n",
                argv[0], argc);
        return 0;
    }
    in_yuv_file = argv[1];      // 输入YUV文件
    if(argc >= 4) {
        write_enable = atoi(argv[3]);
    }

    //    const char *codec_name = "libx264";
//        const char *codec_name = "h264_nvenc";
   /* 测试每种preset的耗时
    ultrafast
    superfast
    veryfast
    faster
    fast
    medium
    slow
    slower
    veryslow
    placebo
    */
#if 1
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "ultrafast", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "ultrafast", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "ultrafast", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "ultrafast", write_enable); // nvenc不支持

    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "superfast", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "superfast", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "superfast", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "superfast", write_enable);  // nvenc不支持


    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "faster", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "faster", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "faster", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "faster", write_enable);   // nvenc不支持

    printf("\n\n--fast--------\n");
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "fast", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "fast", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "fast", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "fast", write_enable);


    printf("\n\n--medium--------\n");
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "medium", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "medium", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "medium", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "medium", write_enable);

    printf("\n\n--slow--------\n");
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slow", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "slow", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slow", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "slow", write_enable);

    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slower", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "slower", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "slower", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "slower", write_enable);   // nvenc不支持


    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "veryslow", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "veryslow", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "veryslow", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "veryslow", write_enable);  // nvenc不支持


    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "placebo", "libx264");
    video_encode_preset_test("libx264", in_yuv_file, out_h264_file, "placebo", write_enable);
    sprintf(out_h264_file, "%s_%s_%s.h264",  argv[2], "placebo", "h264_nvenc");
    video_encode_preset_test("h264_nvenc", in_yuv_file, out_h264_file, "placebo", write_enable);// nvenc不支持
    #endif
}

#if 0
libx264 ultrafast encode time:306ms
libx264 ultrafast encode bps:1027.18kbps

h264_nvenc ultrafast encode time:278ms
h264_nvenc ultrafast encode bps:906.10kbps


libx264 superfast encode time:381ms
libx264 superfast encode bps:1084.67kbps

h264_nvenc superfast encode time:226ms
h264_nvenc superfast encode bps:906.10kbps


libx264 faster encode time:800ms
libx264 faster encode bps:928.86kbps

h264_nvenc faster encode time:219ms
h264_nvenc faster encode bps:906.10kbps

h264_nvenc fast encode time:223ms
h264_nvenc fast encode bps:923.33kbps

libx264 medium encode time:1075ms
libx264 medium encode bps:928.75kbps

thread_count: 1, thread_type:3
h264_nvenc medium encode time:279ms
h264_nvenc medium encode bps:906.10kbps


thread_count: 0, thread_type:0
libx264 slow encode time:1852ms
libx264 slow encode bps:928.24kbps

thread_count: 1, thread_type:3
h264_nvenc slow encode time:456ms
h264_nvenc slow encode bps:921.65kbps

libx264 slower encode time:3017ms
libx264 slower encode bps:925.25kbps

thread_count: 1, thread_type:3
h264_nvenc slower encode time:276ms
h264_nvenc slower encode bps:906.10kbps


libx264 veryslow encode time:4748ms
libx264 veryslow encode bps:920.88kbps

h264_nvenc veryslow encode time:289ms
h264_nvenc veryslow encode bps:906.10kbps


libx264 placebo encode time:17834ms
libx264 placebo encode bps:923.30kbps

thread_count: 1, thread_type:3
h264_nvenc placebo encode time:282ms
h264_nvenc placebo encode bps:906.10kbps

#endif

更多资料:https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到