ffplay源码分析3-代码框架 (3)

从packet队列中取一个packet解码得到一个frame,并判断是否要根据framedrop机制丢弃失去同步的视频帧。参考源码中注释:

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) { 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); // ffplay文档中对"-framedrop"选项的说明: // Drop video frames if video is out of sync.Enabled by default if the master clock is not set to video. // Use this option to enable frame dropping for all master clock sources, use - noframedrop to disable it. // "-framedrop"选项用于设置当视频帧失去同步时,是否丢弃视频帧。"-framedrop"选项以bool方式改变变量framedrop值。 // 音视频同步方式有三种:A同步到视频,B同步到音频,C同步到外部时钟。 // 1) 当命令行不带"-framedrop"选项或"-noframedrop"时,framedrop值为默认值-1,若同步方式是"同步到视频" // 则不丢弃失去同步的视频帧,否则将丢弃失去同步的视频帧。 // 2) 当命令行带"-framedrop"选项时,framedrop值为1,无论何种同步方式,均丢弃失去同步的视频帧。 // 3) 当命令行带"-noframedrop"选项时,framedrop值为0,无论何种同步方式,均不丢弃失去同步的视频帧。 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) { is->frame_drops_early++; av_frame_unref(frame); // 视频帧失去同步则直接扔掉 got_picture = 0; } } } } return got_picture; }

ffplay中framedrop处理有两种,一处是此处解码后得到的frame尚未存入frame队列前,以is->frame_drops_early++为标记;另一处是frame队列中读取frame进行显示的时候,以is->frame_drops_late++为标记。
本处framedrop操作涉及的变量is->frame_last_filter_delay属于滤镜filter操作相关,ffplay中默认是关闭滤镜的,本文不考虑滤镜相关操作。

3.4.3 decoder_decode_frame()

这个函数是很核心的一个函数,可以解码视频帧和音频帧。视频解码线程中,视频帧实际的解码操作就在此函数中进行。分析过程参考3.2节。

3.5 音频解码线程

音频解码线程从音频packet队列中取数据,解码后存入音频frame队列

3.5.1 打开音频设备

音频设备的打开实际是在解复用线程中实现的。解复用线程中先打开音频设备(设定音频回调函数供SDL音频播放线程回调),然后再创建音频解码线程。调用链如下:

main() --> stream_open() --> read_thread() --> stream_component_open() --> audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt); decoder_start(&is->auddec, audio_thread, is);

audio_open()函数填入期望的音频参数,打开音频设备后,将实际的音频参数存入输出参数is->audio_tgt中,后面音频播放线程用会用到此参数。
音频格式的各参数与重采样强相关,audio_open()的详细实现在后面第5节讲述。

3.5.2 audio_thread()

从音频packet_queue中取数据,解码后放入音频frame_queue:

// 音频解码线程:从音频packet_queue中取数据,解码后放入音频frame_queue static int audio_thread(void *arg) { VideoState *is = arg; AVFrame *frame = av_frame_alloc(); Frame *af; int got_frame = 0; AVRational tb; int ret = 0; if (!frame) return AVERROR(ENOMEM); do { if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0) goto the_end; if (got_frame) { tb = (AVRational){1, frame->sample_rate}; if (!(af = frame_queue_peek_writable(&is->sampq))) goto the_end; 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}); // 将frame数据拷入af->frame,af->frame指向音频frame队列尾部 av_frame_move_ref(af->frame, frame); // 更新音频frame队列大小及写指针 frame_queue_push(&is->sampq); } } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF); the_end: av_frame_free(&frame); return ret; } 3.5.3 decoder_decode_frame()

此函数既可以解码音频帧,也可以解码视频帧,函数分析参考3.2节。

3.6 音频播放线程

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxwjj.html