ffplay解码线程分析

视频解码线程

轮询

不断从Packet队列中取出一帧数据进行解码,然后将解码数据放入到Frame队列中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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转换为秒
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);
...

if (ret < 0) goto the_end;
}

解码

解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
int ret = AVERROR(EAGAIN);
// 轮询解码,直到解码一帧成功
// 1. 先尝试从解码器中获取,获取成功则直接返回
// 2. 不成功,则从Packet队列中获取一帧数据,顺便丢弃一些过时数据
// 3. 将获取的一帧数据送给解码器进行解码
for (;;) {
AVPacket pkt;
// 比较Packet队列的序号是否相同,是否是过时的数据
if (d->queue->serial == d->pkt_serial) {
// 不断的尝试从解码器中获取已经解码完成的帧,直到获取成功
do {
// Packet队列是否退出
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) {
// 获取成功,重置PTS
if (decoder_reorder_pts == -1) {
frame->pts = frame->best_effort_timestamp;
} else if (!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};
// 获取成功,重置PTS
if (frame->pts != AV_NOPTS_VALUE)
frame->pts =
av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
else if (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) {
// 如果流解码结束,记录本次结束的Packet队列的序号
d->finished = d->pkt_serial;
// 刷新解码上下文,以便再次解码,不刷新则不能再解码了
avcodec_flush_buffers(d->avctx);
return 0;
}
// 如果获取成功,则直接返回
if (ret >= 0) return 1;
} while (ret != AVERROR(EAGAIN));
}
// 如果Packet队列的序号不相同,则开始丢弃这些过时的数据
do {
// 如果Packet队列中已经空了,则唤醒read线程继续读取数据
if (d->queue->nb_packets == 0) SDL_CondSignal(d->empty_queue_cond);
// packet_pending用于失败重新发送
if (d->packet_pending) {
// 重新复制pkt
av_packet_move_ref(&pkt, &d->pkt);
//去掉重发标记
d->packet_pending = 0;
} else {
// 从Packet队列中读取一个新的Packet,存在pkt中,并将获取的pkt的序号赋值给d->pkt_serial
if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0) return -1;
}
} while (d->queue->serial != d->pkt_serial);
// 如果获取到的是flush_pkt分割标记
if (pkt.data == flush_pkt.data) {
// 清空解码器
avcodec_flush_buffers(d->avctx);
// 重新开始
d->finished = 0;
// pts重新计算
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
} else {
// 获取到正常数据
if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
// 字幕数据
int got_frame = 0;
ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &pkt);
if (ret < 0) {
ret = AVERROR(EAGAIN);
} else {
if (got_frame && !pkt.data) {
d->packet_pending = 1;
av_packet_move_ref(&d->pkt, &pkt);
}
ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF);
}
} else {
// 音频或视频数据,发送给解码器进行解码
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;
// 将Packet还给pkt对象,用于下次重新发送
av_packet_move_ref(&d->pkt, &pkt);
}
}
av_packet_unref(&pkt);
}
}
}

丢帧

解码完成后,对过时的帧进行丢弃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int get_video_frame(VideoState *is, AVFrame *frame) {
int got_picture;
// 解码,将解码的结果放入到frame中
if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
return -1;

if (got_picture) {
// 解码成功
double dpts = NAN;
// 将当前帧的PTS转换成秒
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 (framedrop > 0 ||
(framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
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) {
// 丢帧次数+1
is->frame_drops_early++;
// 是否frame
av_frame_unref(frame);
// 标记未获取到解码数据
got_picture = 0;
}
}
}
}

return got_picture;
}

入队

frame_queue_peek_writable方法中,如果Frame队列已经满了,则会进行阻塞,否则返回一个可写的Frame对象,将数据写入到Frame对象中,调用frame_queue_push通知Frame队列写入完成,整个入队操作就完成了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts,
double duration, int64_t pos, int serial) {
Frame *vp;

// 从视频原始数据队列中获取一个可以写入的帧,vp
if (!(vp = frame_queue_peek_writable(&is->pictq))) return -1;
// 采样横纵比
vp->sar = src_frame->sample_aspect_ratio;
vp->uploaded = 0;
// w h 像素格式
vp->width = src_frame->width;
vp->height = src_frame->height;
vp->format = src_frame->format;
// pts 时长 序号
vp->pts = pts;
vp->duration = duration;
vp->pos = pos;
vp->serial = serial;
// 更改窗口的大小
set_default_window_size(vp->width, vp->height, vp->sar);
// 将AVFrame数据复制到vp中
av_frame_move_ref(vp->frame, src_frame);
// 通知视频原始数据队列,数据已经写入完成
frame_queue_push(&is->pictq);
return 0;
}

音频解码线程

解码和入队的逻辑和视频一样,调用decoder_decode_frame进行解码,解码成功则got_frame为1,再调用frame_queue_peek_writable和frame_queue_push进行入队。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
do {
// 解码,将解码的结果放入到frame中
if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
goto the_end;

if (got_frame) {
// 解码成功
// 音频帧的采样率
tb = (AVRational){1, frame->sample_rate};
...

// 从音频的原始数据队列中取出一个可写入的帧 af,
// 如果返回null,则表明Packet队列已经退出,不会再有数据增加了,直接结束解码
if (!(af = frame_queue_peek_writable(&is->sampq))) goto the_end;
// 设置帧的PTS
af->pts =
(frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
// 设置帧在流中的位置
af->pos = frame->pkt_pos;
// 设置帧的序号
af->serial = is->auddec.pkt_serial;
// 设置帧的时长
af->duration =
av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
// 将帧数据赋值到af中
av_frame_move_ref(af->frame, frame);
// 通知音频的原始数据队列,数据已经成功写入
frame_queue_push(&is->sampq);

...
}
} while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);