本篇的主角是公共解码函数:
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);
}
}这样,解码部分我们就有点数了,后续我们再看看对音频、视频的解码还做了哪些处理呢
夜雨聆风