什么是网站分析,如何快速收录网站,网站后台数据库下载,wordpress自动生成密码QT商业播放器
总体架构图 架构优点#xff1a;解耦#xff0c;采用生产者消费者设计模式#xff0c;各个线程各司其职#xff0c;通过消息队列高效协作
这个项目是一个基于ijkplayer和ffplayer.c的QT商业播放器,
项目有5部分构成#xff1a;
前端QT用户界面
后端是集成了…QT商业播放器
总体架构图 架构优点解耦采用生产者消费者设计模式各个线程各司其职通过消息队列高效协作
这个项目是一个基于ijkplayer和ffplayer.c的QT商业播放器,
项目有5部分构成
前端QT用户界面
后端是集成了ffplayer.c的类--播放的核心逻辑
中间层有3个模块一是ijkmp类-暴露给前后端向消息队列发消息二是参考ijkplayer实现的单链表消息队列三是用QThread启动的消息循环线程--循环取消息处理业务下面我来依次说明这些模块
QT界面 用户界面设计 基本机制
利用QT的信号和槽函数机制界面事件触发后向消息队列发送消息
界面元素事件绑定信号信号绑定槽函数槽函数向消息队列发送对应事件的消息通知ffplay开始工作ffplay将视频画面回调到qt界面声音回调到SDL音频播放流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_framevideo_packet_queue.pushaudio_packet_queue.push2个解码线程从对应包队列中拿到包数据由对应的解码器解码出帧数据将音频帧插入到音频帧队列视频帧插入到视频帧队列 1.创建解码上下文avcodec_alloc_context3
2.从解复用上下文中获取码流的信息并绑定到解码上下文中avcodec_parameters_to_context
3.配置解码器根据对应码流的格式配置(codec_id)avcodec_find_decoder
4.初始化解码器avcodec_open2
5.【循环】从包队列中获取包将包丢到解码器解码出帧数据加入到帧队列【视频解码线程】video_packet_queue.getavcodec_send_packetavcodec_receive_framevideo_frame_queue.push【音频解码线程】audio_packet_queue.getavcodec_send_packetavcodec_receive_frameaudio_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差设置阈值大于渲染上一帧小于丢帧Scale3video_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到对应位置并且刷新包队列和帧队列给新的包的serialavformat_seek_fileserial--标记位区分不同seek段播放的时候会检测
4.在解码线程中中检测seek_req向解码器加入冲刷包冲刷解码器--因为解码器解码会缓存一些帧(IP帧)
音视频同步
音视频同步这块有3种方式视频同步音频音频同步视频加入外部时钟音视频一块同步外部时钟因为研究发现用户对于音频更加敏感所以一般都是让视频去同步音频
我这个项目中采用的就是视频去同步音频基本思路就是视频帧播放快于音频帧播放睡一会持续渲染最后一帧视频帧播放慢于音频帧播放丢帧处理然后还要一个点就是音视频同步流畅的范围是音频时间戳-视频时间戳在-100ms到25ms内这是一个国际标准可以拿这个区间作为检测的阈值范围具体做法在视频播放时检测当前视频帧播放时间戳pts和当前音频帧时间戳的差值diffdiff音频pts-视频pts如果差值diff在-100ms到25ms内就说明已经同步如果差值diff超过25ms说明音频快于视频视频慢了视频丢帧不渲染如果差值diff小于-100ms说明音频慢于视频视频快了持续渲染最后一帧然后视频线程休眠差值diff的绝对值