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;

让我们看看这些值是如何初始化的

本文福利, 免费领取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↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/126468915