本文是根据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;
让我们看看这些值是如何初始化的
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
- /*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不同,于是进行相关的操作,最终实现文件跳转播放的功能。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓