• ffplay源码之serial变量


    本文是根据ffplay源码-https://ffmpeg.org/download.html,分析其中的serial变量

    serial翻译为连续的,在ffplay中是用于判断播放是否连续的标志,serial变量存在于自定义的多个结构体中

    1. /*存储未解码数据包*/
    2. typedef struct MyAVPacketList {
    3. AVPacket *pkt;
    4. int serial;
    5. } MyAVPacketList;
    6. /*存储未解码数据包的队列*/
    7. typedef struct PacketQueue {
    8. ......
    9. int serial;
    10. ......
    11. } PacketQueue;
    12. /*描述时钟*/
    13. typedef struct Clock {
    14. ......
    15. int serial; /* clock is based on a packet with this serial */
    16. int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clockdetection */
    17. ......
    18. } Clock;
    19. /*存储帧数据*/
    20. typedef struct Frame {
    21. ......
    22. int serial;
    23. ......
    24. } Frame;
    25. /*描述解码器*/
    26. typedef struct Decoder {
    27. ......
    28. int pkt_serial;
    29. ......
    30. } Decoder;
    31. /*描述音视频总结构体*/
    32. typedef struct VideoState {
    33. ......
    34. int audio_clock_serial;
    35. ......
    36. } VideoState;

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

    本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

    1. /*MyAVPacketList的serial在压入队列时,赋值为PacketQueue的serial*/
    2. static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
    3. {
    4. MyAVPacketList pkt1;
    5. ......
    6. pkt1.serial = q->serial;
    7. ......
    8. return 0;
    9. }
    10. /*初始化PacketQueue的serial为0*/
    11. static int packet_queue_init(PacketQueue *q)
    12. {
    13. memset(q, 0, sizeof(PacketQueue));
    14. ......
    15. return 0;
    16. }
    17. /*初始化clock的serial为-1,关联queue_serial为PacketQueue的serial*/
    18. static void set_clock_at(Clock *c, double pts, int serial, double time)
    19. {
    20. ......
    21. c->serial = serial;
    22. }
    23. static void set_clock(Clock *c, double pts, int serial)
    24. {
    25. double time = av_gettime_relative() / 1000000.0;
    26. set_clock_at(c, pts, serial, time);
    27. }
    28. static void init_clock(Clock *c, int *queue_serial)
    29. {
    30. /*外部调用为init_clock(&is->vidclk, &is->videoq.serial);
    31. 其中is->videoq为PacketQueue结构体*/
    32. ......
    33. c->queue_serial = queue_serial;
    34. set_clock(c, NAN, -1);
    35. }
    36. /*初始化Frame的serial为0*/
    37. static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last)
    38. {
    39. ......
    40. memset(f, 0, sizeof(FrameQueue));
    41. ......
    42. f->pktq = pktq;
    43. ......
    44. return 0;
    45. }
    46. /*Decoder的serial初始化为-1,同时将关联的PacketQueue的serial自加1,即为1*/
    47. static int decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
    48. ......
    49. d->pkt_serial = -1;
    50. return 0;
    51. }
    52. static int decoder_start(Decoder *d, int (*fn)(void *), const char *thread_name, void* arg)
    53. {
    54. packet_queue_start(d->queue);
    55. ......
    56. return 0;
    57. }
    58. static void packet_queue_start(PacketQueue *q)
    59. {
    60. ......
    61. q->serial++;
    62. ......
    63. }
    64. /*初始化VideoState的audio_clock_serial为-1*/
    65. static VideoState *stream_open(const char *filename,
    66. const AVInputFormat *iformat)
    67. {
    68. VideoState *is;
    69. is = av_mallocz(sizeof(VideoState));
    70. ......
    71. is->audio_clock_serial = -1;
    72. ......
    73. }

    初始化后如图所示

     

    接下来看看这些serial都是如何使用的吧

    • 以下PacketQueue的相关操作都使用到了serial变量

    1. static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
    2. {
    3. MyAVPacketList pkt1;
    4. ......
    5. pkt1.pkt = pkt;
    6. pkt1.serial = q->serial;
    7. ......
    8. return 0;
    9. }
    10. /*压入队列*/
    11. static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
    12. {
    13. AVPacket *pkt1;
    14. ......
    15. ret = packet_queue_put_private(q, pkt1);
    16. ......
    17. return ret;
    18. }
    19. /*清空队列*/
    20. static void packet_queue_flush(PacketQueue *q)
    21. {
    22. ......
    23. q->serial++;
    24. ......
    25. }
    26. /*压出队列*/
    27. static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
    28. {
    29. MyAVPacketList pkt1;
    30. ......
    31. *serial = pkt1.serial;
    32. ......
    33. }

    以下为read_thread中,音视频文件发生跳到时的操作。

    avformat_seek_file用于以时间戳的形式,定位输入文件的读取位置。当定位完文件后,将原本的未解码包队列清空,此时PacketQueue的serial会自加1。后续进行读取未解码数据的操作,并将AVPacket作为MyAVPacketList的一部分、将PacketQueue的serial作为MyAVPacketList的serial,把MyAVPacketList压入PacketQueue中。

    1. static int read_thread(void *arg)
    2. {
    3. ......
    4. for (;;) {
    5. if (is->seek_req) { /*发生文件重定位事件*/
    6. ......
    7. /*重定位文件*/
    8. ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
    9. if (ret < 0) {
    10. av_log(NULL, AV_LOG_ERROR,
    11. "%s: error while seeking\n", is->ic->url);
    12. } else { /*重定位完文件,清空PacketQueue*/
    13. if (is->audio_stream >= 0)
    14. packet_queue_flush(&is->audioq);
    15. if (is->subtitle_stream >= 0)
    16. packet_queue_flush(&is->subtitleq);
    17. if (is->video_stream >= 0)
    18. packet_queue_flush(&is->videoq);
    19. ......
    20. }
    21. is->seek_req = 0;
    22. is->queue_attachments_req = 1;
    23. ......
    24. }
    25. ......
    26. ret = av_read_frame(ic, pkt); /*读取未解码的数据AVPacket*/
    27. ......
    28. /*将读取到的数据压入PacketQueue中*/
    29. if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
    30. packet_queue_put(&is->audioq, pkt);
    31. } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
    32. && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
    33. packet_queue_put(&is->videoq, pkt);
    34. } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
    35. packet_queue_put(&is->subtitleq, pkt);
    36. } else {
    37. av_packet_unref(pkt);
    38. }
    39. ......
    40. }
    41. ......
    42. }

    此函数为解码操作,每一次都会先去判断当前Decoder关联的PacketQueue的serial与Decoder的pkt_serial是否相同,相同则进行解码操作。后面一步为接收未解码数据的操作,若接收前后pkt_serial(接收至MyAVPacketList)不一致,表示文件发生了跳转,则进行清空缓存区以及更正pts操作;若接收前后pkt_serial一致,并且Decoder关联的PacketQueue的serial与Decoder的pkt_serial(接收至MyAVPacketList)相同,则退出接收未解码数据的循环,去执行解码操作。

    1. static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub)
    2. {
    3. ......
    4. for (;;) {
    5. if (d->queue->serial == d->pkt_serial) { /*文件连续,执行后续解码操作*/
    6. ......
    7. }
    8. do {
    9. ......
    10. if (d->packet_pending) {
    11. d->packet_pending = 0;
    12. } else {
    13. int old_serial = d->pkt_serial;
    14. if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
    15. return -1;
    16. if (old_serial != d->pkt_serial) { /*接收前后serial不一致,表明发生了文件重定位*/
    17. avcodec_flush_buffers(d->avctx);
    18. d->finished = 0;
    19. d->next_pts = d->start_pts;
    20. d->next_pts_tb = d->start_pts_tb;
    21. }
    22. }
    23. /*相同,表示接收的MyAVPacketList与最新的PacketQueue相符*/
    24. if (d->queue->serial == d->pkt_serial)
    25. break;
    26. av_packet_unref(d->pkt);
    27. } while (1);
    28. ......
    29. }
    30. ......
    31. }

    下面的将Frame保存至队列中,外部调用为queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial),音频Frame操作也同理,此时Frame的serial值为Decoder的pkt_serial(接收至MyAVPacketList)。

    1. /*保存Frame至队列中*/
    2. static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
    3. {
    4. Frame *vp;
    5. ......
    6. vp->serial = serial;
    7. ......
    8. frame_queue_push(&is->pictq);
    9. return 0;
    10. }

    从下面几个函数可以看出,clock的serial最终是来源于Frame的serial(取自Decoder的pkt_serial(接收至MyAVPacketList))。

    1. /*外部调用为update_video_pts(is, vp->pts, vp->pos, vp->serial)*/
    2. static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial)
    3. {
    4. set_clock(&is->vidclk, pts, serial);
    5. sync_clock_to_slave(&is->extclk, &is->vidclk);
    6. }
    7. /*音频解码函数*/
    8. static int audio_decode_frame(VideoState *is)
    9. {
    10. ......
    11. is->audio_clock_serial = af->serial;
    12. ......
    13. }
    14. /*音频回调函数*/
    15. static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
    16. {
    17. ......
    18. if (!isnan(is->audio_clock)) {
    19. set_clock_at(&is->audclk,
    20. is->audio_clock - (double)
    21. (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);
    22. ......
    23. }
    24. }
    25. is->audio_clock_serial, audio_callback_time / 1000000.0);
    26. ......
    27. }
    28. }

    总的来说,所有serial的值都来源于PacketQueue的serial,当文件正常播放时,各个结构体中的serial值相同;但是当发生文件跳转事件,PacketQueue的serial值率先发生变化,同时清空原有来保持的AVPacket数据,然后各个结构体之间发现自身的serial与原先PacketQueue的serial不同,于是进行相关的操作,最终实现文件跳转播放的功能。

     本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 相关阅读:
    攻防世界-xff_referer+mfw
    AIGC:【LLM(八)】——Baichuan2技术报告
    ARM64汇编0A - thumb模式与IT块
    服务器技术(二)--Linux进阶应用(3)--Bash Shell及Linux部署办公OA项目实践
    Java内存区域介绍以及JDK1.8内存变化
    Matplotlib:科研绘图利器(写论文、数据可视化必备)
    设计模式-原型模式
    安卓TextView的lineHeight*lineCount!=height问题,解决不支持滚动的系统下对多页Text进行分页
    项目管理之分析项目特点的方法
    java 企业工程管理系统软件源码 自主研发 工程行业适用
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/126468915