[SimplePlayer] 7. 多线程处理
在前面的文章中,我们分别实现了视频图像解码、播放,音频解码、播放,现在则需要把这些功能组合起来。总体上来说,整个程序的功能可以分为两条线路:视频以及音频,两条线之间除了后续的同步操作之外基本没有任何关联。而在线路当中,各个模块之间并没有太紧密的耦合,只要上游模块提供了原料,下游模块就可以执行处理。因此,我们可以为各个模块建立独立的线程,这会使得程序结构更加清晰,并且在多核的平台下能更好地发挥平台性能。
所需的各线程及其功能:
- 主线程,除了进行各个模块的初始化之外,所要承担的任务是整个程序的事件处理,如关闭程序。
- 视频线程,进行视频图像解码,把video packet解码成frame。
- 音频线程,进行音频解码,把audio packet解码成frame。
- 读取线程,读取视频文件,demux后分别向视频线程与音频线程提供packet。
- 视频显示线程,进行视频图像显示,这部分并非繁重的任务,因此可以被合并到主线程当中。
- 音频播放线程,进行音频播放,通过SDL的callback实现(SDL会自动为音频输出创建一个线程)。
上述各个线程的处理效率各不相同。例如,读取线程仅需要从磁盘读取视频文件,然后进行复杂度较简单的demux,也就是说很短时间只能就能输出一帧的packet;而视频解码线程则由于其中流程繁杂,需要大量运算,因此通常需要相对较长的时间才能解码出一帧图像。对于这种上下游模块数据处理的效率差异,如果不采取一些应对措施,则会导致线程的频繁切换(每demux、decode、play一帧都需要进行一次线程切换,而线程的上下文切换也会消耗cpu资源),从而降低程序的处理效率。
在上下游线程之间添加一个缓冲就可以很好地改善这一问题。为上下游线程之间添加缓冲后,只要缓冲区还有空间,那么上游的线程就可以继续执行下一帧的处理,并把处理结果输出到该缓冲区内。
本文所用到的缓冲区如下:
- video packet list,存储read thread所输出的video packet。
- audio packet list,存储read thread所输出的audio packet。
- frame queue,存储video thread解码后所输出的视频帧。
- audio ring buffer,存储audio thread解码后所输出的音频数据。
Packet List
Packet list作为demuxer与decoder之间的缓冲区,目的是实现一个packet队列,该队列中的packet先进先出。FFmpeg提供了一个AVPacketList结构体,我们可以用这个结构体来进行队列的构建。
typedef struct AVPacketList {
AVPacket pkt;
struct AVPacketList *next;
} AVPacketList;
AVPacketList当作链表的节点,其中pkt用于维护packet,next用于连接相邻的节点。由于是用链表来实现队列,因此需要一个指向链表头的指针first_pkt以及一个指向链表尾的指针last_pkt。当需要把packet入队列时,把packet加入到链表末尾,而当要取出packet时,则从链表头部取出。
Frame Queue
Video Thread解码出来的视频帧会被缓存在Frame Queue中,显示模块在需要进行图像显示的时候从Frame Queue中取出图像进行显示。由于通常frame所占用的空间都比较大,因此缓存的frame的数量会有所限制,那么我们就可以用一个指向frame的指针数组来进行队列的维护。
为了实现队列的效果,需要分别有两个数字指示队列的头与尾,其中read_index标记的是队列头部,write_index指示的是队列尾部。当要从队列中取出frame时,去获取read_index的数组元素所指示的frame,然后read_index++;当要把frame加入到队列中时,令write_index的数组元素指向需要加入队列的frame,然后write_index++。
Ring Buffer
在前面的章节中我们手动把fltp格式的音频转换为s16的音频,s16的音频格式是把左右声道的音频样本交叉排列的串行数据,ring buffer是一种比较时候用于存储串行数据的数据结构。
Ring buffer,环形缓冲区,原型为一块连续的缓冲区,通过运用指向数据头部(rIndex)以及数据尾部(wIndex)的指针来维护数据的存取,当数据尾部的指针到达缓冲区末尾,就会把尾部指针指向缓冲区开头,同理数据头部的指针也会进行循环移动,如此实现环形缓冲区。
当需要存储数据时,首先需要保证有足够的空间来进行存储,然后从wIndex处开始写入数据,并根据写入数据的长度更新wIndex;当要读取数据时,从rIndex处读取数据,并根据读取的数据长度更新rIndex。
线程安全
对于上面描述的3种队列,为了线程安全(使得对队列的操作能在多线程上安全使用),我们需要保证出/入队列的操作为原子操作。实现则可以采用SDL提供的mutex。
中途退出
视频播放可以进行中途退出的操作,那么我们也有必要提供能在中途终止队列的功能。我们这里所说的终止队列,就是使得再次调用出/入队列的函数时,会返回-1,以表示队列已被终止。我们可以通过设置一个变量abort_request来进行判断,当abort_request为1时队列终止,为0则队列正常运行。
队列的abort函数需要实现:
- 把abort_request设置为1。
- 由于出/入队列函数可能此时会处于等待状态(如:此时已经满队列,入队列函数在等待队列腾出空间),因此abort函数还需要解除出/入队列函数的等待状态。
那么在实现出/入队列的函数时
- 在函数的开头加入对变量abort_request的判断来决定是否返回-1。
- 由于出/入队列函数可能此时会处于等待状态(如:此时已经满队列,入队列函数在等待队列腾出空间),那么在abort函数解除当前函数的等待状态后,应该再次进行abort_request变量的判断,如果abort_request为1则应该直接返回-1,而不应该继续执行后续操作。
[SimplePlayer] 7. 多线程处理的更多相关文章
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- QT实现HTTP JSON高效多线程处理服务器
QT实现HTTP JSON高效多线程处理服务器 Legahero QQ:1395449850 现在一个平台级的系统光靠web打天下是不太现实的了,至少包含APP和web两部分,在早期APP直接访问we ...
- 由一篇文章引发的思考——多线程处理大数组
今天领导给我们发了一篇文章文章,让我们学习一下. 文章链接:TAM - Threaded Array Manipulator 这是codeproject上的一篇文章,花了一番时间阅读了一下.文章主要是 ...
- 多线程处理sql server2008出现Transaction (Process ID) was deadlocked on lock resources with another process and has been chose问题
多线程处理sql server2008某个表中的数据时,在Update记录的时候出现了[Transaction (Process ID 146) was deadlocked on lock reso ...
- WPF 多线程处理(1)
WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 废话不多说,先上图: 多线程处理数据后在th ...
- WPF 多线程处理(5)
WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 项目的目录: 以下是FileStroage的 ...
- WPF 多线程处理(4)
WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 开始一个线程处理读取的文件并且更新到list ...
- WPF 多线程处理(6)
WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 以下是子窗体的UI: <Window ...
- WPF 多线程处理(3)
WPF 多线程处理(1) WPF 多线程处理(2) WPF 多线程处理(3) WPF 多线程处理(4) WPF 多线程处理(5) WPF 多线程处理(6) 首先我们需要几个属性来保存取得的数据,因为在 ...
随机推荐
- 【Dojo 1.x】笔记6 配置对象dojoConfig的用处和真身所在
dojoConfig是对整个Dojo开发环境的配置,它的位置必须是页面的最前,可以是script标签也可以是独立的js文件. 它允许开发者为Dojo SDK的各个方面设置选项和默认行为. 但是,这个对 ...
- 林业有害生物监测系统(重庆宇创GIS)
本文由重庆宇创GIS团队原创,转载请注明来源http://www.cnblogs.com/ycdigit/p/8916073.html 一.概述 林业有害生物监测信息平台(森林病虫害监测预警系统) ...
- Gunicorn与uWSGI
Gunicorn与uWSGI perfork perfork是一种服务端编程模型, Nginx, Gunicorn, uWSGI都是这种模型的实现, 简单的说perfok就是master进程启动注册一 ...
- 简易仿ios菊花加载loading图
原文链接:https://mp.weixin.qq.com/s/wBbQgOfr59wntNK9ZJ5iRw 项目中经常会用到加载数据的loading显示图,除了设计根据app自身设计的动画loadi ...
- Easyui 修改jquery validatebox为英文校验提示为中文提示
修改jquery validatebox为英文校验提示为中文提示 by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 问题描述: 如图,想把校验提示由英文改成中文 ...
- Android八门神器(一): OkHttp框架源码解析
HTTP是我们交换数据和媒体流的现代应用网络,有效利用HTTP可以使我们节省带宽和更快地加载数据,Square公司开源的OkHttp网络请求是有效率的HTTP客户端.之前的知识面仅限于框架API的调用 ...
- js 数组去重小技巧
js 数组去重小技巧 Intro 今天遇到一个问题,需要对数据进行去重,想看一下有没有什么比较方便的方法,果然有些收获. Question 问题描述: 我有一个这样的数据: [ { "Pro ...
- 统计numpy数组中最频繁出现的值
arr = np.array([[1,2,100,4,5,6],[1,1,100,3,5,5],[2,2,4,4,6,6]]) 方法一: count = np.bincount(arr[:,2]) # ...
- Servlet是否单例?
1,测试环境: Java SE版本:1.8.0_161(AMD64) Tomcat版本:9.0.7(AMD64) 2,试验 (1)编写HelloServlet. 由于测试代码很简单,此处只列出doGe ...
- Checkpoint 和Breakpoint
参考:http://www.cnblogs.com/qiangshu/p/5241699.htmlhttp://www.cnblogs.com/biwork/p/3366724.html 1. Che ...