ffplay源码之serial变量

------------------------------------全系列文章目录------------------------------------

本文是根据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不同,于是进行相关的操作,最终实现文件跳转播放的功能。
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值