[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) 首先我们需要几个属性来保存取得的数据,因为在 ...
随机推荐
- echarts图表
<div id="main" style="width: 37.5rem;height: 25rem;"></div> <scri ...
- Dynamics 365 POA表记录的产生
微软动态CRM专家罗勇 ,回复314或者20190311可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 前面的博文 Dyna ...
- 常用matlab函数(不定时更新)
直方图类: histc 直方图分组 示例 histc(a,0:1:10) 意义:将a(矩阵或向量)分组,分组设置为 0-1 1-2 2-3 -.. 9-10,(10-11) 百分位 prctil ...
- OpenCL中的half与float的转换
在kernel中使用half类型可以在牺牲一定精度的代价下来提升运算速度. 在kernel中, 可以比较方便的对half数据进行计算, 但在host上的, 对half的使用就没那么方便了. 查看cl_ ...
- android中的websocket 应用
websocket 在实际的应用中不仅仅能做聊天应用,还可以利用websocket长连接保持数据的实时更新以及信息的推送. websocket 的实现的关键点 第一个:首先需要引入 java-webs ...
- java新知识系列 一
内联函数: 所谓内联函数就是指函数在被调用的地方直接展开,编译器在调用时不用像一般函数那样,参数压栈,返回时参数出栈以及资源释放等,这样提高了程序执行速度. 对应Java语言中也有一个关键字final ...
- 新版的nuget包 PackageLicense 这样写
Intro 最近编译类库项目的时候发现总是有个 licenseUrl 的警告,警告信息如下: warning NU5125: The 'licenseUrl' element will be depr ...
- python读取txt文件最后一行(文件大+文件小)
txt文件小 #coding:utf-8 ''' fname为所读xx.txt文件 输出为:文件第一行和最后一行 ''' fname = 'test.txt' with open(fname, 'r' ...
- 【原】Java学习笔记012 - 数组
package cn.temptation; public class Sample01 { public static void main(String[] args) { // 需求:小店对自己的 ...
- LeetCode算法题-Detect Capital(Java实现)
这是悦乐书的第251次更新,第264篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第118题(顺位题号是520).给定一个单词,你需要判断其中大写字母的使用是否正确.当下 ...