------------------------------------全系列文章目录------------------------------------
本文是根据ffplay源码-https://ffmpeg.org/download.html,分析其中的serial变量,不当之处恳请批评指正。
-
serial翻译为连续的,在ffplay中是用于判断播放是否连续的标志,serial变量存在于自定义的多个结构体中
/*存储未解码数据包*/ typedef struct MyAVPacketList { AVPacket *pkt; int serial; } MyAVPacketList; /*存储未解码数据包的队列*/ typedef struct PacketQueue { ...... int serial; ...... } PacketQueue; /*描述时钟*/ typedef struct Clock { ...... int serial; /* clock is based on a packet with this serial */ int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clockdetection */ ...... } Clock; /*存储帧数据*/ typedef struct Frame { ...... int serial; ...... } Frame; /*描述解码器*/ typedef struct Decoder { ...... int pkt_serial; ...... } Decoder; /*描述音视频总结构体*/ typedef struct VideoState { ...... int audio_clock_serial; ...... } VideoState;
-
让我们看看这些值是如何初始化的
/*MyAVPacketList的serial在压入队列时,赋值为PacketQueue的serial*/ static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) { MyAVPacketList pkt1; ...... pkt1.serial = q->serial; ...... return 0; } /*初始化PacketQueue的serial为0*/ static int packet_queue_init(PacketQueue *q) { memset(q, 0, sizeof(PacketQueue)); ...... return 0; } /*初始化clock的serial为-1,关联queue_serial为PacketQueue的serial*/ static void set_clock_at(Clock *c, double pts, int serial, double time) { ...... c->serial = serial; } static void set_clock(Clock *c, double pts, int serial) { double time = av_gettime_relative() / 1000000.0; set_clock_at(c, pts, serial, time); } static void init_clock(Clock *c, int *queue_serial) { /*外部调用为init_clock(&is->vidclk, &is->videoq.serial); 其中is->videoq为PacketQueue结构体*/ ...... c->queue_serial = queue_serial; set_clock(c, NAN, -1); } /*初始化Frame的serial为0*/ static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) { ...... memset(f, 0, sizeof(FrameQueue)); ...... f->pktq = pktq; ...... return 0; } /*Decoder的serial初始化为-1,同时将关联的PacketQueue的serial自加1,即为1*/ static int decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) { ...... d->pkt_serial = -1; return 0; } static int decoder_start(Decoder *d, int (*fn)(void *), const char *thread_name, void* arg) { packet_queue_start(d->queue); ...... return 0; } static void packet_queue_start(PacketQueue *q) { ...... q->serial++; ...... } /*初始化VideoState的audio_clock_serial为-1*/ static VideoState *stream_open(const char *filename, const AVInputFormat *iformat) { VideoState *is; is = av_mallocz(sizeof(VideoState)); ...... is->audio_clock_serial = -1; ...... }
-
初始化后如图所示
-
接下来看看这些serial都是如何使用的吧
-
以下PacketQueue的相关操作都使用到了serial变量
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) { MyAVPacketList pkt1; ...... pkt1.pkt = pkt; pkt1.serial = q->serial; ...... return 0; } /*压入队列*/ static int packet_queue_put(PacketQueue *q, AVPacket *pkt) { AVPacket *pkt1; ...... ret = packet_queue_put_private(q, pkt1); ...... return ret; } /*清空队列*/ static void packet_queue_flush(PacketQueue *q) { ...... q->serial++; ...... } /*压出队列*/ static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial) { MyAVPacketList pkt1; ...... *serial = pkt1.serial; ...... }
-
以下为read_thread中,音视频文件发生跳到时的操作。
avformat_seek_file用于以时间戳的形式,定位输入文件的读取位置。当定位完文件后,将原本的未解码包队列清空,此时PacketQueue的serial会自加1。后续进行读取未解码数据的操作,并将AVPacket作为MyAVPacketList的一部分、将PacketQueue的serial作为MyAVPacketList的serial,把MyAVPacketList压入PacketQueue中。
static int read_thread(void *arg) { ...... for (;;) { if (is->seek_req) { /*发生文件重定位事件*/ ...... /*重定位文件*/ ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "%s: error while seeking\n", is->ic->url); } else { /*重定位完文件,清空PacketQueue*/ if (is->audio_stream >= 0) packet_queue_flush(&is->audioq); if (is->subtitle_stream >= 0) packet_queue_flush(&is->subtitleq); if (is->video_stream >= 0) packet_queue_flush(&is->videoq); ...... } is->seek_req = 0; is->queue_attachments_req = 1; ...... } ...... ret = av_read_frame(ic, pkt); /*读取未解码的数据AVPacket*/ ...... /*将读取到的数据压入PacketQueue中*/ if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { packet_queue_put(&is->audioq, pkt); } else if (pkt->stream_index == is->video_stream && pkt_in_play_range && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) { packet_queue_put(&is->videoq, pkt); } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) { packet_queue_put(&is->subtitleq, pkt); } else { av_packet_unref(pkt); } ...... } ...... }
-
此函数为解码操作,每一次都会先去判断当前Decoder关联的PacketQueue的serial与Decoder的pkt_serial是否相同,相同则进行解码操作。后面一步为接收未解码数据的操作,若接收前后pkt_serial(接收至MyAVPacketList)不一致,表示文件发生了跳转,则进行清空缓存区以及更正pts操作;若接收前后pkt_serial一致,并且Decoder关联的PacketQueue的serial与Decoder的pkt_serial(接收至MyAVPacketList)相同,则退出接收未解码数据的循环,去执行解码操作。
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) { ...... for (;;) { if (d->queue->serial == d->pkt_serial) { /*文件连续,执行后续解码操作*/ ...... } do { ...... 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) { /*接收前后serial不一致,表明发生了文件重定位*/ avcodec_flush_buffers(d->avctx); d->finished = 0; d->next_pts = d->start_pts; d->next_pts_tb = d->start_pts_tb; } } /*相同,表示接收的MyAVPacketList与最新的PacketQueue相符*/ if (d->queue->serial == d->pkt_serial) break; av_packet_unref(d->pkt); } while (1); ...... } ...... }
-
下面的将Frame保存至队列中,外部调用为queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial),音频Frame操作也同理,此时Frame的serial值为Decoder的pkt_serial(接收至MyAVPacketList)。
/*保存Frame至队列中*/ static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial) { Frame *vp; ...... vp->serial = serial; ...... frame_queue_push(&is->pictq); return 0; }
-
从下面几个函数可以看出,clock的serial最终是来源于Frame的serial(取自Decoder的pkt_serial(接收至MyAVPacketList))。
/*外部调用为update_video_pts(is, vp->pts, vp->pos, vp->serial)*/ static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) { set_clock(&is->vidclk, pts, serial); sync_clock_to_slave(&is->extclk, &is->vidclk); } /*音频解码函数*/ static int audio_decode_frame(VideoState *is) { ...... is->audio_clock_serial = af->serial; ...... } /*音频回调函数*/ static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) { ...... if (!isnan(is->audio_clock)) { set_clock_at(&is->audclk, is->audio_clock - (double) (2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0); ...... } } is->audio_clock_serial, audio_callback_time / 1000000.0); ...... } }
-
-
总的来说,所有serial的值都来源于PacketQueue的serial,当文件正常播放时,各个结构体中的serial值相同;但是当发生文件跳转事件,PacketQueue的serial值率先发生变化,同时清空原有来保持的AVPacket数据,然后各个结构体之间发现自身的serial与原先PacketQueue的serial不同,于是进行相关的操作,最终实现文件跳转播放的功能。