[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) 首先我们需要几个属性来保存取得的数据,因为在 ...
随机推荐
- 2019前端面试题汇总(主要为Vue)
摘要: 经典面试题. 原文:2019前端面试题汇总(主要为Vue) 作者:前端小酱 Fundebug经授权转载,版权归原作者所有. 毕业之后就在一直合肥小公司工作,没有老司机.没有技术氛围,在技术的道 ...
- JavaScript 是如何工作:Shadow DOM 的内部结构 + 如何编写独立的组件!
这是专门探索 JavaScript 及其所构建的组件的系列文章的第 17 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...
- Vue移动端项目模板
一个集成移动端开发插件的Vue移动端模板包含1.css: 使用stylus开发css 集成reset样式文件 修改UI组件文件 统一样式处理(如主题色等)2.UI组件 使用热门的vant与mint-u ...
- MongoDB在Linux下常用优化设置
MongoDB在Linux下常用优化设置 以下是一些MongoDB推荐的常用优化设置.在生产环境下选取合适的参数值,例如预读值和默认文件描述符数目等,会对系统性能有很大的影响. 1.关闭数据库文件的 ...
- Android为TV端助力linux命令
从命令行push到系统目录,用户组是root,而代码里面的是个人用户组,所以要把你push进去的东西改成跟你APK一样的用户组,并且chmod -R 777 文件名修改文件的权限 修改用户组chown ...
- python读取txt文件最后一行(文件大+文件小)
txt文件小 #coding:utf-8 ''' fname为所读xx.txt文件 输出为:文件第一行和最后一行 ''' fname = 'test.txt' with open(fname, 'r' ...
- Asp.net mvc 项目返回Json
因mvc控制器返回类型JsonResult 在处理对象转JSON的时候,对日期的格式化处理并不太符合要求,所以重新继承抽象类ActionResult使用Newtonsoft.Json来系列化 usin ...
- jenkins自动化工具使用教程(转)
自动化构建.测试.部署.代码检测越来越重要.主要有一下几点原因 企业做大,项目变多,多端支持(web,h5,小程序等) 微服务提倡高内聚低耦合,项目因拆分变多 DevOps自动化运维流行 集群化,高可 ...
- Proxmox VE中出现TASK ERROR: command 'apt-get update' failed: exit code 100的解决方法
问题描述: 出现这个错误一般在WEB或者在Proxmox VE的服务器上面能看到日志: PVE中出现TASK ERROR: command 'apt-get update' failed: exit ...
- 歌曲的BPM (Beat Per Minute)--每分钟节拍数
因为老爸喜欢跳舞,总让我帮他整理舞曲,一会儿要慢三,一会儿要慢四,一会儿又要快四....我真的分不清啊 我想啊,慢三,慢四这些应该是歌曲的节拍吧(后来得知专业术语叫BPM),于是就在网上搜看看能不能通 ...