FFmpeg学习:编解码器

完整使用示例,参考官方例子(http://www.ffmpeg.org/doxygen/5.0/muxing_8c-example.html#a57)

视频编解码

  • FFMPEG对通用的视频编解码做了统一接口处理的抽象,比如在解码处理时,无须关心其具体的编解码格式,仅需关心其pixfmt即可

一、视频编码

第一步、视频编码器上下文初始化并打开编码器
	//av_log_set_level(AV_LOG_FATAL); // 设置日志等级
	media_encodec = avcodec_find_encoder(encoderId);
	media_encodec_context = avcodec_alloc_context3(media_encodec);
	if (!media_encodec_context) {
		avcodec_free_context(&media_encodec_context);
		std::cout << "Could not alloc an video encoding context" << std::endl;
	}

	media_encodec_context->codec_id = encoderId;
	media_encodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
	media_encodec_context->time_base = { 1,(int)format_video->frameRate };
	media_encodec_context->pix_fmt = format_video->videoBits;
	media_encodec_context->width = format_video->width;
	media_encodec_context->height = format_video->height;
	media_encodec_context->gop_size = 12;// format_video->frameRate; /* emit one intra frame every twelve frames at most */

	av_opt_set(media_encodec_context->priv_data, "tune", "zerolatency", 0);
	av_opt_set(media_encodec_context->priv_data, "profile", "main", 0);
	av_opt_set(media_encodec_context->priv_data, "preset", "veryfast", 0);

	if (avcodec_open2(media_encodec_context, media_encodec, NULL)) {
		avcodec_free_context(&media_encodec_context);
		std::cout << "Failed to open video encoding context !" << std::endl;
	}
第二步、编码过程
	if (in_frame->data[0] != NULL && 0 > avcodec_send_frame(media_encodec_context, in_frame))
	{
		return ACTK_ERROR_FAIL;
	}

	if (avcodec_receive_packet(media_encodec_context, out_pack) < 0)
	{
		return ACTK_ERROR_FAIL;
	}
第三步、释放编码器
avcodec_free_context(&media_encodec_context);

二、视频解码

第一步、视频解码器上下文初始化并打开编码器
	media_decodec = avcodec_find_decoder(decoderId);
	media_decodec_context = avcodec_alloc_context3(media_decodec);
	if (!media_decodec_context) {
		avcodec_free_context(&media_decodec_context);
		std::cout << "Could not alloc an video decoding context !" << std::endl;
	}

	media_decodec_context->codec_id = decoderId;
	media_decodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
	media_decodec_context->time_base = AVRational{1, static_cast<int>(format_video->frameRate)};
	media_decodec_context->pix_fmt = format_video->videoBits;
	media_decodec_context->height = format_video->height;
	media_decodec_context->width = format_video->width;

	av_opt_set(media_decodec_context->priv_data, "tune", "zerolatency", 0);
	av_opt_set(media_decodec_context->priv_data, "profile", "main", 0);
	av_opt_set(media_decodec_context->priv_data, "preset", "veryfast", 0);

	if (avcodec_open2(media_decodec_context, media_decodec, NULL)) {
		avcodec_free_context(&media_decodec_context);
		std::cout << "Failed to open video decoding context !" << std::endl;
	}
第二步、解码过程
	if (0 > avcodec_send_packet(media_decodec_context, in_pack))
	{
		return ACTK_ERROR_FAIL;
	}

	while (0 == avcodec_receive_frame(media_decodec_context, tmpFrame))
	{
		av_frame_unref(out_frame);
		av_frame_ref(out_frame, tmpFrame);
	}
第三步、释放解码器
avcodec_free_context(&media_decodec_context);

三、视频编解码上下文参数含义

/*AVCodecContext 相当于虚基类,需要用具体的编码器实现来给他赋值*/
pCodecCtxEnc = video_st->codec; 
 
//编码器的ID号,这里我们自行指定为264编码器,实际上也可以根据video_st里的codecID 参数赋值
pCodecCtxEnc->codec_id = AV_CODEC_ID_H264;
 
//编码器编码的数据类型
pCodecCtxEnc->codec_type = AVMEDIA_TYPE_VIDEO;
 
//目标的码率,即采样的码率;显然,采样码率越大,视频大小越大
pCodecCtxEnc->bit_rate = 200000;
 
//固定允许的码率误差,数值越大,视频越小
pCodecCtxEnc->bit_rate_tolerance = 4000000;
 
//编码目标的视频帧大小,以像素为单位
pCodecCtxEnc->width = 640;
pCodecCtxEnc->height = 480;
 
//帧率的基本单位,我们用分数来表示,
//用分数来表示的原因是,有很多视频的帧率是带小数的eg:NTSC 使用的帧率是29.97
pCodecCtxEnc->time_base.den = 30;
pCodecCtxEnc->time_base = (AVRational){1,25};
pCodecCtxEnc->time_base.num = 1;
 
//像素的格式,也就是说采用什么样的色彩空间来表明一个像素点
pCodecCtxEnc->pix_fmt = PIX_FMT_YUV420P;
 
//每250帧插入1个I帧,I帧越少,视频越小
pCodecCtxEnc->gop_size = 250;
 
//两个非B帧之间允许出现多少个B帧数
//设置0表示不使用B帧
//b 帧越多,图片越小
pCodecCtxEnc->max_b_frames = 0;
 
//运动估计
pCodecCtxEnc->pre_me = 2;
 
//设置最小和最大拉格朗日乘数
//拉格朗日乘数 是统计学用来检测瞬间平均值的一种方法
pCodecCtxEnc->lmin = 1;
pCodecCtxEnc->lmax = 5;
 
//最大和最小量化系数
pCodecCtxEnc->qmin = 10;
pCodecCtxEnc->qmax = 50;
 
//因为我们的量化系数q是在qmin和qmax之间浮动的,
//qblur表示这种浮动变化的变化程度,取值范围0.0~1.0,取0表示不削减
pCodecCtxEnc->qblur = 0.0;
 
//空间复杂度的masking力度,取值范围 0.0-1.0
pCodecCtxEnc->spatial_cplx_masking = 0.3;
 
//运动场景预判功能的力度,数值越大编码时间越长
pCodecCtxEnc->me_pre_cmp = 2;
 
//采用(qmin/qmax的比值来控制码率,1表示局部采用此方法,)
pCodecCtxEnc->rc_qsquish = 1;
 
//设置 i帧、p帧与B帧之间的量化系数q比例因子,这个值越大,B帧越不清楚
//B帧量化系数 = 前一个P帧的量化系数q * b_quant_factor + b_quant_offset
pCodecCtxEnc->b_quant_factor = 1.25;
 
//i帧、p帧与B帧的量化系数便宜量,便宜越大,B帧越不清楚
pCodecCtxEnc->b_quant_offset = 1.25;
 
//p和i的量化系数比例因子,越接近1,P帧越清楚
//p的量化系数 = I帧的量化系数 * i_quant_factor + i_quant_offset
pCodecCtxEnc->i_quant_factor = 0.8;
pCodecCtxEnc->i_quant_offset = 0.0;
 
//码率控制测率,宏定义,查API
pCodecCtxEnc->rc_strategy = 2;
 
//b帧的生成策略
pCodecCtxEnc->b_frame_strategy = 0;
 
//消除亮度和色度门限
pCodecCtxEnc->luma_elim_threshold = 0;
pCodecCtxEnc->chroma_elim_threshold = 0;
 
//DCT变换算法的设置,有7种设置,这个算法的设置是根据不同的CPU指令集来优化的取值范围在0-7之间
pCodecCtxEnc->dct_algo = 0;
 
//这两个参数表示对过亮或过暗的场景作masking的力度,0表示不作
pCodecCtxEnc->lumi_masking = 0.0;
pCodecCtxEnc->dark_masking = 0.0;
编码速度&编码质量&视觉优化参数

使用方法:

 av_opt_set(videoṡodecContext->priv_data,"字段名称","字段选项",0);
  1. preset 字段
    主要调节编码速度和质量的平衡,有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢,编码质量从低到高。

  2. tune 字段
    主要配合视频类型和视觉优化的参数况。如果视频的内容符合其中一个可用的调整值又或者有其中需要,则可以使用此选项,否则建议不使用(如tune grain是为高比特率的编码而设计的)。可选配置如下:

    • film: 电影、真人类型;
    • animation: 动画;
    • grain: 需要保留大量的grain时用;
    • stillimage: 静态图像编码时使用;
    • psnr: 为提高psnr做了优化的参数;
    • ssim: 为提高ssim做了优化的参数;
    • fastdecode: 可以快速解码的参数;
    • zerolatency:零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码
  3. profile 字段
    H.264有四种画质级别,分别是baseline, extended, main, high:

    1. Baseline Profile:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;
    2. Extended profile:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;(用的少)
    3. Main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),也支持CAVLC 和CABAC 的支持;
    4. High profile:高级画质。在main Profile 的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV 格式;

    H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式(YUV)的视频序列。在相同配置情况下,High profile(HP)可以比Main profile(MP)降低10%的码率。

    根据应用领域的不同,Baseline profile多应用于实时通信领域,Main profile多应用于流媒体领域,High profile则多应用于广电和存储领域。

使用方法:

	av_dict_set(&dictParam,"preset","medium",0);
    
	av_dict_set(&dictParam,"tune","zerolatency",0);
    
	av_dict_set(&dictParam,"profile","main",0);
	...
	avcodec_open2(pCodecCtx, pCodec,&dictParam);

音频编解码

音频编码

完整使用示例,参考官方例子(http://www.ffmpeg.org/doxygen/5.0/muxing_8c-example.html#a57)

一、编码器初始化和打开(准备工作)
第一步、首先根据编码器id 或 编码器name 查找到编码器
  1. 使用到的数据结构为 AVCodec audioCodec
  2. 两种查找编码器的 api
AVCodec audioCodec;
// 第一种:根据编码器id,可以从要输出的设备类型上下文 AVFormatContext 中获得
audioCodec = avcodec_find_encoder(AVFormatContext->ofomat->audio_codec);
// 第二种:根据编码器name, 如 libacc
audioCodec = avcodec_find_encoder_by_name("libacc");
第二步、申请并配置编码器上下文
  1. 根据编码器申请编码器上下文(采用的数据结构为 AVCodecContext )
AVCodecContext audioCodecCtx;
audioCodecCtx = avcodec_alloc_context3(audioCodec);
  1. 配置编码器上下文
audioCodecCtx->codec_id = fileFormatCtx->oformat->audio_codec;   // 编码器ID
audioCodecCtx->codec_type = audioCodec->type;   // 编码器类型:音频编码器、视频编码器等
audioCodecCtx->codec_tag = 0;   // 用于解决一些编码器错误,如果没有,则使用 基于codec_id的默认值
audioCodecCtx->time_base = audioTimeBase;   
audioCodecCtx->sample_rate = sampleRate;
audioCodecCtx->sample_fmt = audFormat->audioBits;
audioCodecCtx->channels = audFormat->channelNum;
audioCodecCtx->channel_layout = av_get_default_channel_layou(audioCodecCtx->channels);
audioCodec->capabilities = AV_CODEC_CAP_VARIABLE_FRAME_SIZE;    // 设置该参数后,输入帧的大小可以改变
第三步、打开编码器
if (avcodec_open2(audioCodecCtx, audioCodec, NULL) < 0)
{
	std::cout << "could not open audio encoder!" << std::endl;
	return -1;
}
// 将编码器参数保存到 输出流 里的 codecpar 中
avcodec_parameters_from_context(audioStream->codecpar, audioCodecCtx);
二、编码器的使用(即编码过程)

参考官方用例:http://www.ffmpeg.org/doxygen/5.0/encode_audio_8c-example.html#a21

static void encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *output)
{
    int ret;
 
    /* send the frame for encoding */
    ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending the frame to the encoder\n");
        exit(1);
    }
 
    /* read all the available output packets (in general there may be any
     * number of them */
    while (ret >= 0) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error encoding audio frame\n");
            exit(1);
        }
 
        fwrite(pkt->data, 1, pkt->size, output);
        av_packet_unref(pkt);
    }
}
三、编码器释放(结束)

只需要显示释放编码器上下文即可

avcodec_free_context(&ctx);

音频解码

一、音频解码器初始化和打开
	media_decodec = avcodec_find_decoder(decoderId);
	media_decodec_context = avcodec_alloc_context3(media_decodec);
	if (!media_decodec_context) {
		avcodec_free_context(&media_decodec_context);
		std::cout << "Could not alloc an audio decoding context !" << std::endl;
	}

	media_decodec_context->codec_id = decoderId;
	media_decodec_context->codec_type = AVMEDIA_TYPE_AUDIO;
	media_decodec_context->codec_tag = 0;
	media_decodec_context->time_base = AVRational{1,static_cast<int>(format_audio->sampleRate)};
	media_decodec_context->sample_rate = format_audio->sampleRate;
	media_decodec_context->sample_fmt = format_audio->audioBits;
	media_decodec_context->channels = format_audio->channelNum;
	media_decodec_context->channel_layout = av_get_default_channel_layout(format_audio->channelNum);
	
	if (avcodec_open2(media_decodec_context, media_decodec, NULL)) {
		avcodec_free_context(&media_decodec_context);
		std::cout << "Failed to open audio decoding context !" << std::endl;
	}
二、音频包解码过程
	if (0 > avcodec_send_packet(media_decodec_context, in_pack))
	{
		//if (TRUE == isAudio)
		//{
		//	std::cout << "audio decodec avcodec_send_packet() failed !" << std::endl;
		//}
		//else
		//{
		//	std::cout << "video decodec avcodec_send_packet() failed !" << std::endl;
		//}
		return ACTK_ERROR_FAIL;
	}

	while (0 == avcodec_receive_frame(media_decodec_context, tmpFrame))
	{
		av_frame_unref(out_frame);
		av_frame_ref(out_frame, tmpFrame);
	}
三、解码器释放
avcodec_free_context(&ctx);

参考

ffmpeg 编码器AVCodecContext 的配置参数和视频解码的重要成员讲解

posted @ 2022-08-19 13:57  小超不挑食  阅读(420)  评论(0编辑  收藏  举报