乐于分享
好东西不私藏

认真读读ffplay源码(五):公共解码部分

认真读读ffplay源码(五):公共解码部分

本篇的主角是公共解码函数:

decoder_decode_frame

音频、视频、字幕解码过程中都会用到它,先理解它我觉得很关键。

解码

解码过程中有个经常出现的错误

AVERROR(EAGAIN);

表示资源暂时不可用,需要稍候再重试,具体要分两种情况来看:

avcodec_send_packet

当发送数据报这个错误时,表示编解码器内部缓冲区已满,需要先拿走产出的数据后再发送新的数据。

avcodec_receive_frame

当接收解码帧报这个错误时,解码器需要更多输入数据(packet)才能产生一帧完整的输出。

接下来进入主题:
解码函数由一个循环构成,要么报错退出,要么拿到一帧解码数据。

staticintdecoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
int ret = AVERROR(EAGAIN);
for (;;) {
//解码部分
    }
}

然后就是接收解码帧,给解码器发送包数据放在了后面,稍后会看到:

// 是同一序列的
if (d->queue->serial == d->pkt_serial) {
do {
// 中断请求
if (d->queue->abort_request)
return-1;

switch (d->avctx->codec_type) {
// 视频解码
case AVMEDIA_TYPE_VIDEO:
                ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
if (decoder_reorder_pts == -1) {
// 使用兜底时间戳,或者叫尽力而为的时间戳
                        frame->pts = frame->best_effort_timestamp;
                    } elseif (!decoder_reorder_pts) {
// 使用解码时间戳作为显示时间戳
                        frame->pts = frame->pkt_dts;
                    }
                }
break;
// 音频解码
case AVMEDIA_TYPE_AUDIO:
                ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
// 用采样率做时间基
                    AVRational tb = (AVRational){1, frame->sample_rate};
if (frame->pts != AV_NOPTS_VALUE)
                        frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
// 用预测值做时间戳
elseif (d->next_pts != AV_NOPTS_VALUE)
                        frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
if (frame->pts != AV_NOPTS_VALUE) {
// 预测下一个帧的时间戳
                        d->next_pts = frame->pts + frame->nb_samples;
                        d->next_pts_tb = tb;
                    }
                }
break;
        }
// 流结束退出循环
if (ret == AVERROR_EOF) {
            d->finished = d->pkt_serial;
            avcodec_flush_buffers(d->avctx);
return0;
        }
// 拿到正常帧,结束循环
if (ret >= 0)
return1
// 是EAGAIN 跳出循环
    } while (ret != AVERROR(EAGAIN));
}

再往下,又被一个循环拦下:

do {
// 判断有没有可用包,没有发信号要
if (d->queue->nb_packets == 0)
        SDL_CondSignal(d->empty_queue_cond);
// 当前包还可再发给解码器,当发送包遇到EAGAIN,packet_pending会置为1
if (d->packet_pending) {
        d->packet_pending = 0;
    } else {
// 从队列取新包
int old_serial = d->pkt_serial;
if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
return-1;
if (old_serial != d->pkt_serial) {
            avcodec_flush_buffers(d->avctx);
            d->finished = 0;
            d->next_pts = d->start_pts;
            d->next_pts_tb = d->start_pts_tb;
        }
    }
// 是通过一个序列,中断循环
if (d->queue->serial == d->pkt_serial)
break;
// 序列变了,那当前这个包也就没用,果断丢弃。
    av_packet_unref(d->pkt);
while (1);

最后终于来到了把包发给解码器的环节了:

// 字幕
if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
int got_frame = 0;
// 解码字幕
    ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, d->pkt);
if (ret < 0) {
// 字幕解码失败,无伤大雅, 全部转成EAGAIN
        ret = AVERROR(EAGAIN);
    } else {
// got_frame == 1 表示字幕解码成功
// got_frame == 0 d->pkt->data != 0 数据还在,但没输出帧
// got_frame == 0 d->pkt->data == 0 数据已用完,但没输出帧
if (got_frame && !d->pkt->data) {
            d->packet_pending = 1;
        }
        ret = got_frame ? 0 : (d->pkt->data ? AVERROR(EAGAIN) : AVERROR_EOF);
    }
// 释放包
    av_packet_unref(d->pkt);
else { // 音频、视频
// 这里的目的是把包的pos传递到frame中,方便后续处理
if (d->pkt->buf && !d->pkt->opaque_ref) {
        FrameData *fd;

        d->pkt->opaque_ref = av_buffer_allocz(sizeof(*fd));
if (!d->pkt->opaque_ref)
return AVERROR(ENOMEM);
        fd = (FrameData*)d->pkt->opaque_ref->data;
        fd->pkt_pos = d->pkt->pos;
    }
// 最后才是把包发给解码器
if (avcodec_send_packet(d->avctx, d->pkt) == AVERROR(EAGAIN)) {
        av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
// 遇到EAGAIN,说明解码器内部缓冲区已满,需要拿走解码帧再重发一次
        d->packet_pending = 1;
    } else {
// 发送包成功,释放包
        av_packet_unref(d->pkt);
    }
}

这样,解码部分我们就有点数了,后续我们再看看对音频、视频的解码还做了哪些处理呢