【FFmpeg】编码链路上主要函数的简单分析
示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染
流程分析:
【x264】编码核心函数(x264_encoder_encode)的简单分析
1. 主要工作流程
本文分析的FFmpeg版本是FFmpeg-7.0,只考虑视频编码部分的流程,不考虑音频部分
FFmpeg进行编码器的调用时,主要工作流程大约是
编码器:
- 查找编码器(avcodec_find_encoder)
- 创建编码器上下文(avcodec_alloc_context3)
- 创建输入信息的结构体(av_frame_alloc)
- 创建输出信息的结构体(av_packet_alloc)
- 打开编码器(avcodec_open2)
- 将输入帧填充到frame当中(…)
- 将输入帧送入编码器进行编码(avcodec_send_frame)
- 获取编码器输出的已编码信息(avcodec_receive_packet)
- 释放结构体信息(…)
2. 编码过程
2.1 查找编码器(avcodec_find_encoder)
该函数根据AVCodecID来查找编码器,返回一个AVCodec*。函数定义位于libavcodec/allcodecs.c,整体执行流程为:
- 进入查找编码器函数(avcodec_find_encoder)
- 进入内部查找函数(find_codec)
- 循环从codec_list当中读取codec(av_codec_iterate)
- 判断读取的codec是否与输入id匹配,同时检查这个编码器是否有编码能力,如果有能力则返回这个编码器的地址
- 如果没有查找到codec则返回NULL
const AVCodec *avcodec_find_encoder(enum AVCodecID id)
{
return find_codec(id, av_codec_is_encoder);
}
find_codec的实现方式如下,调用了av_codec_is_encoder这个函数
static const AVCodec *find_codec(enum AVCodecID id, int (*x)(const AVCodec *))
{
const AVCodec *p, *experimental = NULL;
void *i = 0;
id = remap_deprecated_codec_id(id); // 暂时没有实际操作,对id没有影响
while ((p = av_codec_iterate(&i))) { // 从codec_list当中循环读取一个codec
if (!x(p)) // x就是avcodec_find_encoder函数,去寻找这个codec是否是编码器
continue;
if (p->id == id) { // 如果找到了这个codec的id,则检查其能否正常使用
if (p->capabilities & AV_CODEC_CAP_EXPERIMENTAL && !experimental) {
experimental = p; // 能够正常使用,将这个codec进行输出
} else
return p;
}
}
return experimental;
}
对于avcodec_find_decoder,工作的流程一致,只是查找的是解码器,调用的函数略有区别
2.2 创建编码器上下文(avcodec_alloc_context3)
该函数用于分配编解码器上下文信息这一结构体的内存,返回一个AVCodecContext*。函数定义位于libavcodec/options.c当中,整体的流程是:
- 调用av_malloc分配AVCodecContext的内存
- 调用init_context_defaults进行变量的初始化,使用的是默认值
初始化主要包括
名称 | 功能 |
---|---|
av_class | 记录了av_log的信息 |
codec_type | 编解码器的类型(视频、音频或其他) |
opt | 记录解码的flag参数(根据codec_type确定) |
ch_layout | 音频的channel layout |
time_base | 时间基 |
framerate | 帧率 |
pkt_timebase | pkt的时间基 |
get_buffer2 | 获取默认AVFrame的buffer |
get_format | 获取默认的format(结合硬件的fomrat确定) |
get_encode_buffer | 获取编码的buffer,这里buffer的内容是AVPacket,即编码输出的buffer |
execute | 执行函数,输入的参数包含函数指针 |
execute2 | 执行函数,输入的参数包含函数指针 |
sample_aspect_ratio | 样本纵横比(宽度除以高度) |
ch_layout.order | 在layout中使用的布局顺序 |
pix_fmt | 像素格式 |
sw_pix_fmt | 标称非加速像素格式 |
sample_fmt | 音频样本格式 |
priv_data | 私有数据,常用于强制转换,是重要的中间数据信息 |
实现的代码如下
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext)); // 分配AVCodecContext的存储空间
if (!avctx)
return NULL;
if (init_context_defaults(avctx, codec) < 0) { // 进行初始值初始化
av_free(avctx);
return NULL;
}
return avctx;
}
使用默认值来初始化变量
static int init_context_defaults(AVCodecContext *s, const AVCodec *codec)
{
const FFCodec *const codec2 = ffcodec(codec);
int flags=0;
memset(s, 0, sizeof(AVCodecContext));
s->av_class = &av_codec_context_class; // 默认的AVClass
s->codec_type = codec ? codec->type : AVMEDIA_TYPE_UNKNOWN;
if (codec) {
s->codec = codec;
s->codec_id = codec->id;
}
// 初始化flags
// 这3个参数声明,是供用户设置用于demuxing和decoding
if(s->codec_type == AVMEDIA_TYPE_AUDIO)
flags= AV_OPT_FLAG_AUDIO_PARAM;
else if(s->codec_type == AVMEDIA_TYPE_VIDEO)
flags= AV_OPT_FLAG_VIDEO_PARAM;
else if(s->codec_type == AVMEDIA_TYPE_SUBTITLE)
flags= AV_OPT_FLAG_SUBTITLE_PARAM;
// 根据flag进行s当中opt的赋值,这里的s是AVCodecContext
av_opt_set_defaults2(s, flags, flags);
// AVChannelLayout当中包含的是音频数据的layout的信息(不太理解音频数据的处理方式)
av_channel_layout_uninit(&s->ch_layout);
s->time_base = (AVRational){0,1};
s->framerate = (AVRational){ 0, 1 };
s->pkt_timebase = (AVRational){ 0, 1 };
// ----- 以下只是配置函数,在这里并没有执行 -----
// 获取默认的buffer,执行s->get_buffer2时会默认调用avcodec_default_get_buffer2,下同
// 1.如果设置了硬件编解码器,则会调用av_hwframe_get_buffer去获取硬件方面的信息
// 2.否则调用video_get_buffer获取buffer,这个buffer分配的空间是AVFrame,并且会记录到internal->pool这个Framepool当中,即输入帧池
s->get_buffer2 = avcodec_default_get_buffer2;
// 获取默认的格式
// 1.如果设置了硬件处理方式,则去解析硬件的pix_fmt信息
// 2.将软件支持的format加入到desc的list当中
s->get_format = avcodec_default_get_format;
// 获取默认的encode的buffer
// 1.会分配AVPacket的内存空间(对于encode而言,输入frame是确定的,需要分配的buffer是存储编码后码流的空间)
s->get_encode_buffer = avcodec_default_get_encode_buffer;
// 获取默认的execute和execute2
// execute指代的是函数的执行,其输入的参数包括函数指针
s->execute = avcodec_default_execute;
s->execute2 = avcodec_default_execute2;
// ----- 配置函数end,在这里并没有执行 -----
s->sample_aspect_ratio = (AVRational){0,1};
// AV_CHANNEL_ORDER_UNSPEC,只定义了channel count,没有额外的信息
s->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
s->pix_fmt = AV_PIX_FMT_NONE;
s->sw_pix_fmt = AV_PIX_FMT_NONE;
s->sample_fmt = AV_SAMPLE_FMT_NONE;
// priv_data常会进行格式转换
if(codec && codec2->priv_data_size){
s->priv_data = av_mallocz(codec2->priv_data_size);
if (!s->priv_data)
return AVERROR(ENOMEM);
if(codec->priv_class){
*(const AVClass**)s->priv_data = codec->priv_class;
av_opt_set_defaults(s->priv_data);
}
}
if (codec && codec2->defaults) {
int ret;
const FFCodecDefault *d = codec2->defaults;
while (d->key) {
ret = av_opt_set(s, d->key, d->value, 0);
av_assert0(ret >= 0);
d++;
}
}
return 0;
}
2.3 创建输入信息的结构体(av_frame_alloc)
该函数用于分配AVFrame*的内存空间。在编解码流程中,AVFrame常用于编码器的输入和解码器的输出。函数的定义位于libavutil/frame.c,主要流程如下:
- 调用av_malloc分配AVFrame的内存
- 调用get_frame_defaults进行初始化
初始化主要内容包括
名称 | 功能 |
---|---|
pts | 显示时间戳 |
pkt_dts | packet的解码时间戳 |
best_effort_timestamp | 最佳时间戳,以流的时间为基准 |
duration | 持续时间 |
pkt_pos | packet的位置 |
pkt_size | packet的大小 |
time_base | 时间基 |
sample_aspect_ratio | 样本纵横比 |
format | 帧的格式 |
extended_data | 拓展数据,用frame->data初始化 |
color_primaries | 颜色的基准(如BT709等) |
color_trc | 颜色格式转换特征 |
colorspace | YUV颜色空间类型(如RGB、BT709等) |
color_range | 颜色范围(如MPEG、JPEG等) |
chroma_location | 色度分量的位置 |
flags | 帧flag |
实现代码如下
AVFrame *av_frame_alloc(void)
{
AVFrame *frame = av_malloc(sizeof(*frame));
if (!frame)
return NULL;
get_frame_defaults(frame);
return frame;
}
调用了get_frame_defaults
static void get_frame_defaults(AVFrame *frame)
{
memset(frame, 0, sizeof(*frame));
frame->pts =
frame->pkt_dts = AV_NOPTS_VALUE;
frame->best_effort_timestamp = AV_NOPTS_VALUE;
frame->duration = 0;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
frame->pkt_pos = -1;
frame->pkt_size = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
frame->time_base = (AVRational){ 0, 1 };
frame->sample_aspect_ratio = (AVRational){ 0, 1 };
frame->format = -1; /* unknown */
frame->extended_data = frame->data;
frame->color_primaries = AVCOL_PRI_UNSPECIFIED;
frame->color_trc = AVCOL_TRC_UNSPECIFIED;
frame->colorspace = AVCOL_SPC_UNSPECIFIED;
frame->color_range = AVCOL_RANGE_UNSPECIFIED;
frame->chroma_location = AVCHROMA_LOC_UNSPECIFIED;
frame->flags = 0;
}
2.4 创建输出信息的结构体(av_packet_alloc)
该函数用于分配AVPacket的空间,在编解码流程中,av_pkt可以作为编码器的输出,也可以作为解码器的输。函数定义位于libavcodec/avpacket.c,主要的流程如下
- 调用av_malloc进行AVPacket*内存的分配
- 调用get_packet_defaults为packet进行默认值配置
配置的内容包括
名称 | 功能 |
---|---|
pts | 显示时间戳 |
dts | 解码时间戳 |
pos | 位置 |
time_base | 时间基 |
实现代码如下
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = av_malloc(sizeof(AVPacket));
if (!pkt)
return pkt;
get_packet_defaults(pkt);
return pkt;
}
调用get_packet_defaults获取默认配置
static void get_packet_defaults(AVPacket *pkt)
{
memset(pkt, 0, sizeof(*pkt));
pkt->pts = AV_NOPTS_VALUE;
pkt->dts = AV_NOPTS_VALUE;
pkt->pos = -1;
pkt->time_base = av_make_q(0, 1);
}
2.5 打开编码器(avcodec_open2)
该函数的功能是根据输入的AVCodec来初始化AVCodecContext。可以理解为配置了AVCodecContext,此时可以开始进行编解码器的工作了,即打开了编解码器。定义位于libavcodec/avcodec.c中,主要的流程为:
- 对输入变量进行一些检查
- 将codec部分信息赋值给codec_ctx
- 如果是解码器,则调用ff_decode_internal_alloc进行内存分配;如果是编码器,则调用ff_encode_internal_alloc进行内存分配,分配之后将地址存储到codec_ctx中。ff_XXX_internal_alloc直接调用av_malloc函数进行内存的分配。
- 分配av_frame和av_pakcet的空间,将地址存储到codec_ctx中
- 分配priv_data的空间,将地址存储到codec_ctx中
- 检查codec是否在codec的白名单中,不在则返回错误
- 根据码流当中的宽高设置维度
- 检查图像的尺寸
- 检查纵横比,如果不符合规定,则配置为默认值
- 检查采样率、每个数据包字节数(针对于音频)
- 检查layout(音频不是很理解,猜测这里应该是声道数)
- 初始化frame数量和编解码器的描述器
- 检查codec的能力
- 对于编码器,调用ff_encode_preinit进行早期初始化,否则调用ff_decode_preinit进行早期初始化
- 检查是否是帧级别的多线程处理
- 检查解码器情况下的channel layout是否有效
实现代码如下
int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
{
int ret = 0;
AVCodecInternal *avci;
const FFCodec *codec2;
// ----- 1.检查输入变量是否存在 ----- //
if (avcodec_is_open(avctx))
return 0;
if (!codec && !avctx->codec) {
av_log(avctx, AV_LOG_ERROR, "No codec provided to avcodec_open2()\n");
return AVERROR(EINVAL);
}
if (codec && avctx->codec && codec != avctx->codec) {
av_log(avctx, AV_LOG_ERROR, "This AVCodecContext was allocated for %s, "
"but %s passed to avcodec_open2()\n", avctx->codec->name, codec->name);
return AVERROR(EINVAL);
}
if (!codec)
codec = avctx->codec;
codec2 = ffcodec(codec);
if ((avctx->codec_type != AVMEDIA_TYPE_UNKNOWN && avctx->codec_type != codec->type) ||
(avctx->codec_id != AV_CODEC_ID_NONE && avctx->codec_id != codec->id)) {
av_log(avctx, AV_LOG_ERROR, "Codec type or id mismatches\n");
return AVERROR(EINVAL);
}
// ----- 2.赋值codec的信息 ----- //
avctx->codec_type = codec->type;
avctx->codec_id = codec->id;
avctx->codec = codec;
if (avctx->extradata_size < 0 || avctx->extradata_size >= FF_MAX_EXTRADATA_SIZE)
return AVERROR(EINVAL);
// ----- 3.根据编解码器的类型进行内存分配 ----- //
// ff_xxx_internal_alloc直接调用了av_malloc进行内存分配
avci = av_codec_is_decoder(codec) ?
ff_decode_internal_alloc() :
ff_encode_internal_alloc();
if (!avci) {
ret = AVERROR(ENOMEM);
goto end;
}
avctx->internal = avci;
// ----- 4.分配Frame和Packet的空间 ----- //
avci->buffer_frame = av_frame_alloc();
avci->buffer_pkt = av_packet_alloc();
if (!avci->buffer_frame || !avci->buffer_pkt) {
ret = AVERROR(ENOMEM);
goto free_and_end;
}
// ----- 5.分配priv_data的空间 ----- //
if (codec2->priv_data_size > 0) {
if (!avctx->priv_data) {
avctx->priv_data = av_mallocz(codec2->priv_data_size);
if (!avctx->priv_data) {
ret = AVERROR(ENOMEM);
goto free_and_end;
}
if (codec->priv_class) {
*(const AVClass **)avctx->priv_data = codec->priv_class;
av_opt_set_defaults(avctx->priv_data);
}
}
if (codec->priv_class && (ret = av_opt_set_dict(avctx->priv_data, options)) < 0)
goto free_and_end;
} else {
avctx->priv_data = NULL;
}
if ((ret = av_opt_set_dict(avctx, options)) < 0)
goto free_and_end;
// ----- 6.检查codec是否在白名单当中 ----- //
if (avctx->codec_whitelist && av_match_list(codec->name, avctx->codec_whitelist, ',') <= 0) {
av_log(avctx, AV_LOG_ERROR, "Codec (%s) not on whitelist \'%s\'\n", codec->name, avctx->codec_whitelist);
ret = AVERROR(EINVAL);
goto free_and_end;
}
// ----- 7.根据码流当中的宽高设置维度 ----- //
// only call ff_set_dimensions() for non H.264/VP6F/DXV codecs so as not to overwrite previously setup dimensions
if (!(avctx->coded_width && avctx->coded_height && avctx->width && avctx->height &&
(avctx->codec_id == AV_CODEC_ID_H264 || avctx->codec_id == AV_CODEC_ID_VP6F || avctx->codec_id == AV_CODEC_ID_DXV))) {
if (avctx->coded_width && avctx->coded_height)
ret = ff_set_dimensions(avctx, avctx->coded_width, avctx->coded_height);
else if (avctx->width && avctx->height)
ret = ff_set_dimensions(avctx, avctx->width, avctx->height);
if (ret < 0)
goto free_and_end;
}
// ----- 8.检查图像的尺寸 ----- //
if ((avctx->coded_width || avctx->coded_height || avctx->width || avctx->height)
&& ( av_image_check_size2(avctx->coded_width, avctx->coded_height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0
|| av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0)) {
av_log(avctx, AV_LOG_WARNING, "Ignoring invalid width/height values\n");
ff_set_dimensions(avctx, 0, 0);
}
// ----- 9.检查纵横比 ----- //
if (avctx->width > 0 && avctx->height > 0) {
if (av_image_check_sar(avctx->width, avctx->height,
avctx->sample_aspect_ratio) < 0) {
av_log(avctx, AV_LOG_WARNING, "ignoring invalid SAR: %u/%u\n",
avctx->sample_aspect_ratio.num,
avctx->sample_aspect_ratio.den);
avctx->sample_aspect_ratio = (AVRational){ 0, 1 };
}
}
// ----- 10.检查采样率和block_align(针对于音频) ----- //
if (avctx->sample_rate < 0) {
av_log(avctx, AV_LOG_ERROR, "Invalid sample rate: %d\n", avctx->sample_rate);
ret = AVERROR(EINVAL);
goto free_and_end;
}
if (avctx->block_align < 0) {
av_log(avctx, AV_LOG_ERROR, "Invalid block align: %d\n", avctx->block_align);
ret = AVERROR(EINVAL);
goto free_and_end;
}
// ----- 11.检查音频的layout ----- //
/* AV_CODEC_CAP_CHANNEL_CONF is a decoder-only flag; so the code below
* in particular checks that nb_channels is set for all audio encoders. */
if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && !avctx->ch_layout.nb_channels
&& !(codec->capabilities & AV_CODEC_CAP_CHANNEL_CONF)) {
av_log(avctx, AV_LOG_ERROR, "%s requires channel layout to be set\n",
av_codec_is_decoder(codec) ? "Decoder" : "Encoder");
ret = AVERROR(EINVAL);
goto free_and_end;
}
if (avctx->ch_layout.nb_channels && !av_channel_layout_check(&avctx->ch_layout)) {
av_log(avctx, AV_LOG_ERROR, "Invalid channel layout\n");
ret = AVERROR(EINVAL);
goto free_and_end;
}
if (avctx->ch_layout.nb_channels > FF_SANE_NB_CHANNELS) {
av_log(avctx, AV_LOG_ERROR, "Too many channels: %d\n", avctx->ch_layout.nb_channels);
ret = AVERROR(EINVAL);
goto free_and_end;
}
// ----- 12.初始化帧数量和编解码描述器 ----- //
avctx->frame_num = 0;
avctx->codec_descriptor = avcodec_descriptor_get(avctx->codec_id);
// ----- 13.检查codec的能力 ----- //
if ((avctx->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) &&
avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
const char *codec_string = av_codec_is_encoder(codec) ? "encoder" : "decoder";
const AVCodec *codec2;
av_log(avctx, AV_LOG_ERROR,
"The %s '%s' is experimental but experimental codecs are not enabled, "
"add '-strict %d' if you want to use it.\n",
codec_string, codec->name, FF_COMPLIANCE_EXPERIMENTAL);
codec2 = av_codec_is_encoder(codec) ? avcodec_find_encoder(codec->id) : avcodec_find_decoder(codec->id);
if (!(codec2->capabilities & AV_CODEC_CAP_EXPERIMENTAL))
av_log(avctx, AV_LOG_ERROR, "Alternatively use the non experimental %s '%s'.\n",
codec_string, codec2->name);
ret = AVERROR_EXPERIMENTAL;
goto free_and_end;
}
if (avctx->codec_type == AVMEDIA_TYPE_AUDIO &&
(!avctx->time_base.num || !avctx->time_base.den)) {
avctx->time_base.num = 1;
avctx->time_base.den = avctx->sample_rate;
}
// ----- 14.早期初始化 ----- //
if (av_codec_is_encoder(avctx->codec))
ret = ff_encode_preinit(avctx);
else
ret = ff_decode_preinit(avctx);
if (ret < 0)
goto free_and_end;
// ----- 15.检查是否是帧级别的多线程处理 ----- //
if (HAVE_THREADS && !avci->frame_thread_encoder) {
/* Frame-threaded decoders call FFCodec.init for their child contexts. */
lock_avcodec(codec2);
ret = ff_thread_init(avctx);
unlock_avcodec(codec2);
if (ret < 0) {
goto free_and_end;
}
}
if (!HAVE_THREADS && !(codec2->caps_internal & FF_CODEC_CAP_AUTO_THREADS))
avctx->thread_count = 1;
if (!(avctx->active_thread_type & FF_THREAD_FRAME) ||
avci->frame_thread_encoder) {
if (codec2->init) {
lock_avcodec(codec2);
ret = codec2->init(avctx);
unlock_avcodec(codec2);
if (ret < 0) {
avci->needs_close = codec2->caps_internal & FF_CODEC_CAP_INIT_CLEANUP;
goto free_and_end;
}
}
avci->needs_close = 1;
}
ret=0;
// ----- 16.检查解码器情况下channel layout是否有效 ----- //
if (av_codec_is_decoder(avctx->codec)) {
if (!avctx->bit_rate)
avctx->bit_rate = get_bit_rate(avctx);
/* validate channel layout from the decoder */
if ((avctx->ch_layout.nb_channels && !av_channel_layout_check(&avctx->ch_layout)) ||
avctx->ch_layout.nb_channels > FF_SANE_NB_CHANNELS) {
ret = AVERROR(EINVAL);
goto free_and_end;
}
if (avctx->bits_per_coded_sample < 0) {
ret = AVERROR(EINVAL);
goto free_and_end;
}
}
if (codec->priv_class)
av_assert0(*(const AVClass **)avctx->priv_data == codec->priv_class);
end:
return ret;
free_and_end:
ff_codec_close(avctx);
goto end;
}
2.6 将输入帧送入编码器进行编码(avcodec_send_frame)
除去一些必要的检查之外,编码过程的主要函数调用流程为:
- 送帧主函数(avcodec_send_frame)
- 分接收类型的编码上层函数(encode_receive_packet_internal)
- 分线程的编码上层函数(encode_simple_receive_packet)
- 单线程编码主函数(ff_encode_encode_cb)
- 核心编码函数(X264_frame)
2.6.1 送帧主函数(avcodec_send_frame)
对于avcodec_send_frame函数而言,主要功能是将原始帧送入到编码器当中进行编码。函数位于libavcodec/encode.c,实现的主要流程为:
- 检查codec是否打开,检查是否是编码器
- 检查输入的frame buffer是否为空
- 针对音频数据进行参数检查并对数据进行填充(encode_send_frame_internal)
- 在pkt没有数据的情况下,辅助获取编码的pkt内容(encode_receive_packet_internal)
实现的代码为
int attribute_align_arg avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame)
{
AVCodecInternal *avci = avctx->internal;
int ret;
// ----- 1.检查codec是否打开,检查是否是编码器 ----- //
if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
return AVERROR(EINVAL);
if (avci->draining)
return AVERROR_EOF;
// ----- 2.检查输入的frame是否为空 ----- //
if (avci->buffer_frame->buf[0])
return AVERROR(EAGAIN);
if (!frame) {
avci->draining = 1;
} else {
// ----- 3. 如果是音频,进行参数检查和数据填充 ----- //
ret = encode_send_frame_internal(avctx, frame);
if (ret < 0)
return ret;
}
// ----- 4.在pkt没有数据的情况下,辅助获取编码的pkt内容 ----- //
if (!avci->buffer_pkt->data && !avci->buffer_pkt->side_data) {
ret = encode_receive_packet_internal(avctx, avci->buffer_pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
}
avctx->frame_num++;
return 0;
}
2.6.2 分接收类型的编码上层函数(encode_receive_packet_internal)
进行编码器操作的代码如下(encode_receive_packet_internal),主要流程为:
- 检查图像尺寸
- 调用receive_packet进行编码
static int encode_receive_packet_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
AVCodecInternal *avci = avctx->internal;
int ret;
if (avci->draining_done)
return AVERROR_EOF;
av_assert0(!avpkt->data && !avpkt->side_data);
if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
if ((avctx->flags & AV_CODEC_FLAG_PASS1) && avctx->stats_out)
avctx->stats_out[0] = '\0';
// ----- 1.检查图像尺寸 ----- //
if (av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx))
return AVERROR(EINVAL);
}
// ----- 2.获取packet ----- //
if (ffcodec(avctx->codec)->cb_type == FF_CODEC_CB_TYPE_RECEIVE_PACKET) {
ret = ffcodec(avctx->codec)->cb.receive_packet(avctx, avpkt);
if (ret < 0)
av_packet_unref(avpkt);
else
// Encoders must always return ref-counted buffers.
// Side-data only packets have no data and can be not ref-counted.
av_assert0(!avpkt->data || avpkt->buf);
} else
ret = encode_simple_receive_packet(avctx, avpkt);
if (ret >= 0)
avpkt->flags |= encode_ctx(avci)->intra_only_flag;
if (ret == AVERROR_EOF)
avci->draining_done = 1;
return ret;
}
2.6.3 分线程的编码上层函数(encode_simple_receive_packet —> encode_simple_internal)
以简单的编码操作函数encode_simple_receive_packet为例,主要调用了encode_simple_internal,其主要工作流程如下:
- 获取下一个编码帧(ff_encode_get_frame)
- 编码分两种情况,多线程编码(ff_thread_video_encode_frame)和正常编码(ff_encode_encode_cb)
static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
AVCodecInternal *avci = avctx->internal;
AVFrame *frame = avci->in_frame;
const FFCodec *const codec = ffcodec(avctx->codec);
int got_packet;
int ret;
if (avci->draining_done)
return AVERROR_EOF;
// ----- 1.由编码器调用以获取下一帧进行编码 ----- //
if (!frame->buf[0] && !avci->draining) {
av_frame_unref(frame);
ret = ff_encode_get_frame(avctx, frame);
if (ret < 0 && ret != AVERROR_EOF)
return ret;
}
if (!frame->buf[0]) {
if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
avci->frame_thread_encoder))
return AVERROR_EOF;
// Flushing is signaled with a NULL frame
frame = NULL;
}
got_packet = 0;
av_assert0(codec->cb_type == FF_CODEC_CB_TYPE_ENCODE);
// ----- 2.编码 ----- //
if (CONFIG_FRAME_THREAD_ENCODER && avci->frame_thread_encoder)
// 多线程编码
/* This will unref frame. */
ret = ff_thread_video_encode_frame(avctx, avpkt, frame, &got_packet);
else {
ret = ff_encode_encode_cb(avctx, avpkt, frame, &got_packet);
}
if (avci->draining && !got_packet)
avci->draining_done = 1;
return ret;
}
2.6.4 单线程编码主函数(ff_encode_encode_cb)
其中,ff_encode_encode_cb最主要的工作函数是调用了encode函数进行编码
int ff_encode_encode_cb(AVCodecContext *avctx, AVPacket *avpkt,
AVFrame *frame, int *got_packet)
{
const FFCodec *const codec = ffcodec(avctx->codec);
int ret;
// ----- 1.执行编码过程 ----- //
ret = codec->cb.encode(avctx, avpkt, frame, got_packet);
emms_c();
av_assert0(ret <= 0);
// ...
return ret;
}
这里调用的encode函数是一个函数指针,如果使用的是x264,其调用的函数应该是X264_frame。
FFCodec ff_libx264_encoder = {
.p.name = "libx264",
CODEC_LONG_NAME("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_H264,
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
AV_CODEC_CAP_OTHER_THREADS |
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE |
AV_CODEC_CAP_ENCODER_FLUSH |
AV_CODEC_CAP_ENCODER_RECON_FRAME,
.p.priv_class = &x264_class,
.p.wrapper_name = "libx264",
.priv_data_size = sizeof(X264Context),
.init = X264_init,
FF_CODEC_ENCODE_CB(X264_frame),
.flush = X264_flush,
.close = X264_close,
.defaults = x264_defaults,
#if X264_BUILD < 153
.init_static_data = X264_init_static,
#else
.p.pix_fmts = pix_fmts_all,
#endif
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS
#if X264_BUILD < 158
| FF_CODEC_CAP_NOT_INIT_THREADSAFE
#endif
,
};
2.6.5 核心编码函数(X264_frame)
该函数完成实际的264编码,假设调用了x264编码器。该函数位于libavcodec/libx264.c中,大致的流程为:
- 进行编码(x264_encoder_encode)
- 解析编码帧类型
- 判断是否是关键帧
- 计算编码帧的质量
此时,再向下走就进入了编码器内部,不再属于链路。编码器内部在其他文章中分析。下层的分析参考【x264】编码核心函数(x264_encoder_encode)的简单分析
static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame,
int *got_packet)
{
X264Context *x4 = ctx->priv_data;
x264_nal_t *nal;
int nnal, ret;
x264_picture_t pic_out = {0}, *pic_in;
int pict_type;
int64_t wallclock = 0;
X264Opaque *out_opaque;
ret = setup_frame(ctx, frame, &pic_in);
if (ret < 0)
return ret;
do {
// ----- 1.进行编码 ----- //
if (x264_encoder_encode(x4->enc, &nal, &nnal, pic_in, &pic_out) < 0)
return AVERROR_EXTERNAL;
if (nnal && (ctx->flags & AV_CODEC_FLAG_RECON_FRAME)) {
AVCodecInternal *avci = ctx->internal;
av_frame_unref(avci->recon_frame);
avci->recon_frame->format = csp_to_pixfmt(pic_out.img.i_csp);
if (avci->recon_frame->format == AV_PIX_FMT_NONE) {
av_log(ctx, AV_LOG_ERROR,
"Unhandled reconstructed frame colorspace: %d\n",
pic_out.img.i_csp);
return AVERROR(ENOSYS);
}
avci->recon_frame->width = ctx->width;
avci->recon_frame->height = ctx->height;
for (int i = 0; i < pic_out.img.i_plane; i++) {
avci->recon_frame->data[i] = pic_out.img.plane[i];
avci->recon_frame->linesize[i] = pic_out.img.i_stride[i];
}
ret = av_frame_make_writable(avci->recon_frame);
if (ret < 0) {
av_frame_unref(avci->recon_frame);
return ret;
}
}
ret = encode_nals(ctx, pkt, nal, nnal);
if (ret < 0)
return ret;
} while (!ret && !frame && x264_encoder_delayed_frames(x4->enc));
if (!ret)
return 0;
pkt->pts = pic_out.i_pts;
pkt->dts = pic_out.i_dts;
out_opaque = pic_out.opaque;
if (out_opaque >= x4->reordered_opaque &&
out_opaque < &x4->reordered_opaque[x4->nb_reordered_opaque]) {
wallclock = out_opaque->wallclock;
pkt->duration = out_opaque->duration;
if (ctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
pkt->opaque = out_opaque->frame_opaque;
pkt->opaque_ref = out_opaque->frame_opaque_ref;
out_opaque->frame_opaque_ref = NULL;
}
opaque_uninit(out_opaque);
} else {
// Unexpected opaque pointer on picture output
av_log(ctx, AV_LOG_ERROR, "Unexpected opaque pointer; "
"this is a bug, please report it.\n");
}
// ----- 2.解析X264类型 ----- //
switch (pic_out.i_type) {
case X264_TYPE_IDR:
case X264_TYPE_I:
pict_type = AV_PICTURE_TYPE_I;
break;
case X264_TYPE_P:
pict_type = AV_PICTURE_TYPE_P;
break;
case X264_TYPE_B:
case X264_TYPE_BREF:
pict_type = AV_PICTURE_TYPE_B;
break;
default:
av_log(ctx, AV_LOG_ERROR, "Unknown picture type encountered.\n");
return AVERROR_EXTERNAL;
}
// ----- 3.解析是否是关键帧 ----- //
pkt->flags |= AV_PKT_FLAG_KEY*pic_out.b_keyframe;
if (ret) {
int error_count = 0;
int64_t *errors = NULL;
int64_t sse[3] = {0};
if (ctx->flags & AV_CODEC_FLAG_PSNR) {
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(ctx->pix_fmt);
double scale[3] = { 1,
(double)(1 << pix_desc->log2_chroma_h) * (1 << pix_desc->log2_chroma_w),
(double)(1 << pix_desc->log2_chroma_h) * (1 << pix_desc->log2_chroma_w),
};
error_count = pix_desc->nb_components;
// ----- 4.计算编码质量 ----- //
for (int i = 0; i < pix_desc->nb_components; ++i) {
double max_value = (double)(1 << pix_desc->comp[i].depth) - 1.0;
double plane_size = ctx->width * (double)ctx->height / scale[i];
/* psnr = 10 * log10(max_value * max_value / mse) */
double mse = (max_value * max_value) / pow(10, pic_out.prop.f_psnr[i] / 10.0);
/* SSE = MSE * width * height / scale -> because of possible chroma downsampling */
sse[i] = (int64_t)floor(mse * plane_size + .5);
};
errors = sse;
}
ff_side_data_set_encoder_stats(pkt, (pic_out.i_qpplus1 - 1) * FF_QP2LAMBDA,
errors, error_count, pict_type);
if (wallclock)
ff_side_data_set_prft(pkt, wallclock);
}
*got_packet = ret;
return 0;
}
2.7 获取编码器输出的已编码信息(avcodec_receive_packet)
该函数的主要作用是获取已经编码的信息(存储在AVPacket中),函数的主要工作流程是:
- 检查输入变量是否存在
- 获取已经编码的信息(encode_receieve_packet_internal),这里调用的函数和2.6节中调用的函数相同,均调用了encode_receive_packet_internal。但是在这里进行的是获取操作,将获取的帧信息放到AVPacket当中。在实现上,如果已经调用了avcodec_send_frame,则pkt->data不为空,此时avcodec_receive_packet会返回这个pkt而不会执行编码
int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
{
AVCodecInternal *avci = avctx->internal;
int ret;
av_packet_unref(avpkt);
// ----- 1.检查输入变量是否存在 ----- //
if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
return AVERROR(EINVAL);
if (avci->buffer_pkt->data || avci->buffer_pkt->side_data) {
av_packet_move_ref(avpkt, avci->buffer_pkt);
} else {
// ----- 2.获取已经编码的信息 ----- //
ret = encode_receive_packet_internal(avctx, avpkt);
if (ret < 0)
return ret;
}
return 0;
}
3.小结
从编码链路这一整体系统上看,大致的输入是AVFrame,处理系统是AVCodec,记录信息是AVCodecContext,推送输入的引擎是avcodec_send_frame,接收输出的引擎是avcodec_receive_packet,输出是AVPacket。在这之中需要考虑的细节问题是:
-
输入的AVFrame是什么格式?
对于编码器而言,输入的AVFrame主要是以yuv420p的格式进行存储。通常的处理方式是读取yuv格式文件,随后将三通道分离存储到AVFrame当中。此外,还有一些其他的格式如NV12等等 -
处理系统AVCodec使用什么格式?
AVCodec可以使用的编码器包括H264、H265、AV1等等主流的编解码器,其中商业上最常用的就是H264格式的编码器(如x264),在某些场景下会使用H265编码器(如硬件设备属于高性能设备),AV1编码器暂时使用很少。这是因为软件算法和硬件设备的发展前后不一致,目前很多硬件设备仅具备H264硬件编码能力,更高级别的编码器能力还在发展中 -
记录信息AVCodecContext记录了哪些信息?
AVCodecContext记录了编码流程中的信息,包括码流帧的长宽,高度,AVCodec信息,Log信息,时间,色度格式等等一系列的信息,它纵览全局,是极其重要的结构体 -
推送输入的引擎是avcodec_send_frame如何处理输入信息?
在考虑输入信息时,分了很多个层级逐步处理。首先,检查这个输入是否存在;其次,输入是否是CB_RECEIVE_TYPE;随后,是否以多线程处理;最后,使用哪个编码器进行实际编码。在做二次开发时,可以先简单考虑具体使用哪个编码器,随后再考虑线程问题 -
输出引擎avcodec_receive_packet如何接收输出信息?
在考虑输出信息时,比较简单,只需要从已经编码的结构体当中去取出编码帧即可。但是这里调用了一个与输入引擎相同的函数(encode_receive_packet_internal),这个函数会判断是否是取出操作,感觉是将二者合并。早期的FFmpeg版本当中,是将推送引擎和接收引擎分开的 -
输出AVPacket何时有效?
在输出引擎能够正常输出时既有效,并且数据存储在data中,可以正常存储为yuv格式,也可以直接使用sdl进行在线播放。通常来说,在线视频软件(如会议、直播、点播等)都是使用SDL直接播放的
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen