【FFmpeg】解码链路上主要函数的简单分析
- 1. 主要工作流程
- 2. 解码过程
- 3.小结
示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染
流程分析:
【FFmpeg】编码链路上主要函数的简单分析
1. 主要工作流程
本文分析的FFmpeg版本是FFmpeg-7.0,只考虑视频解码部分的流程,不考虑音频部分
FFmpeg进行解码器的调用时,主要工作流程大约是
解码器:
- 查找解码器(avcodec_find_decoder)
- 创建解码器上下文(avcodec_alloc_context3)
- 创建输入信息的结构体(av_packet_alloc)
- 创建输出信息的结构体(av_frame_alloc)
- 创建解析器(av_parser_init)
- 打开解码器(avcodec_open2)
- 将输入帧填充到packet当中(…)
- 解析每一个帧的头信息(av_parser_parse2)
- 将输入帧送入解码器进行解码(avcodec_send_packet)
- 获取解码器输出的已解码信息(avcodec_receive_frame)
- 释放结构体信息(…)
2. 解码过程
2.1 查找解码器(avcodec_find_decoder)
该函数的主要功能是根据AVCodecID查找解码器,并返回一个AVCodec*。这个过程与avcodec_find_encoder一致,只不过查找的codec_list是解码器的codec_list
const AVCodec *avcodec_find_decoder(enum AVCodecID id)
{
return find_codec(id, av_codec_is_decoder);
}
2.2 创建解码器上下文(avcodec_alloc_context3)
与编码器一致
2.3 创建输入信息的结构体(av_packet_alloc)
与编码器一致
2.4 创建输出信息的结构体(av_frame_alloc)
与编码器一致
2.5 创建解析器(av_parser_init)
该函数用于创建一个解析器,该解析器能够解析码流文件的头信息。函数位于libavcodec/parser.c。主要流程分如下:
- 获取parser_list中的parser,查看是否与输入的codec_id匹配
- 找到对应的parser,进行初始化
- 根据特定的解码器进行初始化(parser_init)
AVCodecParserContext *av_parser_init(int codec_id)
{
AVCodecParserContext *s = NULL;
const AVCodecParser *parser;
void *i = 0;
int ret;
if (codec_id == AV_CODEC_ID_NONE)
return NULL;
// ----- 1.获取parser_list中的parser,查看是否与输入的codec_id匹配 ----- //
while ((parser = av_parser_iterate(&i))) {
if (parser->codec_ids[0] == codec_id ||
parser->codec_ids[1] == codec_id ||
parser->codec_ids[2] == codec_id ||
parser->codec_ids[3] == codec_id ||
parser->codec_ids[4] == codec_id ||
parser->codec_ids[5] == codec_id ||
parser->codec_ids[6] == codec_id)
goto found;
}
return NULL;
// ----- 2.找到对应的parser,进行初始化 ----- //
found:
s = av_mallocz(sizeof(AVCodecParserContext));
if (!s)
goto err_out;
s->parser = parser;
s->priv_data = av_mallocz(parser->priv_data_size);
if (!s->priv_data)
goto err_out;
s->fetch_timestamp=1;
s->pict_type = AV_PICTURE_TYPE_I;
if (parser->parser_init) {
// ----- 3.解析器的初始化 ----- //
ret = parser->parser_init(s);
if (ret != 0)
goto err_out;
}
s->key_frame = -1;
s->dts_sync_point = INT_MIN;
s->dts_ref_dts_delta = INT_MIN;
s->pts_dts_delta = INT_MIN;
s->format = -1;
return s;
err_out:
if (s)
av_freep(&s->priv_data);
av_free(s);
return NULL;
}
2.6 打开解码器(avcodec_open2)
与编码器一致
2.7 解析每一个帧的头信息(av_parser_parse2)
该函数用于解析帧的头部信息,主要工作流程为:
- 检查codec_id和offset等信息
- 使用特定的解析器进行解析(parser_parse)
- 更新offset,用以更新下一帧的头信息
// 解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据
// poutbuf: 解析后输出的压缩编码数据帧
// buf: 解析前的压缩编码数据帧
// 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。
// 当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts, int64_t pos)
{
int index, i;
uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];
// 检查当前的codec id是否不为空
av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);
/* Parsers only work for the specified codec ids. */
// ----- 1.检查当前的parser的codec类型 ----- //
av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||
avctx->codec_id == s->parser->codec_ids[1] ||
avctx->codec_id == s->parser->codec_ids[2] ||
avctx->codec_id == s->parser->codec_ids[3] ||
avctx->codec_id == s->parser->codec_ids[4] ||
avctx->codec_id == s->parser->codec_ids[5] ||
avctx->codec_id == s->parser->codec_ids[6]);
/* 第一次进入时,flags为0,会进入if将offset设置成当前pkt的pos */
if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
s->next_frame_offset =
s->cur_offset = pos;
s->flags |= PARSER_FLAG_FETCHED_OFFSET;
}
if (buf_size == 0) {
/* padding is always necessary even if EOF, so we add it here */
memset(dummy_buf, 0, sizeof(dummy_buf));
buf = dummy_buf;
} else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
/* add a new packet descriptor */
// 保留一下cur_offset, frame_end 信息, 有4个槽位供使用, 方便知道数据偏移量
i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
s->cur_frame_start_index = i;
s->cur_frame_offset[i] = s->cur_offset;
s->cur_frame_end[i] = s->cur_offset + buf_size;
s->cur_frame_pts[i] = pts;
s->cur_frame_dts[i] = dts;
s->cur_frame_pos[i] = pos;
}
if (s->fetch_timestamp) {
s->fetch_timestamp = 0;
s->last_pts = s->pts;
s->last_dts = s->dts;
s->last_pos = s->pos;
ff_fetch_timestamp(s, 0, 0, 0);
}
// ----- 2.使用特定的解析器进行解析 ----- //
/* WARNING: the returned index can be negative */
index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
poutbuf_size, buf, buf_size);
av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
FILL(field_order);
FILL(coded_width);
FILL(coded_height);
FILL(width);
FILL(height);
}
// ----- 3.更新offset,用以更新下一帧的头信息 ----- //
/* update the file pointer */
if (*poutbuf_size) {
/* fill the data for the current frame */
s->frame_offset = s->next_frame_offset;
/* offset of the next frame */
s->next_frame_offset = s->cur_offset + index;
s->fetch_timestamp = 1;
} else {
/* Don't return a pointer to dummy_buf. */
*poutbuf = NULL;
}
if (index < 0)
index = 0;
s->cur_offset += index;
return index;
}
2.8 将输入帧送入解码器进行解码(avcodec_send_packet)
除去一些必要的检查之外,解码过程的主要函数调用流程为:
- 解码主函数(avcodec_send_packet)
- 分接收类型的解码获取帧(decode_receive_frame_internal)
- 内部解码获取帧函数(decode_simple_receive_frame)
- 分线程的解码函数(decode_simple_internal)
- 核心解码函数(h264_decode_frame)
2.8.1 解码主函数(avcodec_send_packet)
该函数的功能是将输入的帧(码流)送入解码器进行解码,其主要流程如下:
- 检查参数
- 解码获取帧(decode_receive_frame_internal)
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
AVCodecInternal *avci = avctx->internal;
DecodeContext *dc = decode_ctx(avci);
int ret;
// ----- 1.检查参数 ----- //
if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
return AVERROR(EINVAL);
if (dc->draining_started)
return AVERROR_EOF;
if (avpkt && !avpkt->size && avpkt->data)
return AVERROR(EINVAL);
if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
if (!AVPACKET_IS_EMPTY(avci->buffer_pkt))
return AVERROR(EAGAIN);
ret = av_packet_ref(avci->buffer_pkt, avpkt);
if (ret < 0)
return ret;
} else
dc->draining_started = 1;
if (!avci->buffer_frame->buf[0] && !dc->draining_started) {
// ----- 2.解码获取帧 ----- //
ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
}
return 0;
}
2.8.2 分接收类型的解码获取帧(decode_receive_frame_internal)
如果cb_type类型为FF_CODEC_CB_TYPE_RECEIVE_FRAME,则调用receive_frame;否则调用decode_simple_receive_frame
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
{
AVCodecInternal *avci = avctx->internal;
DecodeContext *dc = decode_ctx(avci);
const FFCodec *const codec = ffcodec(avctx->codec);
int ret, ok;
av_assert0(!frame->buf[0]);
if (codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_FRAME) {
ret = codec->cb.receive_frame(avctx, frame);
emms_c();
if (!ret) {
if (avctx->codec->type == AVMEDIA_TYPE_VIDEO)
ret = (frame->flags & AV_FRAME_FLAG_DISCARD) ? AVERROR(EAGAIN) : 0;
else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
int64_t discarded_samples = 0;
ret = discard_samples(avctx, frame, &discarded_samples);
}
}
} else
ret = decode_simple_receive_frame(avctx, frame);
if (ret == AVERROR_EOF)
avci->draining_done = 1;
/* preserve ret */
ok = detect_colorspace(avctx, frame);
if (ok < 0) {
av_frame_unref(frame);
return ok;
}
if (!ret) {
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
if (!frame->width)
frame->width = avctx->width;
if (!frame->height)
frame->height = avctx->height;
} else
frame->flags |= AV_FRAME_FLAG_KEY;
ret = fill_frame_props(avctx, frame);
if (ret < 0) {
av_frame_unref(frame);
return ret;
}
#if FF_API_FRAME_KEY
FF_DISABLE_DEPRECATION_WARNINGS
frame->key_frame = !!(frame->flags & AV_FRAME_FLAG_KEY);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
#if FF_API_INTERLACED_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
frame->interlaced_frame = !!(frame->flags & AV_FRAME_FLAG_INTERLACED);
frame->top_field_first = !!(frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
frame->best_effort_timestamp = guess_correct_pts(dc,
frame->pts,
frame->pkt_dts);
/* the only case where decode data is not set should be decoders
* that do not call ff_get_buffer() */
av_assert0((frame->private_ref && frame->private_ref->size == sizeof(FrameDecodeData)) ||
!(avctx->codec->capabilities & AV_CODEC_CAP_DR1));
if (frame->private_ref) {
FrameDecodeData *fdd = (FrameDecodeData*)frame->private_ref->data;
if (fdd->post_process) {
ret = fdd->post_process(avctx, frame);
if (ret < 0) {
av_frame_unref(frame);
return ret;
}
}
}
}
/* free the per-frame decode data */
av_buffer_unref(&frame->private_ref);
return ret;
}
2.8.3 内部解码获取帧函数(decode_simple_receive_frame)
如果frame的buf不存在,说明需要进行解码,调用decode_simple_internal
static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
int ret;
int64_t discarded_samples = 0;
while (!frame->buf[0]) {
if (discarded_samples > avctx->max_samples)
return AVERROR(EAGAIN);
// ----- 解码 ----- //
ret = decode_simple_internal(avctx, frame, &discarded_samples);
if (ret < 0)
return ret;
}
return 0;
}
2.8.4 分线程的解码函数(decode_simple_internal)
从注解看,该函数是用于实现简单API的解码器的receive_frame_wrapper的核心。某些解码器可能会消耗部分数据包而不返回任何输出,因此需要在循环中调用此函数,直到它返回EAGAIN
函数的主要流程为:
- 如果解码数据包不存在,则寻找下一个要解码的数据包
- 执行解码操作,decode(单线程)和ff_thread_decode_frame(多线程)
- 清空输出队列
- 更新时间戳
static inline int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame, int64_t *discarded_samples)
{
AVCodecInternal *avci = avctx->internal;
AVPacket *const pkt = avci->in_pkt;
const FFCodec *const codec = ffcodec(avctx->codec);
int got_frame, consumed;
int ret;
// ----- 1.获取下一个要解码的数据包 ----- //
if (!pkt->data && !avci->draining) {
av_packet_unref(pkt);
ret = ff_decode_get_packet(avctx, pkt);
if (ret < 0 && ret != AVERROR_EOF)
return ret;
}
// Some codecs (at least wma lossless) will crash when feeding drain packets
// after EOF was signaled.
if (avci->draining_done)
return AVERROR_EOF;
if (!pkt->data &&
!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
avctx->active_thread_type & FF_THREAD_FRAME))
return AVERROR_EOF;
got_frame = 0;
// ----- 2.执行解码操作 ----- //
if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {
consumed = ff_thread_decode_frame(avctx, frame, &got_frame, pkt);
} else {
consumed = codec->cb.decode(avctx, frame, &got_frame, pkt);
if (!(codec->caps_internal & FF_CODEC_CAP_SETS_PKT_DTS))
frame->pkt_dts = pkt->dts;
if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
if(!avctx->has_b_frames)
frame->pkt_pos = pkt->pos;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
}
}
emms_c();
if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
ret = (!got_frame || frame->flags & AV_FRAME_FLAG_DISCARD)
? AVERROR(EAGAIN)
: 0;
} else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
ret = !got_frame ? AVERROR(EAGAIN)
: discard_samples(avctx, frame, discarded_samples);
}
if (ret == AVERROR(EAGAIN))
av_frame_unref(frame);
// FF_CODEC_CB_TYPE_DECODE decoders must not return AVERROR EAGAIN
// code later will add AVERROR(EAGAIN) to a pointer
av_assert0(consumed != AVERROR(EAGAIN));
if (consumed < 0)
ret = consumed;
if (consumed >= 0 && avctx->codec->type == AVMEDIA_TYPE_VIDEO)
consumed = pkt->size;
if (!ret)
av_assert0(frame->buf[0]);
if (ret == AVERROR(EAGAIN))
ret = 0;
// ----- 3.清空输出队列 ----- //
/* do not stop draining when got_frame != 0 or ret < 0 */
if (avci->draining && !got_frame) {
if (ret < 0) {
/* prevent infinite loop if a decoder wrongly always return error on draining */
/* reasonable nb_errors_max = maximum b frames + thread count */
int nb_errors_max = 20 + (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME ?
avctx->thread_count : 1);
if (decode_ctx(avci)->nb_draining_errors++ >= nb_errors_max) {
av_log(avctx, AV_LOG_ERROR, "Too many errors when draining, this is a bug. "
"Stop draining and force EOF.\n");
avci->draining_done = 1;
ret = AVERROR_BUG;
}
} else {
avci->draining_done = 1;
}
}
// ----- 4.更新时间戳 ----- //
if (consumed >= pkt->size || ret < 0) {
av_packet_unref(pkt);
} else {
pkt->data += consumed;
pkt->size -= consumed;
pkt->pts = AV_NOPTS_VALUE;
pkt->dts = AV_NOPTS_VALUE;
if (!(codec->caps_internal & FF_CODEC_CAP_SETS_FRAME_PROPS)) {
#if FF_API_FRAME_PKT
// See extract_packet_props() comment.
avci->last_pkt_props->stream_index = avci->last_pkt_props->stream_index - consumed;
#endif
avci->last_pkt_props->pts = AV_NOPTS_VALUE;
avci->last_pkt_props->dts = AV_NOPTS_VALUE;
}
}
return ret;
}
这里核心的解码函数为decode,这是一个函数指针,假如使用的是h264解码器,则会调用ff_h264_decoder结构体内的解码器函数,即h264_decode_frame
const FFCodec ff_h264_decoder = {
.p.name = "h264",
CODEC_LONG_NAME("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_H264,
.priv_data_size = sizeof(H264Context),
.init = h264_decode_init,
.close = h264_decode_end,
FF_CODEC_DECODE_CB(h264_decode_frame),
.p.capabilities = AV_CODEC_CAP_DR1 |
AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
AV_CODEC_CAP_FRAME_THREADS,
.hw_configs = (const AVCodecHWConfigInternal *const []) {
// 下面是一些硬件解码器的使用
#if CONFIG_H264_DXVA2_HWACCEL
HWACCEL_DXVA2(h264),
#endif
#if CONFIG_H264_D3D11VA_HWACCEL
HWACCEL_D3D11VA(h264),
#endif
#if CONFIG_H264_D3D11VA2_HWACCEL
HWACCEL_D3D11VA2(h264),
#endif
#if CONFIG_H264_D3D12VA_HWACCEL
HWACCEL_D3D12VA(h264),
#endif
#if CONFIG_H264_NVDEC_HWACCEL
HWACCEL_NVDEC(h264),
#endif
#if CONFIG_H264_VAAPI_HWACCEL
HWACCEL_VAAPI(h264),
#endif
#if CONFIG_H264_VDPAU_HWACCEL
HWACCEL_VDPAU(h264),
#endif
#if CONFIG_H264_VIDEOTOOLBOX_HWACCEL
HWACCEL_VIDEOTOOLBOX(h264),
#endif
#if CONFIG_H264_VULKAN_HWACCEL
HWACCEL_VULKAN(h264),
#endif
NULL
},
.caps_internal = FF_CODEC_CAP_EXPORTS_CROPPING |
FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
.flush = h264_decode_flush,
UPDATE_THREAD_CONTEXT(ff_h264_update_thread_context),
UPDATE_THREAD_CONTEXT_FOR_USER(ff_h264_update_thread_context_for_user),
.p.profiles = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
.p.priv_class = &h264_class,
};
2.8.5 核心解码函数(h264_decode_frame)
以h264解码为例,会调用h264_decode_frame函数执行具体的解码操作。
主要的工作流程为:
- 读取到流的末尾,输出buffer中剩余的信息
- 检查是否需要解码额外的信息
- 解码nal信息(decode_nal_unit)
- 催促去获得下一个延时的帧,因为此时的帧不存在并且达到了文件的末尾
- 计算消耗的比特数
这个过程中,解码nal信息是最关键函数,直接解码数据包当中的信息。这是链路上的最底层函数,再向下走就是解码器内部的函数,将在其他文章中记录。从代码上看,在FFmpeg中,H264的软解是有具体实现的,但是H264的软编需要额外编译x264的库
static int h264_decode_frame(AVCodecContext *avctx, AVFrame *pict,
int *got_frame, AVPacket *avpkt)
{
const uint8_t *buf = avpkt->data;
int buf_size = avpkt->size;
H264Context *h = avctx->priv_data;
int buf_index;
int ret;
h->flags = avctx->flags;
h->setup_finished = 0;
h->nb_slice_ctx_queued = 0;
ff_h264_unref_picture(&h->last_pic_for_ec);
// ----- 1.读取到流的末尾,输出buffer中剩余的信息 ----- //
/* end of stream, output what is still in the buffers */
if (buf_size == 0)
return send_next_delayed_frame(h, pict, got_frame, 0);
// ----- 2.检查是否需要解码额外的信息 ----- //
if (av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, NULL)) {
size_t side_size;
uint8_t *side = av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
ff_h264_decode_extradata(side, side_size,
&h->ps, &h->is_avc, &h->nal_length_size,
avctx->err_recognition, avctx);
}
if (h->is_avc && buf_size >= 9 && buf[0]==1 && buf[2]==0 && (buf[4]&0xFC)==0xFC) {
if (is_avcc_extradata(buf, buf_size))
return ff_h264_decode_extradata(buf, buf_size,
&h->ps, &h->is_avc, &h->nal_length_size,
avctx->err_recognition, avctx);
}
// ----- 3.解码nal信息 ----- //
buf_index = decode_nal_units(h, buf, buf_size);
if (buf_index < 0)
return AVERROR_INVALIDDATA;
// ----- 4.催促去获得下一个延时的帧,因为此时的帧不存在并且达到了文件的末尾 ----- //
if (!h->cur_pic_ptr && h->nal_unit_type == H264_NAL_END_SEQUENCE) {
av_assert0(buf_index <= buf_size);
return send_next_delayed_frame(h, pict, got_frame, buf_index);
}
if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) && (!h->cur_pic_ptr || !h->has_slice)) {
if (avctx->skip_frame >= AVDISCARD_NONREF ||
buf_size >= 4 && !memcmp("Q264", buf, 4))
return buf_size;
av_log(avctx, AV_LOG_ERROR, "no frame!\n");
return AVERROR_INVALIDDATA;
}
if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) ||
(h->mb_y >= h->mb_height && h->mb_height)) {
if ((ret = ff_h264_field_end(h, &h->slice_ctx[0], 0)) < 0)
return ret;
/* Wait for second field. */
if (h->next_output_pic) {
ret = finalize_frame(h, pict, h->next_output_pic, got_frame);
if (ret < 0)
return ret;
}
}
av_assert0(pict->buf[0] || !*got_frame);
ff_h264_unref_picture(&h->last_pic_for_ec);
// ----- 5.计算消耗的比特数 ----- //
return get_consumed_bytes(buf_index, buf_size);
}
2.9 获取解码器输出的已解码信息(avcodec_receive_frame)
从注解看,该函数实现从解码器或编码器返回解码的输出数据(当使用@ref AV_CODEC_FLAG_RECON_FRAME标志时)
函数首先判断是否是解码器,如果是则进入解码流程(ff_decode_receive_frame),否则进入编码流程(ff_encode_receive_frame)
int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
av_frame_unref(frame);
if (av_codec_is_decoder(avctx->codec))
return ff_decode_receive_frame(avctx, frame);
return ff_encode_receive_frame(avctx, frame);
}
这里获取解码帧(ff_decode_receive_frame),主要工作流程为:
- 检查buf后,获取解码帧(decode_receive_frame_internal),这里与送入解码器解码使用的函数相同,但区别是这里只取帧,而不进行解码
- 检查帧是否有效(frame_validate)
- 查看输出分辨率是否变化
int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
AVCodecInternal *avci = avctx->internal;
int ret;
if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
return AVERROR(EINVAL);
// ----- 1.检查buf后,获取解码帧 ----- //
if (avci->buffer_frame->buf[0]) {
av_frame_move_ref(frame, avci->buffer_frame);
} else {
ret = decode_receive_frame_internal(avctx, frame);
if (ret < 0)
return ret;
}
// ----- 2.检查帧是否有效 ----- //
ret = frame_validate(avctx, frame);
if (ret < 0)
goto fail;
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
ret = apply_cropping(avctx, frame);
if (ret < 0)
goto fail;
}
avctx->frame_num++;
// ----- 3.查看输出分辨率是否变化 ----- //
#if FF_API_DROPCHANGED
if (avctx->flags & AV_CODEC_FLAG_DROPCHANGED) {
if (avctx->frame_num == 1) {
avci->initial_format = frame->format;
switch(avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO:
avci->initial_width = frame->width;
avci->initial_height = frame->height;
break;
case AVMEDIA_TYPE_AUDIO:
avci->initial_sample_rate = frame->sample_rate ? frame->sample_rate :
avctx->sample_rate;
ret = av_channel_layout_copy(&avci->initial_ch_layout, &frame->ch_layout);
if (ret < 0)
goto fail;
break;
}
}
if (avctx->frame_num > 1) {
int changed = avci->initial_format != frame->format;
switch(avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO:
changed |= avci->initial_width != frame->width ||
avci->initial_height != frame->height;
break;
case AVMEDIA_TYPE_AUDIO:
changed |= avci->initial_sample_rate != frame->sample_rate ||
avci->initial_sample_rate != avctx->sample_rate ||
av_channel_layout_compare(&avci->initial_ch_layout, &frame->ch_layout);
break;
}
if (changed) {
avci->changed_frames_dropped++;
av_log(avctx, AV_LOG_INFO, "dropped changed frame #%"PRId64" pts %"PRId64
" drop count: %d \n",
avctx->frame_num, frame->pts,
avci->changed_frames_dropped);
ret = AVERROR_INPUT_CHANGED;
goto fail;
}
}
}
#endif
return 0;
fail:
av_frame_unref(frame);
return ret;
}
3.小结
从视频编解码的角度上看,解码流程比编码流程简单,速度更快,但是需要进行额外的头信息分析。此外,对于264解码而言,FFmpeg内部有封装,无需额外的编译库,解码之后的信息,可以直接使用获取的AVPacket,调用SDL2进行播放,实现播放器的功能
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen