暂停也是播放器非常常见的功能。对于 FFplay 播放器,可以通过 p 键 或者空格键 来切换暂停状态。

先来看一下处理 p 键 的代码,如下:

从上图可以看到,调了 toggle_pause() 函数,注意这个 cur_stream 参数,其实这个参数不是视频流或者音频流,而是 FFplay 的全局管理器 VideoState

toggle_pause() 函数的实现如下:

static void toggle_pause(VideoState *is)
{stream_toggle_pause(is);is->step = 0;
}

后面为什么会把 is->step 置为 0 ?is->step 变量是给逐帧播放用的,为 0 代表退出逐帧播放模式。

这是因为逐帧播放实际上就是用切换暂停状态来实现的,每播放一帧就立即暂停。具体请看《FFplay逐帧播放分析》


再来看一下 stream_toggle_pause() 函数的实现,如下:

从 启动状态 切换到 暂停状态的时候,is->paused 等于 0,所以是不会跑进去 if (is->paused) {...} 里面的。

上图中用 set_clock() 更新了外部时钟set_clock() 函数会把 Clock::pts 更新到当前最新的播放时刻。

通常情况下,get_clock() 函数获取当前的最新播放时刻,是用 Clock::pts + 消逝的时间。

但是消逝的时间可以是 0 的,什么情况下消逝的时间是 0 呢?就是暂停的时候,当播放器暂停的时候,他外部时钟也会暂停,所以消逝的时间为 0 ,如下:

stream_toggle_pause() 函数才需要更新外部时钟的 pts 成最新的值,如果不更新,在暂停状态下获取到的外部时钟播放时刻就不准。

在暂停状态下,哪段代码还会调 get_clock() 获取外部时钟的播放时刻呢?这个后面揭晓。

stream_toggle_pause() 函数最后还会把 4 个暂停变量都被设置成 1,或者 从 1 切换成 0 。如下:

is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused;

我们来看一下这 4 个暂停变量会影响哪些代码逻辑?

首先是 main 主线程,主线程主要是处理键盘事件 跟 播放视频画面,键盘事件不会受到暂停状态影响,该处理还是继续处理。

播放视频画面 的函数是 video_refresh() ,但是暂停状态下也不会调 video_refresh() ,如下:

但是暂停状态下,如果改变了 ffplay 窗口大小,is->forece_refresh 机会变成 1,就会调 video_refresh() 。

因此 main 主线程,在暂停状态下,只会不断检测,处理键盘事件以及一些窗口事件,大部分时候并不会调 video_refresh() 播放视频画面。


再来看一下 read_thread解复用线程,在暂停状态下,它在干什么?

上图的是为了兼容一些网络流的播放,有些流媒体协议支持暂停跟播放操作,当暂停的时候,服务器端就不会再推流过来了。对于本地播放,上面的代码是没用的。

我翻了一圈 read_thread() 函数的代码,发现它并不会因为 pause 变成 1 而停下,read_thread() 线程即使在暂停状态下,也是不断运行,不断读取数据,直到 塞满队列,塞满队列就会休眠 10 ms,如下:

感兴趣的读者可以在暂停状态下往 SDL_CondWaitTimeout() 那里打个断点,会不断跑进去那里的逻辑。


再来看一下 video_thread视频解码线程,在暂停状态下,它在干什么?

研究 video_thread() 函数的代码,我们发现了第一个在暂停状态下,获取外部时钟的地方,也就是 video_thread() 里面的 get_video_frame() 函数

当外部时钟是 主时钟的时候,这里的 get_master_clock() 获取的就是外部时钟的播放时刻。因此如果在 stream_toggle_pause() 里面不更新外部时钟,这里获取到的时间就是错误的,会导致误判,丢帧。

翻了一圈 video_thread() 解码线程的代码,发现也不会受到 paused 的影响,还是会正常解码,但是如果塞满 FrameQueue 队列的时候,就会一直阻塞在 frame_queue_peek_writable() 函数里面。


再来看一下 audio_thread音频解码线程,在暂停状态下,它在干什么?

研究发现,跟 video_thread() 视频解码类似,也不会受到 paused 的影响,还是会正常解码,但是如果塞满 FrameQueue 队列的时候,就会一直阻塞在 frame_queue_peek_writable() 函数里面。


最后来看一下 sdl_audio_callback音频播放线程分析,在暂停状态下,它在干什么?

首先,sdl_audio_callback() 是回调函数,所以你可以猜到,肯定不会阻塞,肯定会返回去给 SDL 。

首先,sdl_audio_callback() 是会受到 paused 的影响的,如下:

暂停状态下,audio_decode_frame() 函数会直接返回 -1,就会导致 输出静音数据,只要直接把 stream 指向的内存数据设置为 0 就是输出静音数据了。

memset(stream, 0, len1);

虽然是输出静音数据,但是音频播放线程还是在跑的,没有阻塞,她在跑,就会更新音频时钟,如下:

虽然会更新音频时钟,但是因为在暂停状态下没有跑进去 audio_decode_frame(),所以 is->audio_clock 没有更新。因此即便更新音频时钟,也是用原来的值来更新的。

所以音频时钟相当于没有更新。

读者可以在 sdl_audio_callback() 入口加个日志,如下:

会发现虽然音频时钟一直在跑,但是这个 get_clock() 返回的值是没有增长的。


总结一下,从启动状态切换到暂停状态,影响的地方。

1,导致视频播放函数没有调用,导致 FrameQueue 堆积,所以视频解码线程会阻塞在 frame_queue_peek_writable() 函数里面

2,导致音频播放线程直接输出静音数据,没有从 FrameQueue 读数据,导致 FrameQueue 堆积,所以音频解码线程会阻塞在 frame_queue_peek_writable() 函数里面。

3,音频解码线程,视频解码线程阻塞,一直不从 PacketQueue 拿数据去解码,导致 PacketQueue 堆积,从而导致 read_thread解复用线程不再从文件读取数据了。


回到 stream_toggle_pause() 函数,当从暂停状态切换到启动状态的时候,有一段逻辑非常奇怪,如下:

可以看到,它会会更新 is->frame_timer 跟 视频时钟,为什么要这么做呢?

因为 is->frame_timer 的单位是系统时间,代表窗口现在这一帧画面是在什么系统时间开始播放的。更新 is->frame_timer 之后代表窗口当前画面是从此刻开始播放的。

暂停之后,系统时间是一直在跑的。例如,暂停 8 秒钟之后,再调 av_gettime_relative() 会发现比之前多了 8 秒。

因此切换回来的时候,要及时更新 is->frame_timer ,这个变量的含义不能变的。如果你不更新,就代表视频播放线程卡顿,没有调度过来,导致的这帧画面播放了 8 秒,虽然他确实是播放了 8秒,但是不能体现在 is->frame_timer 变量上面。

后面在 video_refresh() 里面会用 is->frame_timer 来判断是否已经超过预定的播放时刻,超过了就会丢帧,如下:

上图中,只有超过 10 秒才会纠正 is->frame_timer ,如果你只暂停了 8 秒就不会纠正。

因此,如果在 暂停状态切换到启动状态的时候,不更新 is->frame_timer ,就会导致 8 秒的视频帧被丢弃,因为会误判它们都已经过了预定的播放时刻。


再来看一下,从暂停状态切换到启动状态的时候,为啥需要更新视频时钟,如下:

注意,他是先把视频时钟的暂停状态恢复,然后再更新时钟的,所以 get_clock() 获取的是 pts + 消逝的时间,消逝的时间其实就等于暂停了多久。

其实我也不太清楚 这里更新 视频时钟的 pts 的具体作用,因为暂停状态已经恢复了,随意即使这里不执行 set_clock()get_clock() 获取到的也是正确的播放时刻。

个人猜测是字幕的场景用的,因为那里用了 clock 里面的 pts 字段。


至此,ffplay 播放器暂停功能分析完毕。


推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

FFplay暂停分析相关推荐

  1. FFMPEG 3.4.2 - ffplay源代码分析 (三)

    1.数据结构之VideoState VideoState是所有其他数据结构的母体. main 线程启动新线程read_thread,初始化VideoState. AVFormatContext保存与& ...

  2. FFmpeg学习笔记之ffplay流程分析

    背景说明 FFmpeg是一个开源,免费,跨平台的视频和音频流方案,它提供了一套完整的录制.转换以及流化音视频的解决方案.而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器.学习f ...

  3. FFplay退出分析

    当 FFplay播放器播放完一个 mp4 文件的时候,画面就会停止在最后一帧,并不会自动退出,如下: 有两种方式可以退出 FFplay 播放器. 1,在命令行使用 autoexit 参数,这是一个布尔 ...

  4. FFplay序列号分析

    在之前的几篇文章里面,都零零散散提及过序列号这个概念.但是 序列号 这个概念对于 FFplay 播放器 非常重要的,很多代码都跟序列号有关.所以单独写一篇文章介绍序列号. 序列号 主要是给 快进快退  ...

  5. FFplay源代码分析:整体流程图

    转自:雷博 http://blog.csdn.net/leixiaohua1020/article/details/11980843 转载于:https://www.cnblogs.com/dk666 ...

  6. 音视频技术之ffplay源码分析-音视频同步

    音视频同步的目的是为了使播放的声音和显示的画面保持一致.视频按帧播放,图像显示设备每次显示一帧画面,视频播放速度由帧率确定,帧率指示每秒显示多少帧:音频按采样点播放,声音播放设备每次播放一个采样点,声 ...

  7. ffplay.c学习-2-数据读取线程

    ffplay.c学习-2-数据读取线程 目录 准备⼯作 avformat_alloc_context 创建上下⽂ ic->interrupt_callback avformat_open_inp ...

  8. ffplay.c学习-1-框架及数据结构

    ffplay.c学习-1-框架及数据结构 目录 ffplay.c的意义 FFplay框架分析 数据结构分析 struct VideoState 播放器封装 struct Clock 时钟封装 stru ...

  9. ffplay for mfc 代码备忘

    之前上传了一个开源播放器工程ffplay for mfc.它将ffmpeg项目中的ffplay播放器(ffplay.c)移植到了VC的环境下,并且使用MFC做了一套界面.它可以完成一个播放器播放视频的 ...

最新文章

  1. mysql命令创建模式_mysql在命令行模式下创建数据库时要显式指定字符集
  2. 档案中级职称计算机需要考几个模块,2020年职称申报需要准备哪些档案资料?这些细节必须知道!...
  3. PM Basic Skill---Communicate Plan
  4. UA MATH564 概率不等式 QE练习题
  5. 打开高效文本编辑之门_Linux awk之关联数组
  6. appium java 测试用例_如何在C#中使用Appium编写测试用例?
  7. 前端程序员能力不足?表现在哪几点,你需要加强的地方!
  8. 《软件需求分析(第二版)》第 1 章——软件需求基础知识 重点部分总结
  9. html资源文件放在哪里,09 Spring Boot开发web项目之静态资源放哪里?
  10. 查找在Git中删除文件的时间
  11. 【LeetCode】【数组】题号:485,最大连续1的个数
  12. 摄影测量学之共线方程的应用
  13. mysql中gtid关闭方法_CDH-mysql 开启关闭 gtid
  14. 第二章 Maven的安装和配置
  15. Visual studio +Intel Fortran 环境安装与设置
  16. 原神ios android,原神ios和安卓数据互通吗 原神ios和安卓能一起玩吗
  17. 详解rem布局-利用rem布局实现移动端高清显示
  18. 雷电模拟器 手机模拟器 安装 连接
  19. Mac苹果 M1配置cocoapods
  20. 携程网今日瘫痪 官方称遭到不明攻击

热门文章

  1. Python读取雷达位姿数据方案二
  2. windows下使用OpenGL实现yuv420p转rgb播放视频(三重纹理实现)
  3. LitePal基本使用概述
  4. Stata:模型结果如何导入到Word和Excel。
  5. App打开小程序,无法跳转回App的问题
  6. 【LeetCode】6. ZigZag Conversion Z 字形变换
  7. Json文件转换为Excel文件!涉及读文件,时间戳转化,写文档
  8. [笔记].电机行业常用的中英文对照
  9. php封装多段mp4,解决ffmpeg将多段视频裁剪拼接后卡顿现象
  10. 成都正规python培训班