Qt线程封装FFmpeg播放器类
介绍
工作开发中需要处理的文件很多并无音频,针对这一场景,这里分享工作中自己封装使用的类库。精简的代码实现了播放、暂停、停止、快进、快退、进度跳转、倍速播放功能。直接放代码,方便后期复制使用。
代码
头文件
/*** @file videoplayer.h* @brief ffmpeg实现视频解码* @author ZXT* @date 2023.12.23*/#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H#include <QThread>
#include <QDebug>
#include <QImage>extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
}class VideoDecoder : public QThread
{Q_OBJECT
public:explicit VideoDecoder(QObject *parent = nullptr);~VideoDecoder();/*** @brief 开始播放* @param path入参 路径*/void startPlay(const QString &path);/*** @brief 停止播放*/void stopPlay();/*** @brief 暂停或继续播放* @param pause入参 ture暂停 false继续*/void pausePlay(bool pause);/*** @brief 进度跳转播放* @param sec入参 跳转的秒数*/void seekPlay(int sec);/*** @brief 倍速播放* @param speed入参 播放速度*/void speedPlay(float speed = 1.0);signals:/*** @brief 播放时长* @param sec入参 时长秒数*/void sigDuration(int sec);/*** @brief 播放位置* @param sec入参 当前秒数*/void sigPlayPosition(int sec);/*** @brief 播放结束* @param ret出参 状态码*/void sigPlayFinished(int ret);/*** @brief 发送解码后的显示图像* @param image出参 视频图像*/void sigSendImage(const QImage &image);protected:void run();private://运行标志volatile bool m_isRun = false;//暂停状态volatile bool m_pause = false;//进度跳转状态volatile bool m_seek = false;//优化跳转速度volatile bool m_seekFilter = false;//文件路径QString m_filePath;//开始时间 ms单位int64_t m_startTime = 0;//暂停时间 ms单位int64_t m_pauseTime = 0;//跳转时间 ms单位int64_t m_seekTime = 0;//时长信息 秒int m_videoDuration = 0;//播放速率float m_speedValue = 1.0;//上一次的播放速率float m_lastSpeedValue = 1.0;
};#endif // VIDEOPLAYER_H
实现文件
#include "videoplayer.h"VideoDecoder::VideoDecoder(QObject *parent) : QThread(parent)
{}VideoDecoder::~VideoDecoder()
{quit();wait();
}//开始播放
void VideoDecoder::startPlay(const QString &path)
{m_filePath = path;m_isRun = true;m_pause = false;m_seek = false;m_speedValue = 1.0;m_lastSpeedValue = 1.0;this->start();
}//停止播放
void VideoDecoder::stopPlay()
{m_pause = false;m_seek = false;m_isRun = false;
}//暂停或继续播放
void VideoDecoder::pausePlay(bool pause)
{m_pause = pause;if(pause){m_pauseTime = av_gettime_relative() / 1000;}else{int offset = av_gettime_relative() / 1000 - m_pauseTime;m_startTime += offset;}
}//进度跳转播放
void VideoDecoder::seekPlay(int sec)
{if(!m_isRun)return;if(m_videoDuration == sec)sec -= 2;m_seekTime = sec * 1000;m_seekFilter = true;m_seek = true;
}//倍速播放
void VideoDecoder::speedPlay(float speed)
{int64_t elapsed = av_gettime_relative() / 1000 - m_startTime;int offset = elapsed - (elapsed * this->m_lastSpeedValue / speed);m_startTime += offset;m_speedValue = speed;m_lastSpeedValue = speed;
}void VideoDecoder::run()
{qDebug() << "VideoDecoder start" << m_filePath;std::string temp = m_filePath.toStdString();AVFormatContext *inFmtCtx = avformat_alloc_context();int ret = avformat_open_input(&inFmtCtx, temp.c_str(), NULL, NULL);if (ret < 0){qDebug() << "open input error";emit sigPlayFinished(-1);return;}//获取流信息ret = avformat_find_stream_info(inFmtCtx, NULL);if (ret < 0){qDebug() << "find stream info error";emit sigPlayFinished(-1);return;}//获取视频流信息 目前只有视频流bool getVideo = false;int videoIndex = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);AVStream *videoStream = NULL;AVCodec *videoDecoder = NULL;AVCodecContext *videoDeCodecCtx = NULL;if (videoIndex >= 0){videoStream = inFmtCtx->streams[videoIndex];//初始化解码器videoDecoder = avcodec_find_decoder(videoStream->codecpar->codec_id);videoDeCodecCtx = avcodec_alloc_context3(videoDecoder);if(videoDeCodecCtx != NULL){avcodec_parameters_to_context(videoDeCodecCtx, videoStream->codecpar);ret = avcodec_open2(videoDeCodecCtx, videoDecoder, NULL);if(ret < 0)avcodec_free_context(&videoDeCodecCtx);elsegetVideo = true;}}if(!getVideo){avformat_close_input(&inFmtCtx);emit sigPlayFinished(-1);return;}AVFrame *swsFrame = av_frame_alloc();SwsContext *swsCtx = nullptr;uint8_t *videoData = nullptr;//输出视频参数信息if(getVideo){int srcW = videoStream->codecpar->width;int srcH = videoStream->codecpar->height;AVPixelFormat format = videoDeCodecCtx->pix_fmt;m_videoDuration = inFmtCtx->duration / AV_TIME_BASE;emit sigDuration(m_videoDuration);int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, srcW, srcH, 1);videoData = (uint8_t *)av_malloc(byte * sizeof(uint8_t));av_image_fill_arrays(swsFrame->data, swsFrame->linesize, videoData, (AVPixelFormat)AV_PIX_FMT_RGB32, srcW, srcH, 1);swsCtx = sws_getContext(srcW, srcH, (AVPixelFormat)format, srcW, srcH, (AVPixelFormat)AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);}//开始时刻m_startTime = av_gettime_relative() / 1000;int64_t ptsTime = 0;int64_t ptsBackup = 0;int curPlayPos = 0;AVPacket *packet = av_packet_alloc();AVFrame *videoFrame = av_frame_alloc();while(m_isRun){//暂停if(m_pause){QThread::msleep(200);continue;}//进度切换if(m_seek){//跳转的播放时刻 单位微秒int64_t timeStamp = m_seekTime * 1000;if (inFmtCtx->start_time != AV_NOPTS_VALUE){timeStamp += inFmtCtx->start_time;}//注:seek若关键帧间隔大需避免延时timeStamp = av_rescale_q(timeStamp, AVRational{1, AV_TIME_BASE}, videoStream->time_base);ret = av_seek_frame(inFmtCtx, videoIndex, timeStamp, AVSEEK_FLAG_BACKWARD);if(ret < 0){qDebug() << "av_seek_frame fail" << m_seekTime;}else{//清空内部帧队列if(videoDeCodecCtx)avcodec_flush_buffers(videoDeCodecCtx);//调整时钟int64_t offset = m_seekTime - ptsTime;m_startTime -= offset;}m_seek = false;}//不断读取packetret = av_read_frame(inFmtCtx, packet);if (ret == AVERROR_EOF){m_isRun = false;break;}if(packet->stream_index == videoIndex){//编码数据进行解码ret = avcodec_send_packet(videoDeCodecCtx, packet);if (ret < 0){av_packet_unref(packet);continue;}ret = avcodec_receive_frame(videoDeCodecCtx, videoFrame);if (ret < 0){av_packet_unref(packet);continue;}//计算当前帧实际时间 msptsTime = videoFrame->pts * av_q2d(videoStream->time_base) * 1000;if(m_seekFilter){//跳转播放时间不符合的帧直接丢弃 默认阈值200msint offset = m_seekTime - ptsTime;if(0 > offset || offset < 200){m_seekFilter = false;}else{av_frame_unref(videoFrame);av_packet_unref(packet);continue;}}//倍速将改变原pts值 太高倍速会导致解码消耗过高、渲染过频繁可考虑抽帧ptsBackup = ptsTime;ptsTime *= (1 / m_speedValue);//控制速度 ms单位qint64 elapsed = av_gettime_relative() / 1000 - m_startTime;int64_t sleepMs = ptsTime - elapsed;if(sleepMs > 3){QThread::msleep(sleepMs);}//发送播放位置信息int sec = ptsBackup / 1000;if(sec != curPlayPos){curPlayPos = sec;emit sigPlayPosition(curPlayPos);}//将解码后的frame数据转换为Imagesws_scale(swsCtx, (const uint8_t *const *)videoFrame->data, videoFrame->linesize, 0, videoFrame->height, swsFrame->data, swsFrame->linesize);QImage image((uchar *)videoData, videoFrame->width, videoFrame->height, QImage::Format_RGB32);QImage copy = image.copy();emit sigSendImage(copy);av_frame_unref(videoFrame);}av_packet_unref(packet);}//释放资源sws_freeContext(swsCtx);av_frame_free(&swsFrame);av_free(videoData);av_packet_free(&packet);av_frame_free(&videoFrame);avcodec_free_context(&videoDeCodecCtx);avformat_close_input(&inFmtCtx);emit sigPlayFinished(0);qDebug() << "VideoDecoder end" << m_filePath;return;
}相关文章:
Qt线程封装FFmpeg播放器类
介绍 工作开发中需要处理的文件很多并无音频,针对这一场景,这里分享工作中自己封装使用的类库。精简的代码实现了播放、暂停、停止、快进、快退、进度跳转、倍速播放功能。直接放代码,方便后期复制使用。 代码 头文件 /*** file videopla…...
git 常用命令 修改 远程仓库 默认分支
git remote set-head origin -a 将 origin/HEAD 指向 远程仓库的 默认分支(-a 即 --auto) git remote set-head origin dev 将 origin/HEAD 指向 (origin/dev) git remote set-head origin -d 删除 origin/…...
springboot项目禁用dataSource数据源功能,只需修改yml文件,关闭数据库连接功能
当我们遇到以下报错: Description: Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class Action: Consider the following: If you …...
【Vue.js设计与实现解读-1】
Vue设计与实现阅读-1 1、命令式和声明式2、性能3、虚拟DOM性能4、运行时和编译时5、总结 前言 最近工作清闲了些,想着很久没有看书,Vue.js设计与实现这本书看了好几次都没有读完,趁着这个机会边读边记录一下吧。如果有理解的不正确的地方&…...
苗情生态自动监测系统-科普知识
随着科技的飞速发展,智能化技术在各个领域的应用越来越广泛。在农业领域,苗情生态自动监测系统的出现,为农业生产带来了革命性的变革。它不仅能够实时监测植物的生长状况,还能对环境因素进行全面监控,为农业生产提供科…...
test 系统学习-04-test converate 测试覆盖率 jacoco 原理介绍
测试覆盖率 测试覆盖率(test coverage)是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据,有利于客观认识软件质量,正确了解测试状态,有效改进测试工作。 当然,要发挥这些作用,前提是我们掌握了真实的测试覆盖…...
小型企业成为网络犯罪分子获取数据的目标
在过去十年的大部分时间里,网络犯罪的巨额资金来自针对大型组织的勒索软件攻击。这种威胁仍然存在。但犯罪分子可能会将注意力转向中小企业 (SMB)。这对消费者的影响将是巨大的。 将软件即服务 (SaaS) 技术用于核心业务功能继续将中小企业整合到全球供应链中。由于…...
PyTorch的Tensor(张量)
一、Tensor概念 什么是张量? 张量是一个多维数组,它是标量、向量、矩阵的高维拓展 Tensor与Variable Variable是torch.autograd中的数据类型,主要用于封装Tensor,进行自动求导。 data: 被包装的Tensorgrad: data的梯度&…...
spug发布问题汇总记录
问题导览 1. [vite]: Rollup failed to resolve import "element-plus" from "src/main.js". 项目框架简介 vue3viteelement-plus 解决方案 - 1. 配置淘宝镜像源:npm config set registry https://registry.npm.taobao.org/ - 2. npm inst…...
SpringBoot-搭建集成Mybatis的项目
本文介绍了如何在IntelliJ IDEA中使用SpringBoot和Mybatis构建Java Web应用程序。通过本文的学习,读者将了解如何使用IntelliJ IDEA快速搭建一个基于SpringBoot和Mybatis的Java Web应用程序,提高开发效率。IntelliJ IDEA是一款功能强大的Java集成开发环境…...
mysql隐式转换规则
MySQL 中的隐式类型转换发生在比较操作或者其他一些需要特定数据类型参数的上下文中,如果参与操作的表达式或列的数据类型不匹配,MySQL 就会自动进行数据类型转换以适配预期的数据类型。 以下是 MySQL 的一些常见隐式转换规则: 字符串和数字…...
怎么解决 Nginx反向代理加载速度慢?
Nginx反向代理加载速度慢可能由多种原因引起,以下是一些可能的解决方法: 1,网络延迟: 检查目标服务器的网络状况,确保其网络连接正常。如果目标服务器位于不同的地理位置,可能会有较大的网络延迟。考虑使用…...
Eureka工作原理超详细讲解介绍
Eureka 是 Netflix 开源的一款服务注册与发现框架,主要用于构建分布式系统中的服务治理和负载均衡。下面是关于 Eureka 工作原理的详细介绍:1.Eureka 架构: Eureka 采用了客户端-服务器架构,包括 Eureka Server 和 Eureka Client …...
SQL WHERE 语句(条件选择)
WHERE 子句用于过滤记录。 SQL WHERE 子句 WHERE 子句用于提取那些满足指定条件的记录。 SQL WHERE 语法 SELECT column1, column2, ... FROM table_name WHERE condition; 参数说明: column1, column2, ...:要选择的字段名称,可以为多…...
用UCLI(TCL)控制verdi dump 波形
UCLI(Unified Command-line Interface)为Synopsys验证工具了提供一组通用命令,通过UCLI可以执行任意TCL(Tool Command Language)命令。在我们的验证环境中,通常跟ucli打交道的地方是用来控制开始dump和结束…...
如何使用 Python+selenium 进行 web 自动化测试?
Selenium是一个自动化测试工具,它可以模拟用户在浏览器中的操作,比如点击、输入、选择等等。它支持多种浏览器,包括Chrome、Firefox、Safari等等,并且可以在多个平台上运行。 安装和配置Selenium 在使用Selenium之前,…...
约瑟夫问题
约瑟夫问题 题目描述 n n n 个人围成一圈,从第一个人开始报数,数到 m m m 的人出列,再由下一个人重新从 1 1 1 开始报数,数到 m m m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。…...
文件管理方法:利用文件大小进行筛选,高效移动文件至目标文件夹
在日常工作中,文件管理是一项至关重要的任务。为了更高效地管理文件,可以利用文件大小进行筛选,并将文件快速移动至目标文件夹。接下来一起来看看云炫文件管理器如何利用文件大小进行筛选,以及如何高效移动文件至目标文件夹的方法…...
python报错:TypeError: Descriptors cannot be created directly.
问题 报错提示: TypeError:不能直接创建描述符。 如果此调用来自 _pb2.py 文件,则您生成的代码已过期,必须使用 protoc > 3.19.0 重新生成。 如果您不能立即重新生成原型,其他一些可能的解决方法是: 1.…...
Linux 内核调试
文章目录 一、方法论 一、方法论 qemu 虚拟机 Linux内核学习 Linux 内核调试 一:概述 Linux 内核调试 二:ubuntu20.04安装qemu Linux 内核调试 三:《QEMU ARM guest support》翻译 Linux 内核调试 四:qemu-system-arm功能选项整…...
嵌入式系统错误处理机制与实现
嵌入式系统中的错误处理机制深度解析1. 错误概念与分类1.1 错误分类体系在嵌入式系统开发中,错误处理是确保系统可靠性的关键环节。从严重性维度分析,程序错误可分为两类:致命性错误:系统无法执行恢复操作,典型处理方式…...
别再为HackBar许可证发愁了!手把手教你用Burp Suite社区版完成同类测试
从HackBar到Burp Suite:安全测试工具的高效迁移指南 在Web安全测试领域,工具的选择往往决定了工作效率的上限。许多初级安全研究人员习惯使用HackBar这类轻量级浏览器插件进行快速测试,但当遇到功能限制或商业授权问题时,往往会陷…...
极域电子教室突破技术:从系统控制到自主操作的攻防对抗
极域电子教室突破技术:从系统控制到自主操作的攻防对抗 【免费下载链接】JiYuTrainer 极域电子教室防控制软件, StudenMain.exe 破解 项目地址: https://gitcode.com/gh_mirrors/ji/JiYuTrainer 一、核心痛点:极域电子教室的控制枷锁 在信息化教…...
SPIRAN ART SUMMONER对比评测:与传统图像生成算法的效果差异
SPIRAN ART SUMMONER对比评测:与传统图像生成算法的效果差异 本文通过实际测试对比,展示SPIRAN ART SUMMONER与传统图像生成算法在效果、速度、易用性等方面的真实差异,用数据和案例说话。 1. 评测背景与方法 图像生成技术近年来发展迅猛&am…...
S32的进阶之路->7,S32DS中FTM中断与PWM结合的实战应用
1. FTM中断与PWM结合的应用场景 在嵌入式开发中,定时器和PWM(脉冲宽度调制)是最常用的外设功能之一。S32K144芯片的FTM(FlexTimer Module)模块提供了强大的定时和PWM生成能力,而将它们与中断结合使用&#…...
Git-RSCLIP模型在计算机网络教学中的应用
Git-RSCLIP模型在计算机网络教学中的应用 1. 引言 计算机网络课程的教学一直面临着抽象概念多、协议交互复杂、拓扑结构难以直观展示的挑战。传统的教学方式往往依赖于静态的图表和文字描述,学生很难真正理解数据包在网络中的流动过程、协议之间的交互关系&#x…...
RISC-V开发工具链技术解析与选型指南
1. RISC-V开发工具链技术解析1.1 RISC-V生态发展背景随着处理器架构领域对开放性和灵活性的需求增长,RISC-V指令集架构凭借其开源特性获得了广泛关注。与传统架构相比,RISC-V免除了授权费用,降低了开发门槛,这使得芯片厂商和工具链…...
如何利用Outline构建现代化团队知识管理体系
如何利用Outline构建现代化团队知识管理体系 【免费下载链接】outline Outline 是一个基于 React 和 Node.js 打造的快速、协作式团队知识库。它可以让团队方便地存储和管理知识信息。你可以直接使用其托管版本,也可以自己运行或参与开发。源项目地址:ht…...
突破reCAPTCHA屏障:EzCaptcha自动化识别实战指南
1. 为什么我们需要自动化处理reCAPTCHA? 每次在网上注册账号或者提交表单时,那个让你"勾选我不是机器人"的小方框,就是reCAPTCHA验证系统。作为谷歌推出的智能验证工具,它确实有效阻止了大量垃圾注册和恶意攻击…...
从DTC诊断码到ECU恢复:深入解析车载CAN总线的BUSOFF快慢恢复机制
从DTC诊断码到ECU恢复:车载CAN总线BUSOFF快慢恢复机制实战指南 当CAN总线上的某个ECU因连续发送失败而触发BUSOFF状态时,整个车载网络的稳定性便面临严峻考验。作为汽车电子诊断工程师,我们常常需要在深夜的生产线上,面对闪烁的故…...
