[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作为demuxer与decoder之间的缓冲区,目的是实现一个packet队列,该队列中的packet先进先出。FFmpeg提供了一个AVPacketList结构体,我们可以用这个结构体来进行队列的构建。

js;auto-links:true;collapse:false;first-line:1;gutter:true;html-script:false;light:false;ruler:false;smart-tabs:true;tab-size:4;toolbar:true;
typedef struct AVPacketList {
AVPacket pkt;
struct AVPacketList *next;
} AVPacketList;

AVPacketList当作链表的节点,其中pkt用于维护packet,next用于连接相邻的节点。由于是用链表来实现队列,因此需要一个指向链表头的指针first_pkt以及一个指向链表尾的指针last_pkt。当需要把packet入队列时,把packet加入到链表末尾,而当要取出packet时,则从链表头部取出。

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++。

前面的章节中我们手动把fltp格式的音频转换为s16的音频,s16的音频格式是把左右声道的音频样本交叉排列的串行数据,ring buffer是一种比较时候用于存储串行数据的数据结构。

Ring buffer,环形缓冲区,原型为一块连续的缓冲区,通过运用指向数据头部(rIndex)以及数据尾部(wIndex)的指针来维护数据的存取,当数据尾部的指针到达缓冲区末尾,就会把尾部指针指向缓冲区开头,同理数据头部的指针也会进行循环移动,如此实现环形缓冲区。

当需要存储数据时,首先需要保证有足够的空间来进行存储,然后从wIndex处开始写入数据,并根据写入数据的长度更新wIndex;当要读取数据时,从rIndex处读取数据,并根据读取的数据长度更新rIndex。

对于上面描述的3种队列,为了线程安全(使得对队列的操作能在多线程上安全使用),我们需要保证出/入队列的操作为原子操作。实现则可以采用SDL提供的mutex。

视频播放可以进行中途退出的操作,那么我们也有必要提供能在中途终止队列的功能。我们这里所说的终止队列,就是使得再次调用出/入队列的函数时,会返回-1,以表示队列已被终止。我们可以通过设置一个变量abort_request来进行判断,当abort_request为1时队列终止,为0则队列正常运行。

队列的abort函数需要实现:

那么在实现出/入队列的函数时

Original: https://www.cnblogs.com/TaigaCon/p/9986827.html
Author: TaigaComplex
Title: [SimplePlayer] 7. 多线程处理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/539650/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spring Cloud 新一代Web框架微服务翘楚(一)

    序言 springcloud是微服务架构的集大成者,将一系列优秀的组件进行了整合。基于springboot构建,对我们熟悉spring的程序员来说,上手比较容易。 通过一些简单的注…

    Java 2023年5月30日
    063
  • 【Java基础】Java注解简单入门

    注解简单来说就是配置,是特别的配置,之前常用的配置文件,可以用注解替换。然后通过反射去获取注解的信息。 如何定义一个注解 你在IDE中新建一个注解定义,是这样的结构的: packa…

    Java 2023年5月29日
    0108
  • 【Java面试手册-算法篇】给定一个数字,请判断是否为回文数字?

    在回答这个问题之前,首先得清楚什么是回文数字,回文数字有什么特征。 回文数字:设n是一任意自然数,若将n的各位数字反向排列所得自然数n1与n相等,则称n为一回文数。通俗地说,回文数…

    Java 2023年6月8日
    063
  • 你文件乱码了么

    之前对文件的编码,解码一直停留在很抽象的层面,就想着各种编码方式,什么gbk,utf8,ascii等等,然后什么方式编码,就用什么方式解码,比较模糊的,而且项目中uft8编码无处不…

    Java 2023年6月6日
    096
  • Java学习笔记(韩顺平教育 b站有课程)

    Java学习地址 视频地址 真的很推荐大家去听老韩的课(非常细谁听谁知道):【零基础 快速学Java】韩顺平 零基础30天学会Java_哔哩哔哩_bilibili 使用心得 说一下…

    Java 2023年6月5日
    093
  • Go 使用 zap 日志库

    1.前言 zap 是我个人比较喜欢的日志库,是 uber 开源的,有较好的性能。很多开源 Go 项目都使用它作为日志组件。 2.安装使用 安装 go get -u go.uber….

    Java 2023年6月8日
    0104
  • 点击按钮收藏

    后台代码 RouteServlet类: FavoriteService接口: FavoriteServiceImpl实现类: FavoriteDao接口: FavoriteDaoI…

    Java 2023年6月6日
    051
  • yum安装软件报错Error: Nothing to do

    今天在一台新服务器上装一些常用软件,一开始安装ncdu(一个很好用的磁盘分析工具,用来查找大文件),报错如下: 在网上找了各种办法,什么更新yum啊,清理yum缓存啊的,统统没用 …

    Java 2023年6月9日
    063
  • SSM整合_年轻人的第一个增删改查_删除

    写在前面 SSM整合_年轻人的第一个增删改查_基础环境搭建SSM整合_年轻人的第一个增删改查_查找SSM整合_年轻人的第一个增删改查_新增SSM整合_年轻人的第一个增删改查_修改S…

    Java 2023年6月5日
    093
  • [Java]HashMap源码解析

    构造函数 // 默认构造函数。 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fie…

    Java 2023年6月5日
    071
  • Java精进-手写持久层框架

    前言 本文适合有一定java基础的同学,通过自定义持久层框架,可以更加清楚常用的mybatis等开源框架的原理。 JDBC操作回顾及问题分析 学习java的同学一定避免不了接触过j…

    Java 2023年6月13日
    0125
  • 14.将对象的字符串写入文件并保存到盘符

    public JsonVo savaCloudScript(ScriptModelVO scriptModelVo) throws RollbackableBizException…

    Java 2023年6月13日
    086
  • 基于Redisson的延迟队列实现

    package com.dong.mytest.demo.client; import cn.hutool.extra.spring.SpringUtil; import com….

    Java 2023年6月5日
    058
  • [Java]HashMap与ConcurrentHashMap的一些总结

    HashMap底层数据结构 JDK7:数组+链表 JDK8:数组+链表+红黑树 JDK8中的HashMap什么时候将链表转为红黑树? 当发现链表中的元素大于8之后,判断当前数组长度…

    Java 2023年6月5日
    072
  • Session管理之ThreadLocal之线程安全

    在各种Session 管理方案中, ThreadLocal 模式得到了大量使用。ThreadLocal 是 Java中一种较为特殊的线程绑定机制。通过ThreadLocal存取的数…

    Java 2023年5月30日
    075
  • JAVA入门基础_从零开始的培训_SSM的学习

    Mybatis的学习 Mybaits快速入门案例 Mybatis开发环境搭建 配置Mybaits的全局配置文件 配置一个Mapper接口文件 配置一个Mapper接口对应的xml文…

    Java 2023年6月9日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球