回顾下上文read_thread
中流程分析
- 打开文件,检测Stream信息
- 循环等待start接口调用,进入播放的流程
- 打开音频播放器,创建音频解码线程audio_thread; 创建视频解码器,创建视频解码线程video_thread; 创建字幕解码线程subtitle_thread;
- 循环读取Packet,解封装,并存入PacketQueue
视频解码——video_thead
简化代码如下(删除滤镜相关代码,先不做分析)
static int video_thread(void *arg)
{
AVRational tb = is->video_st->time_base;
AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
for (;;) {
ret = get_video_frame(is, frame); //解码获取一帧视频画面
if (ret < 0)//解码结束
goto the_end;
if (!ret)//没有解码得到画面
continue;
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);//用帧率估计帧时长
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);//将pts转化为秒为单位
ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);//将解码后的帧存入FrameQueue
av_frame_unref(frame);
if (ret < 0)
goto the_end;
}
the_end:
av_frame_free(&frame);
return 0;
}
这样流程比较清晰
- 调用
get_video_frame
方法,获取一帧解码的视频画面,赋值到frame
中 - 计算
duration
和pts
- 调用
queue_picture
将解码后帧存入FrameQueue
get_video_frame
static int get_video_frame(VideoState *is, AVFrame *frame)
{
int got_picture;
if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
return -1;
if (got_picture) {
frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
}
//丢帧处理
if (got_picture) {
double dpts = NAN;
if (frame->pts != AV_NOPTS_VALUE)
dpts = av_q2d(is->video_st->time_base) * frame->pts;
frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
ffp->stat.decode_frame_count++;
if (frame->pts != AV_NOPTS_VALUE) {
double diff = dpts - get_master_clock(is);
if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
diff - is->frame_last_filter_delay < 0 &&
is->viddec.pkt_serial == is->vidclk.serial &&
is->videoq.nb_packets) {
is->frame_drops_early++;
is->continuous_frame_drops_early++;
if (is->continuous_frame_drops_early > ffp->framedrop) {
is->continuous_frame_drops_early = 0;
} else {
ffp->stat.drop_frame_count++;
ffp->stat.drop_frame_rate = (float)(ffp->stat.drop_frame_count) / (float)(ffp->stat.decode_frame_count);
av_frame_unref(frame);
got_picture = 0;
}
}
}
}
}
return got_picture;
}
流程大概如下:
- 调用
decoder_decode_frame
进行解码操作,获取解码后的数据 - 如果解码成功,在一定条件下,进入丢帧策略
if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER))
{ "framedrop", "drop frames when cpu is too slow",
OPTION_OFFSET(framedrop), OPTION_INT(0, -1, 120) },
条件满足二者其一即可
framedrop>0
ffp->framedrop <0
并且当前的同步时钟类型不是video
的时候,在ffplay_options
中,默认framedrop
为-1
decoder_decode_frame
简化代码如下
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
for (;;) {
//1. 流连续的情况下,不断调用avcodec_receive_frame获取解码后的frame
if (d->queue->serial == d->pkt_serial) {
do {
ret = avcodec_receive_frame(d->avctx, frame);
if (ret == AVERROR_EOF) {
return 0;
}
if (ret >= 0)
return 1;
} while (ret != AVERROR(EAGAIN));
}
//2. 取一个packet
do {
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
if (d->packet_pending) {
av_packet_move_ref(&pkt, &d->pkt);
d->packet_pending = 0;
}else{
if (packet_queue_get_or_buffering(d->queue, &pkt, 1, &d->pkt_serial) < 0)
return -1;
}
} while (d->queue->serial != d->pkt_serial);
//3. 将packet送入解码器
avcodec_send_packet(d->avctx, &pkt);
}
}
- 序列号相同的情况下,调用
avcodec_receive_frame
获取解码后的帧,失败跳出循环 - 如果
d->queue->nb_packets
为空,则发送SDL_CondSignal(d->empty_queue_cond)
信号,表示队列为空,通知读线程继续读数据,否则,调用packet_queue_get_or_buffering
获取一个packet
,此方法是一个阻塞方法 - 将
packet
送入解码器 这里有一个d->packet_pending
的概念,表示在send_packet
的时候,失败的packet.
if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
d->packet_pending = 1;
av_packet_move_ref(&d->pkt, &pkt);
}
省略代码中还有针对flush_pkt
的处理:
if (pkt.data == flush_pkt.data) {
avcodec_flush_buffers(d->avctx);
d->finished = 0;
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
}
了解过PacketQueue的代码,我们知道在往PacketQueue送入一个flush_pkt后,PacketQueue的serial值会加1,而送入的flush_pkt和PacketQueue的serial值保持一致。所以如果有“过时”Packet,过滤后,取到的第一个pkt将是flush_pkt。
根据分析的代码,已经可以获取到一帧解码好的数据了,接下来就需要将frame
放入队列之中
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
av_frame_unref(frame);
queue_picture
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
{
Frame *vp;
if (!(vp = frame_queue_peek_writable(&is->pictq)))
return -1;
vp->sar = src_frame->sample_aspect_ratio;
vp->uploaded = 0;
vp->width = src_frame->width;
vp->height = src_frame->height;
vp->format = src_frame->format;
vp->pts = pts;
vp->duration = duration;
vp->pos = pos;
vp->serial = serial;
set_default_window_size(vp->width, vp->height, vp->sar);
av_frame_move_ref(vp->frame, src_frame);
frame_queue_push(&is->pictq);
return 0;
}
frame_queue_peek_writable
取出当前队列的写节点- 简单的赋值操作,然后把该拷贝的拷贝给节点(struct Frame)保存,然后
frame_queue_push
,push节点到队列中。
音频/字幕解码流程大同小异,就不重复分析了。