当前位置: 首页 > news >正文

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播放器类

介绍 工作开发中需要处理的文件很多并无音频&#xff0c;针对这一场景&#xff0c;这里分享工作中自己封装使用的类库。精简的代码实现了播放、暂停、停止、快进、快退、进度跳转、倍速播放功能。直接放代码&#xff0c;方便后期复制使用。 代码 头文件 /*** file videopla…...

git 常用命令 修改 远程仓库 默认分支

git remote set-head origin -a 将 origin/HEAD 指向 远程仓库的 默认分支&#xff08;-a 即 --auto&#xff09; git remote set-head origin dev 将 origin/HEAD 指向 &#xff08;origin/dev&#xff09; git remote set-head origin -d 删除 origin/…...

springboot项目禁用dataSource数据源功能,只需修改yml文件,关闭数据库连接功能

当我们遇到以下报错&#xff1a; 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、总结 前言 最近工作清闲了些&#xff0c;想着很久没有看书&#xff0c;Vue.js设计与实现这本书看了好几次都没有读完&#xff0c;趁着这个机会边读边记录一下吧。如果有理解的不正确的地方&…...

苗情生态自动监测系统-科普知识

随着科技的飞速发展&#xff0c;智能化技术在各个领域的应用越来越广泛。在农业领域&#xff0c;苗情生态自动监测系统的出现&#xff0c;为农业生产带来了革命性的变革。它不仅能够实时监测植物的生长状况&#xff0c;还能对环境因素进行全面监控&#xff0c;为农业生产提供科…...

test 系统学习-04-test converate 测试覆盖率 jacoco 原理介绍

测试覆盖率 测试覆盖率(test coverage)是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据&#xff0c;有利于客观认识软件质量&#xff0c;正确了解测试状态&#xff0c;有效改进测试工作。 当然&#xff0c;要发挥这些作用&#xff0c;前提是我们掌握了真实的测试覆盖…...

小型企业成为网络犯罪分子获取数据的目标

在过去十年的大部分时间里&#xff0c;网络犯罪的巨额资金来自针对大型组织的勒索软件攻击。这种威胁仍然存在。但犯罪分子可能会将注意力转向中小企业 (SMB)。这对消费者的影响将是巨大的。 将软件即服务 (SaaS) 技术用于核心业务功能继续将中小企业整合到全球供应链中。由于…...

PyTorch的Tensor(张量)

一、Tensor概念 什么是张量&#xff1f; 张量是一个多维数组&#xff0c;它是标量、向量、矩阵的高维拓展 Tensor与Variable Variable是torch.autograd中的数据类型&#xff0c;主要用于封装Tensor&#xff0c;进行自动求导。 data: 被包装的Tensorgrad: data的梯度&…...

spug发布问题汇总记录

问题导览 1. [vite]: Rollup failed to resolve import "element-plus" from "src/main.js". 项目框架简介 vue3viteelement-plus 解决方案 - 1. 配置淘宝镜像源&#xff1a;npm config set registry https://registry.npm.taobao.org/ - 2. npm inst…...

SpringBoot-搭建集成Mybatis的项目

本文介绍了如何在IntelliJ IDEA中使用SpringBoot和Mybatis构建Java Web应用程序。通过本文的学习&#xff0c;读者将了解如何使用IntelliJ IDEA快速搭建一个基于SpringBoot和Mybatis的Java Web应用程序&#xff0c;提高开发效率。IntelliJ IDEA是一款功能强大的Java集成开发环境…...

mysql隐式转换规则

MySQL 中的隐式类型转换发生在比较操作或者其他一些需要特定数据类型参数的上下文中&#xff0c;如果参与操作的表达式或列的数据类型不匹配&#xff0c;MySQL 就会自动进行数据类型转换以适配预期的数据类型。 以下是 MySQL 的一些常见隐式转换规则&#xff1a; 字符串和数字…...

怎么解决 Nginx反向代理加载速度慢?

Nginx反向代理加载速度慢可能由多种原因引起&#xff0c;以下是一些可能的解决方法&#xff1a; 1&#xff0c;网络延迟&#xff1a; 检查目标服务器的网络状况&#xff0c;确保其网络连接正常。如果目标服务器位于不同的地理位置&#xff0c;可能会有较大的网络延迟。考虑使用…...

Eureka工作原理超详细讲解介绍

Eureka 是 Netflix 开源的一款服务注册与发现框架&#xff0c;主要用于构建分布式系统中的服务治理和负载均衡。下面是关于 Eureka 工作原理的详细介绍&#xff1a;1.Eureka 架构&#xff1a; Eureka 采用了客户端-服务器架构&#xff0c;包括 Eureka Server 和 Eureka Client …...

SQL WHERE 语句(条件选择)

WHERE 子句用于过滤记录。 SQL WHERE 子句 WHERE 子句用于提取那些满足指定条件的记录。 SQL WHERE 语法 SELECT column1, column2, ... FROM table_name WHERE condition; 参数说明&#xff1a; column1, column2, ...&#xff1a;要选择的字段名称&#xff0c;可以为多…...

用UCLI(TCL)控制verdi dump 波形

UCLI&#xff08;Unified Command-line Interface&#xff09;为Synopsys验证工具了提供一组通用命令&#xff0c;通过UCLI可以执行任意TCL&#xff08;Tool Command Language&#xff09;命令。在我们的验证环境中&#xff0c;通常跟ucli打交道的地方是用来控制开始dump和结束…...

如何使用 Python+selenium 进行 web 自动化测试?

Selenium是一个自动化测试工具&#xff0c;它可以模拟用户在浏览器中的操作&#xff0c;比如点击、输入、选择等等。它支持多种浏览器&#xff0c;包括Chrome、Firefox、Safari等等&#xff0c;并且可以在多个平台上运行。 安装和配置Selenium 在使用Selenium之前&#xff0c;…...

约瑟夫问题

约瑟夫问题 题目描述 n n n 个人围成一圈&#xff0c;从第一个人开始报数,数到 m m m 的人出列&#xff0c;再由下一个人重新从 1 1 1 开始报数&#xff0c;数到 m m m 的人再出圈&#xff0c;依次类推&#xff0c;直到所有的人都出圈&#xff0c;请输出依次出圈人的编号。…...

文件管理方法:利用文件大小进行筛选,高效移动文件至目标文件夹

在日常工作中&#xff0c;文件管理是一项至关重要的任务。为了更高效地管理文件&#xff0c;可以利用文件大小进行筛选&#xff0c;并将文件快速移动至目标文件夹。接下来一起来看看云炫文件管理器如何利用文件大小进行筛选&#xff0c;以及如何高效移动文件至目标文件夹的方法…...

python报错:TypeError: Descriptors cannot be created directly.

问题 报错提示&#xff1a; TypeError&#xff1a;不能直接创建描述符。 如果此调用来自 _pb2.py 文件&#xff0c;则您生成的代码已过期&#xff0c;必须使用 protoc > 3.19.0 重新生成。 如果您不能立即重新生成原型&#xff0c;其他一些可能的解决方法是&#xff1a; 1.…...

Linux 内核调试

文章目录 一、方法论 一、方法论 qemu 虚拟机 Linux内核学习 Linux 内核调试 一&#xff1a;概述 Linux 内核调试 二&#xff1a;ubuntu20.04安装qemu Linux 内核调试 三&#xff1a;《QEMU ARM guest support》翻译 Linux 内核调试 四&#xff1a;qemu-system-arm功能选项整…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...