[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) 首先我们需要几个属性来保存取得的数据,因为在 ...
随机推荐
- DOM编程以及domReady加载的几种方式
1,关于DOM编程 DOM编程主要是对dom树节点进行操作,所以你必须掌握基本的节点类型,如何去获取节点名字以及值(这些相关知识你可以去网上查,这里推荐一个慕课学习网站->https ...
- 认证与Shiro安全框架
本文内容均来自官网 1.简介 Apache Shiro是Java的一个安全框架.功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案. 实际上,S ...
- 环境配置(pycharm+virtualenv+git+github等)
本文转载自https://blog.csdn.net/xiaogeldx/article/details/87315081 铺垫 数据表示方式 - 计算机使用二进制作为自己的机器语言也就是数据的表示方 ...
- 【20190415】JavaScript-事件流与stopPropagation()、stopImmediatePropagation()的误区解析
这两天仔细看了一下MDN上关于事件流机制和相关方法的文档,发现有个很大的误区.过去我一直以为stopPropagation()就是用来阻止事件冒泡的,甚至很多博客和菜鸟教程上都是这样写的.但实际上文档 ...
- vue中引用swiper轮播插件
有时候我们需要在vue中使用轮播组件,如果是在vue组件中引入第三方组件的话,最好通过npm安装,从而进行统一安装包管理. 申明:本文所使用的是vue.2x版本. 通过npm安装插件: npm ins ...
- 从.Net到Java学习第一篇——开篇
以前我常说,公司用什么技术我就学什么.可是对于java,我曾经一度以为“学java是不可能的,这辈子不可能学java的.”结果,一遇到公司转java,我就不得不跑路了,于是乎,回头一看N家公司交过社保 ...
- win10系统关闭自动更新
win10关闭自动更新 步骤①右键“此电脑”选择“管理”选项 步骤②(如下图所示): 步骤③: 步骤④: 好啦!这样就大功告成了!
- Linux中输入输出重定向的问题
Linux 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示.一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器. 输出重定向:命令的输出不 ...
- SQL数据库一些系统语法含义
昨天在数据库中建立数据表的时候要求显示的添加一些系统语法规则,对于这些设置不知道都是什么含义,这次记录下来供以后学习. (1)SET ANSI_NULLS ON语句 T-SQL支持在与空值进行比较时, ...
- 001_python多进程实例
一.工作中需要执行zk数据对比,需要按照机器进行并发,举例以下的例子 # coding:utf8 # !/usr/bin/python import time from multiprocessing ...