本文为做者原创,转载请注明出处:http://www.javashuo.com/article/p-rnnbskcv-w.htmlhtml
FFmpeg编解码处理系列笔记:
[0]. FFmpeg时间戳详解
[1]. FFmpeg编解码处理1-转码全流程简介
[2]. FFmpeg编解码处理2-编解码API详解
[3]. FFmpeg编解码处理3-视频编码
[4]. FFmpeg编解码处理4-音频编码git
基于FFmpeg 4.1版本。github
编码使用avcodec_send_frame()
和avcodec_receive_packet()
两个函数。ide
视频编码的步骤:函数
frame->pict_type=AV_PICTURE_TYPE_NONE
,让编码器根据设定参数自行生成I/B/P帧类型完整源码在open_output_file()函数中,下面摘出关键部分:布局
// 3. 构建AVCodecContext if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) // 音频流或视频流 { // 3.1 查找编码器AVCodec,本例使用与解码器相同的编码器 AVCodec *encoder = NULL; if ((dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) && (strcmp(v_enc_name, "copy") != 0)) { encoder = avcodec_find_encoder_by_name(v_enc_name); } else if ((dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && (strcmp(a_enc_name, "copy") != 0)) { encoder = avcodec_find_encoder_by_name(a_enc_name); } else { encoder = avcodec_find_encoder(dec_ctx->codec_id); } if (!encoder) { av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n"); return AVERROR_INVALIDDATA; } // 3.2 AVCodecContext初始化:分配结构体,使用AVCodec初始化AVCodecContext相应成员为默认值 AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder); if (!enc_ctx) { av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n"); return AVERROR(ENOMEM); } // 3.3 AVCodecContext初始化:配置图像/声音相关属性 /* In this example, we transcode to same properties (picture size, * sample rate etc.). These properties can be changed for output * streams easily using filters */ if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { enc_ctx->height = dec_ctx->height; // 图像高 enc_ctx->width = dec_ctx->width; // 图像宽 enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 采样宽高比:像素宽/像素高 /* take first format from list of supported formats */ if (encoder->pix_fmts) // 编码器支持的像素格式列表 { enc_ctx->pix_fmt = encoder->pix_fmts[0]; // 编码器采用所支持的第一种像素格式 } else { enc_ctx->pix_fmt = dec_ctx->pix_fmt; // 编码器采用解码器的像素格式 } /* video time_base can be set to whatever is handy and supported by encoder */ enc_ctx->time_base = av_inv_q(dec_ctx->framerate); // 时基:解码器帧率取倒数 enc_ctx->framerate = dec_ctx->framerate; //enc_ctx->bit_rate = dec_ctx->bit_rate; /* emit one intra frame every ten frames * check frame pict_type before passing frame * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I * then gop_size is ignored and the output of encoder * will always be I frame irrespective to gop_size */ //enc_ctx->gop_size = 10; //enc_ctx->max_b_frames = 1; } else { enc_ctx->sample_rate = dec_ctx->sample_rate; // 采样率 enc_ctx->channel_layout = dec_ctx->channel_layout; // 声道布局 enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 声道数量 /* take first format from list of supported formats */ enc_ctx->sample_fmt = encoder->sample_fmts[0]; // 编码器采用所支持的第一种采样格式 enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; // 时基:编码器采样率取倒数 // enc_ctx->codec->capabilities |= AV_CODEC_CAP_VARIABLE_FRAME_SIZE; // 只读标志 // 初始化一个FIFO用于存储待编码的音频帧,初始化FIFO大小的1个采样点 // av_audio_fifo_alloc()第二个参数是声道数,第三个参数是单个声道的采样点数 // 采样格式及声道数在初始化FIFO时已设置,各处涉及FIFO大小的地方都是用的单个声道的采样点数 pp_audio_fifo[i] = av_audio_fifo_alloc(enc_ctx->sample_fmt, enc_ctx->channels, 1); if (pp_audio_fifo == NULL) { av_log(NULL, AV_LOG_ERROR, "Could not allocate FIFO\n"); return AVERROR(ENOMEM); } } if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // 3.4 AVCodecContext初始化:使用AVCodec初始化AVCodecContext,初始化完成 /* Third parameter can be used to pass settings to encoder */ ret = avcodec_open2(enc_ctx, encoder, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); return ret; } // 3.5 设置输出流codecpar ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i); return ret; } // 3.6 保存输出流contex pp_enc_ctx[i] = enc_ctx; }
完整源码在transcode_video()
函数中,下面摘出关键部分:测试
// 2. 滤镜处理 ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt); if (ret == AVERROR_EOF) { av_log(NULL, AV_LOG_INFO, "filtering vframe EOF\n"); flt_finished = true; av_frame_free(&frame_flt); // flush encoder } else if (ret < 0) { av_log(NULL, AV_LOG_INFO, "filtering vframe error %d\n", ret); goto end; } flush_encoder: // 3. 编码 if (frame_flt != NULL) { // 3.1 设置帧类型。若是不设置,则使用输入流中的帧类型。 frame_flt->pict_type = AV_PICTURE_TYPE_NONE; } // 3.2 编码 ret = av_encode_frame(sctx->o_codec_ctx, frame_flt, &opacket); if (ret == AVERROR(EAGAIN)) // 须要读取新的packet喂给编码器 { //av_log(NULL, AV_LOG_INFO, "encode vframe need more packet\n"); goto end; } else if (ret == AVERROR_EOF) { av_log(NULL, AV_LOG_INFO, "encode vframe EOF\n"); enc_finished = true; goto end; } else if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "encode vframe error %d\n", ret); goto end; } // 3.3 更新编码帧中流序号,并进行时间基转换 // AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不一样的封装格式其AVStream.time_base不一样 // 因此输出文件中,每一个packet须要根据输出封装格式从新计算pts和dts opacket.stream_index = sctx->stream_idx; av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base); // 4. 将编码后的packet写入输出媒体文件 ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket); av_packet_unref(&opacket);
作一个实验,修改5.1.2节frame_flt->pict_type值和5.1.1节enc_ctx->gop_size和enc_ctx->max_b_frames,将编码后视频帧I/B/P类型打印出来,观察实验结果。this
咱们选一个很短的视频文件用于测试(右键另存为):tnmil3.flv
编码
tnmil3.flv重命名为tnmil.flv 转码:tnmil.flv ==> tnmilo1.flv 命令:./transcode -i tnmil.flv -c:v copy -c:a copy tnmilo1.flv tnmil.flv ==> tnmilo1.flv 不修改frame IBP类型,不设置编码器gop_size和max_b_frames IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP tnmil.flv ==> tnmilo3.flv 将frame IBP类型设为NONE,将编码器gop_size设为10,max_b_frames设为1 IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP tnmilo3.flv ==> tnmilo4.flv 不修改frame IBP类型,不设置编码器gop_size和max_b_frames IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP tnmilo3.flv ==> tnmilo5.flv 将frame IBP类型设为NONE,不设置编码器gop_size(默认-1)和max_b_frames(默认0) IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP IBPBPBPBPBBBPBPBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBPBBBPPIBBP
实验结论以下:
将原始视频帧frame送入视频编码器后生成编码帧packet,那么code