QT商业播放器

news2025/1/12 20:57:13

QT商业播放器

总体架构图

在这里插入图片描述

架构优点:解耦,采用生产者消费者设计模式,各个线程各司其职,通过消息队列高效协作

这个项目是一个基于ijkplayer和ffplayer.c的QT商业播放器,
项目有5部分构成:
前端QT用户界面
后端是集成了ffplayer.c的类--播放的核心逻辑,
中间层有3个模块,
	一是ijkmp类-暴露给前后端向消息队列发消息,
	二是参考ijkplayer实现的单链表消息队列,
	三是用QThread启动的消息循环线程--循环取消息处理业务

下面我来依次说明这些模块:

QT界面

用户界面设计

基本机制

利用QT的信号和槽函数机制,界面事件触发后向消息队列发送消息

  1. 界面元素事件绑定信号
  2. 信号绑定槽函数
  3. 槽函数向消息队列发送对应事件的消息,通知ffplay开始工作
  4. ffplay将视频画面回调到qt界面,声音回调到SDL音频播放流
  5. ffplay内部通过各个信号量控制工作流状态,响应消息只需要调用接口改变ffplay内部的各个信号量即可

元素事件包括:

  • 播放,暂停,停止

  • 快进,快退(按钮seek)

  • 进度条seek

  • 音量控制

  • 文件路径

后端ffplay类

播放器的播放逻辑–一个播放器真正的灵魂部分

主要接口

主要接口就是stream_open(),和stream_close()

  • stream_open负责启动线程和各个队列
  • stream_close负责关闭线程并且回收资源

主要工作流程

  • 解复用线程将从媒体文件中解出来的原始码流包插入到音频包队列和视频包队列

    1.创建解复用上下文结构体(对文件数据的格式化)
    	avformat_alloc_context
    2.打开文件,主要是探测协议类型
    	avformat_open_input
    3.探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息
    	avformat_find_stream_info
    4.获取音频和视频流标志--为了5划分包
    	av_find_best_stream
    5.【循环】读取媒体数据,得到的是音视频分离后、解码前的数据包,将数据包插入到对应的包队列
    	av_read_frame
    	video_packet_queue.push
    	audio_packet_queue.push
    
  • 2个解码线程从对应包队列中拿到包数据,由对应的解码器解码出帧数据,将音频帧插入到音频帧队列,视频帧插入到视频帧队列

    1.创建解码上下文
    	avcodec_alloc_context3
    2.从解复用上下文中获取码流的信息,并绑定到解码上下文中
    	avcodec_parameters_to_context
    3.配置解码器,根据对应码流的格式配置(codec_id)
    	avcodec_find_decoder
    4.初始化解码器
    	avcodec_open2
    5.【循环】从包队列中获取包,将包丢到解码器,解码出帧数据,加入到帧队列
    	【视频解码线程】
    	video_packet_queue.get
    	avcodec_send_packet
    	avcodec_receive_frame
    	video_frame_queue.push
    	
    	【音频解码线程】
    	audio_packet_queue.get
    	avcodec_send_packet
    	avcodec_receive_frame
    	audio_frame_queue.push
    
  • 播放线程从帧队列中拿到帧数据,经过音视频同步后,视频帧数据回调到qt的Widget容器,绘制出画面;音频数据回调到SDL音频播放函数

    【音频播放线程】
    1.初始化音频设备
    	SDL_OpenAudio
    2.配置数据回调函数
    	【循环】取帧队列数据拷贝到SDL音频播放流中
    	audio_frame_queue.get
    	
    【视频播放线程】
    1.配置QT显示窗口
    	painter.drawImage
    2.获取队列当前Frame,使用ffmpeg的Scale3转换算法将frameYUV格式图像统一转为RGB格式图像,调用QT显示窗口的刷新回调函数
    	video_frame_queue.get
    	视频同步音频:计算音视频pts差,设置阈值,大于渲染上一帧,小于丢帧
    	Scale3
    	video_refresh_callback
    

亮点

缓存队列

这个部分有2部分队列,包队列和帧队列

包队列

包队列是原始码流包的缓存队列

2个帧队列
H264码流包队列
	NALU包:由分割符(00 00 00 01)、头信息、压缩数据构成

AAC码流包队列
	ADTS包:由分隔符(0xFFF)、头信息、压缩数据构成


PacketQueue 是一个链表队列

  • 内存充足情况下:可以不限制将数据包放入队列中,不需要考虑队列的大小。

  • 控制队列大小:如果我们需要控制队列的大小,我们可以使用以下三个变量来限制队列节点的数量:

    size:控制队列中数据包的总大小。

    duration:控制队列中数据包的总播放时间。

    nb_packets:控制队列中数据包的数量。

    在ffplay中,限制所有队列总大小为:15mb
    这是一个经验数值,大概能缓存4k视频2.4s左右
    
    而我在设计的时候直接用的各队列播放时间<2.4s控制即可
    超过就队满,不让存包数据,限制队列大小
    
帧队列

帧队列是解码后的可播放音视频数据

视频帧队列
	每一帧都是YUV格式图像数据

音频帧队列
	每一帧都是PCM采样帧流数据
	

FrameQueue是一个循环数组队列

  • 数组队列适合于事先明确了缓冲区的最⼤容量的情形

  • 避免假队空----定义一个size

  • 写端位于解码线程,读端位于播放线程

  • 设置互斥锁机制—线程安全

在ffplay中:一般设置为音频队列最大9帧,视频队列最大3帧

因为缓存的是解码后的帧,所以队列不能设置过大,过大容易爆内存,通常是缓存一个比较小的值

ijk播放器核心类

里面维护一个消息队列和真正的播放器ffplayer

ijk播放器核心类暴露给前端的接口都是往消息队列中插入消息,不会直接操作ijkplayer。

比如开始播放,暂停,seek等,都是前端调用ijkmp暴露给前端的接口,向消息队列中插入消息,然后在消息循环线程取消息时,在消息分发过滤器中才操作ijkplayer

消息循环子线程

使用QThread启动message_loop消息循环线程

方便利用QT的信号和槽函数机制,qt_ui及时响应后端发给前端的消息

设置消息分发过滤器,处理前端发给后端的消息

设计

在这里插入图片描述

流程

message_loop是QThread启动的具体run函数,里面主要是一个while循环,调用ijkmp的消息分发过滤器获取消息,然后根据返回的消息做响应

而ijkmp的消息分发过滤器会先检测这个消息

  • 如果是前端发向后端的消息,就直接调用ffplay类控制播放,继续取下一个消息;
  • 如果是后端发向前端的消息,就直接返回给message_loop循环线程,让message_loop利用信号和槽函数,控制qt界面的响应

亮点

  • 采用消息分发过滤器模块,只开辟一个线程,同步处理2个端的业务

消息队列

概念

  • 消息队列是连接qt界面和ijk播放器核心之间的桥梁

  • 作用就是传输指令

  • 本质就是结构体单链表队列

消息节点设计
  • 数据域:设计为三个int值,1个任意类型值+任意类型值的释放函数指针

  • 指针域:next指针指向下一个节点

两个队列
  • 工作消息队列:正常请求消息节点,增删

  • 回收消息队列:空消息节点,循环使用

亮点

  • 使用回收消息队列

    使用回收消息队列可以有效提高程序效率
    
    插入消息节点时先从回收消息队列中直接取空节点赋值新消息,如果没有再创建新节点赋值新消息
    
    取节点后,把消息节点清空,插入到回收消息队列中
    

功能实现

播放暂停停止

  • 前端qt界面发送相应消息

  • 在消息循环中,检测到消息,调用ffplay相关接口,改变ffplay类中的控制变量,进而改变线程状态(线程内循环会检测这些变量)

  • 播放暂停是设置了一个暂停标志位,暂停1,播放0

    在播放线程会去检测暂停标志位,如果暂停线程就休眠100ms,然后continue跳过本次循环
    
  • 停止是设置了一个退出标志位,停止1,未停止0

    各个线程都会检测退出标志位,如果退出,break退出循环
    

快进、快退、进度条seek

1.前端发送seek消息,并携带对应seek后的位置-时间戳
2.消息循环检测到后,调用ffplay类相关接口,改变ffplay类中的控制变量
	seek_req--标记位
	seek_pos--seek时间戳
3.在解复用线程中检测seek_req,调用ffmpeg的API seek到对应位置,并且刷新包队列和帧队列,给新的包的serial++
	avformat_seek_file
	serial--标记位,区分不同seek段,播放的时候会检测
4.在解码线程中中检测seek_req,向解码器加入冲刷包,冲刷解码器--因为解码器解码会缓存一些帧(IP帧)
	

音视频同步

音视频同步这块有3种方式,视频同步音频,音频同步视频,加入外部时钟,音视频一块同步外部时钟

因为研究发现用户对于音频更加敏感,所以一般都是让视频去同步音频
我这个项目中采用的就是视频去同步音频

基本思路就是
	视频帧播放快于音频帧播放:睡一会,持续渲染最后一帧
	视频帧播放慢于音频帧播放:丢帧处理

然后还要一个点就是音视频同步流畅的范围是音频时间戳-视频时间戳在-100ms到25ms内,这是一个国际标准,可以拿这个区间作为检测的阈值范围

具体做法
	在视频播放时,检测当前视频帧播放时间戳pts和当前音频帧时间戳的差值diff(diff=音频pts-视频pts)
	如果差值diff在-100ms到25ms内,就说明已经同步
	如果差值diff超过25ms,说明音频快于视频,视频慢了,视频丢帧不渲染
	如果差值diff小于-100ms,说明音频慢于视频,视频快了,持续渲染最后一帧,然后视频线程休眠差值diff的绝对值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视频二维码的制作方法,支持内容修改编辑

现在学生经常会需要使用音视频二维码&#xff0c;比如外出打开、才艺展示、课文背诵等等。那么如何制作一个可以长期使用的二维码呢&#xff1f;下面来给大家分享一个二维码制作&#xff08;免费在线二维码生成器-二维码在线制作-音视频二维码在线生成工具-机智熊二维码&#x…

快速了解Spring Cache

SpringCache是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单的加一个注解&#xff0c;就可以实现缓存功能。 SpringCache提供了一层抽象&#xff0c;底层可以切换不同的缓存实现。例如&#xff1a; EHChche Redis Caffeine 常用注解&#xff1a; Enabl…

JMETER自适应高分辨率的显示器

系列文章目录 历史文章 每天15分钟JMeter入门篇&#xff08;一&#xff09;&#xff1a;Hello JMeter 每天15分钟JMeter入门篇&#xff08;二&#xff09;&#xff1a;使用JMeter实现并发测试 每天15分钟JMeter入门篇&#xff08;三&#xff09;&#xff1a;认识JMeter的逻辑控…

UG\NX二次开发 获取所有子部件,封装两个函数

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 感谢粉丝订阅 感谢 凉夜ronin 订阅本专栏,非常感谢。 简介 UG\NX二次开发 获取所有子部件,封装两个函数 效果 获取非抑制的所有子部件 //获取非抑制的所有子部件 vector<tag_t> GetChildPart(tag_t partOcc) {…

MyBatisPlus(十)判空查询

说明 判空查询&#xff0c;对应SQL语句中的 IS NULL语句&#xff0c;查询对应字段为 NULL 的数据。 isNull /*** 查询用户列表&#xff0c; 查询条件&#xff1a;电子邮箱为 null 。*/Testvoid isNull() {LambdaQueryWrapper<User> wrapper new LambdaQueryWrapper<…

基于安卓android微信小程序的远景民宿预订小程序

运行环境 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Ma…

W25Q128芯片手册精读

文章目录 前言1. 概述2. 特性3. 封装类型和引脚配置3.1 8焊盘WSON 8x6 mm3.2其他封装 4. 引脚描述4.1 片选4.2 串行数据输入输出4.3 写保护4.4 保持脚4.5 时钟 5. 块图6. 功能描述6.1 SPI功能6.1.1 标准SPI6.1.2 双通道SPI6.1.3 四通道SPI6.1.4 保持功能 6.2 写保护6.2.1 写保护…

【Golang】gin框架入门

文章目录 gin框架入门认识gingo流行的web框架gin介绍快速入门 路由RESTful API规范请求方法URI处理函数分组路由 请求参数GET请求参数POST请求参数路径参数文件参数 响应字符串方式JSON方式XML方式文件格式设置HTTP响应头重定向YAML方式 模板渲染基本使用多个模板渲染自定义模板…

SpringCache--缓存框架 ----苍穹外卖day7

目录 简介 ​快速入门 引入依赖 常用注解​ 使用步骤 1.开启缓存注解 2. Cacheable注解 简介 快速入门 引入依赖 常用注解 使用步骤 1.开启缓存注解 2. Cacheable注解 该注解仅用于查询操作&#xff0c…

零基础部署nginx mysql springboot

参考&#xff1a;写给开发人员看的Docker干货&#xff0c;零基础部署nginx mysql springboot 一、连接linux 阿里云 参考&#xff1a;部署到Linux 可能需要购买&#xff1a;购买链接 二、安装docker # 先切换到root用户下 sudo su# 更新apt-get&#xff0c;保证apt-get最新…

Shiro应用到Web Application

一、权限基础 a) 认证(你是谁&#xff1f;) 判断你(被认证者)是谁的过程。通常被认证者提供用户名和密码。 常见的认证包含如下几种&#xff1a; 匿名认证&#xff1a;允许访问资源&#xff0c;不做任何类型的安全检查。表单认证&#xff1a;访问资源之前&#xff0c;需要提…

SpringBoot vue云办公系统

SpringBoot vue云办公系统 系统功能 云办公系统 登录 员工资料管理: 搜索员工 添加编辑删除员工 导入导出excel 薪资管理: 工资账套管理 添加编辑删除工资账套 员工账套设置 系统管理: 基础信息设置 部门管理 职位管理 职称管理 权限组管理 操作员管理 开发环境和技术 开发语…

JavaEE-线程进阶

模拟实现一个定时器 运行结果如下&#xff1a; 上述模拟定时器的全部代码&#xff1a; import java.util.PriorityQueue;//创建一个类&#xff0c;用来描述定时器中的一个任务 class MyTimerTask implements Comparable<MyTimerTask> {//任务执行时间private long …

数据分析视角中的商业分析学习笔记

数据分析一大堆&#xff0c;结果却是大家早就知道的结论&#xff1f;是工具和方法出问题了吗&#xff1f;真正原因可能是你的思维有误区。 为什么分析的这么辛苦&#xff0c;得出的结论大家早知道&#xff0c;谁谁都不满意&#xff1f;核心原因有3个&#xff1a; 分析之前&am…

DHCPsnooping 配置实验(2)

DHCP报文泛洪攻击 限制接收到报文的速率 vlan 视图或者接口视图 dhcp request/ dhcp-rate dhcp snooping check dhcp-request enable dhcp snooping alarm dhcp-request enable dhcp snooping alarm dhcp-request threshold 1 超过则丢弃报文 查看[Huawei]dis dhcp statistic…

使用python-opencv检测图片中的人像

最简单的方法进行图片中的人像检测 使用python-opencv配合yolov3模型进行图片中的人像检测 1、安装python-opencv、numpy pip install opencv-python pip install numpy 2、下载yolo模型文件和配置文件&#xff1a; 下载地址&#xff1a; https://download.csdn.net/down…

如何使用 AI与人工智能的定义、研究价值、发展阶段

目录 一、什么是人工智能 二、人工智能的研究价值 三、人工智能的发展阶段 一、什么是人工智能 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一门研究如何使计算机能够模拟和执行人类智能活动的科学与技术。人工智能旨在开发智能代理&…

CLIP 论文逐段精读【论文精读】

00:06评价&#xfeff; 评价&#xff1a;工作clip呢自从去年2月底提出就立马火爆全场&#xff0c;他的方法出奇的简单&#xff0c;但是效果呢又出奇的好&#xff0c;很多结果和结论呢都让人瞠目结舌。比如呢作者说clip的这个迁移学习能力是非常强的&#xff0c;它预训好的这个模…

【C语言】利用数组处理批量数据(一维数组和二维数组)

前言:在前面学习的程序中使用的变量都属于基本类型&#xff0c;例如整型、字符型、浮点型数据&#xff0c;这些都是简单的数据类型。对于简单的问题&#xff0c;使用这些简单的数据类型就可以了。但是对于有些需要处理的数据&#xff0c;只用以上简单的数据类型是不够的&#x…

Obsidian插件推荐_231005

起因 十一在家整理 OB 笔记&#xff0c;发现两个超好用的插件&#xff1a;Linter & tag wrangler。 前一阵把 Obsidian 笔记用 Hexo 转换成静态网页发到 github.io 做自己 blog 网站。发现了笔记中的一些问题&#xff0c;比如 tag 过多、重复&#xff0c;markdown 格式不…