播放器开发(五):视频帧处理并用SDL渲染播放
目录
学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】
步骤
VideoOutPut模块
1、初始化【分配缓存、读取信息】
2、开始线程工作【从队列读帧->缩放->发送渲染信号到窗口】
VideoWidget自定义Widget类
1、定义内部变量
2、如果使用SDL,需要进行初始化
3、接收到信号后需要执行槽函数进行渲染
主要代码
分配缓存
// 根据格式和视频宽高获取一张图像的字节数据大小int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);// 分配缓存空间buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));// 类似于格式化已经申请的内存av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);// 初始化分配并返回SwsContextswsContext = sws_getContext(decCtx->width, decCtx->height, pixelFormat, videoWidth, videoHeight, AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
执行缩放
sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);
创建SDL窗口纹理渲染器
// 这里必须要先SDL_CreateWindowFrom,然后在设置父类为this,否则会直接把父类当成参数作为背景sdlWindow = SDL_CreateWindowFrom((void *) winId());//渲染器sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);//纹理 大小是视频大小sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);SDL_SetTextureScaleMode(sdlTexture, SDL_ScaleModeLinear);
SDL渲染
int ret = 1;ret = SDL_RenderClear(sdlRenderer);if (ret != 0) {qDebug() << "SDL_RenderClear error";}// 帧数据更新到纹理ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);// 如果这里的帧数据已经是YUV则需要使用下面的// ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);if (ret != 0) {qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();}// 将纹理绘制到渲染器上ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);if (ret != 0) {qDebug() << "SDL_RenderCopy error";}// 刷新渲染器,将内容显示到窗口上SDL_RenderPresent(sdlRenderer);
SDL窗口渲染在mac系统上好像有点问题,缩放的时候图像会很模糊,暂时不知道是什么情况。望知道的朋友告知原因。
QPainter渲染
使用Qt的QPainter进行渲染可以原画像显示,不会有模糊的情况。
// 注意这里需要使用RGB格式
QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);
QPainter painter(this);
painter.drawImage(this->rect(), image);
完整模块
VideoOutPut
1、run函数实现内为什么需要av_usleep(39999):
在run函数中是进行循环读取帧,然后缩放,最后发送信号进行渲染,如果我们不进行sleep,哪么就会在极短的时间内循环读取完所有的帧,渲染过快导致无法看清图像,因此我们需要进行sleep,这个时间对于单个视频流来说,一般是FPS,就是画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。这个值一般是可以通过视频流的一些参数计算出来的,我们放到同步部分在说。
// VideoOutPut.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include "queue/AVFrameQueue.h"
#include <QDebug>
#include <QObject>
#include <QtGui>
#include <QtWidgets>
#include <thread>class VideoOutPut : public QObject {Q_OBJECT
private:std::thread *m_thread;bool isStopped = true; // 是否已经停止 停止时退出线程bool isPlaying = false;// 是否正在播放bool isPause = false; // 是否暂停uint8_t *buffer;//存储解码后图片buffer//图像缩放、颜色空间转换上下文SwsContext *swsContext;AVFrame *playFrame; //转换后的帧对象AVFrameQueue *frameQueue;//解码后的帧队列// 解码器上下文AVCodecContext *decCtx;// 音频解码器上下文
public:VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue);int init();int start();void run();int videoWidth; //视频宽度int videoHeight;//视频高度
Q_SIGNALS:void refreshImage(const SDL_Rect &sdlRect, AVFrame *frame);
};// VideoOutPut.cpp
#include "VideoOutPut.h"
VideoOutPut::VideoOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue): decCtx(dec_ctx), frameQueue(frame_queue) {
}
int VideoOutPut::init() {//获取分辨率大小videoWidth = decCtx->width;videoHeight = decCtx->height;playFrame = av_frame_alloc();AVPixelFormat pixelFormat = decCtx->pix_fmt;qDebug() << av_get_pix_fmt_name(pixelFormat);int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));av_image_fill_arrays(playFrame->data, playFrame->linesize, buffer, AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);swsContext = sws_getContext(decCtx->width, decCtx->height, pixelFormat, videoWidth, videoHeight, AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);return 1;
}
int VideoOutPut::start() {m_thread = new std::thread(&VideoOutPut::run, this);if (!m_thread->joinable()) {qDebug() << "VideoOutPut视频帧处理线程创建失败";return -1;}isStopped = false;isPlaying = true;return 0;
}void VideoOutPut::run() {AVFrame *frame;while (!isStopped) {frame = frameQueue->pop(10);if (frame) {//图像缩放、颜色空间转换sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);av_frame_unref(frame);//视频区域SDL_Rect sdlRect;sdlRect.x = 0;sdlRect.y = 0;sdlRect.w = decCtx->width;sdlRect.h = decCtx->height;//渲染到sdl窗口emit refreshImage(sdlRect, playFrame);av_usleep(39999);}}
}
VideoWidget
我们这里使用到QWidget,直接把SDL封装进去,做一个自定义类,之后使用qt creator 提升一下就行了。
注意:在使用时,如果要把VideoWidget嵌入到某个widget内部,需要在new 之后在单独setParent,不然会把整个父类主窗口当作sdl窗口。
//VideoWidget.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include <QApplication>
#include <QDebug>
#include <QMetaType>
#include <QWidget>
#include <QtGui>class VideoWidget : public QWidget {Q_OBJECT
private:SDL_Rect m_originalSDLRect;AVFrame *frame = nullptr;SDL_Window *sdlWindow = nullptr;SDL_Renderer *sdlRenderer = nullptr;SDL_Texture *sdlTexture = nullptr;protected:void paintEvent(QPaintEvent *event) override;public:VideoWidget(QWidget *parent = 0);~VideoWidget();void initSDL();
public slots:void updateImage(const SDL_Rect &sdl_rect, AVFrame *frame);
};//VideoWidget.cpp
#include "VideoWidget.h"
VideoWidget::VideoWidget(QWidget *parent): QWidget(parent) {// 注册SDL类qRegisterMetaType<SDL_Rect>("SDL_Rect");
}
VideoWidget::~VideoWidget() {if (frame)av_frame_free(&frame);if (sdlTexture)SDL_DestroyTexture(sdlTexture);if (sdlRenderer)SDL_DestroyRenderer(sdlRenderer);if (sdlWindow)SDL_DestroyWindow(sdlWindow);SDL_Quit();
}
void VideoWidget::initSDL() {// SDL initif (SDL_Init(SDL_INIT_VIDEO) != 0) {qDebug() << "SDL_INIT_VIDEO error";return;}// 这里必须要先SDL_CreateWindowFrom,然后在设置父类为this,否则会直接把父类当成参数作为背景sdlWindow = SDL_CreateWindowFrom((void *) winId());//渲染器sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);//纹理 大小是视频大小sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);SDL_SetTextureScaleMode(sdlTexture, SDL_ScaleModeLinear);
}
void VideoWidget::updateImage(const SDL_Rect &sdl_rect, AVFrame *frame) {this->m_originalSDLRect = sdl_rect;this->frame = frame;this->update();
}
void VideoWidget::paintEvent(QPaintEvent *event) {if (!frame) {return;}
#if 0//QPainter渲染QImage image((uchar *) frame->data[0], 1920, 1080, QImage::Format_RGB32);QPainter painter(this);painter.drawImage(this->rect(), image);
#endif#if 1//SDL渲染int ret = 1;ret = SDL_RenderClear(sdlRenderer);if (ret != 0) {qDebug() << "SDL_RenderClear error";}// 创建SDL纹理并从表面创建ret = SDL_UpdateTexture(sdlTexture, &m_originalSDLRect, frame->data[0], frame->linesize[0]);// ret = SDL_UpdateYUVTexture(sdlTexture, &sdlRect2, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);if (ret != 0) {qDebug() << "SDL_UpdateYUVTexture error:" << SDL_GetError();}// 将纹理绘制到渲染器上ret = SDL_RenderCopy(sdlRenderer, sdlTexture, &m_originalSDLRect, nullptr);if (ret != 0) {qDebug() << "SDL_RenderCopy error";}// 刷新渲染器,将内容显示到窗口上SDL_RenderPresent(sdlRenderer);
#endif
}
PlayerMain
因为我们使用到了QT,所以先简单创建一个qt的ui,并且在内调用播放视频的函数,测试是否能够看见画面
//PlayerMain.h
#include <QWidget>
#include <QtCore>
//-----------
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include "thread/DecodeThread.h"
#include "thread/DemuxThread.h"
#include "widget/VideoWidget.h"
#include "output/VideoOutPut.h"QT_BEGIN_NAMESPACE
namespace Ui {class PlayerMain;
}
QT_END_NAMESPACEclass PlayerMain : public QWidget {Q_OBJECTpublic:explicit PlayerMain(QWidget *parent = nullptr);~PlayerMain() override;private:Ui::PlayerMain *ui;// 解复用DemuxThread *demuxThread;DecodeThread *audioDecodeThread;DecodeThread *videoDecodeThread;// 解码-音频AVPacketQueue audioPacketQueue;AVFrameQueue audioFrameQueue;// 解码-视频AVPacketQueue videoPacketQueue;AVFrameQueue videoFrameQueue;VideoOutPut *videoOutPut;
};//PlayerMain.cpp
#include "PlayerMain.h"
#include "ui_PlayerMain.h"PlayerMain::PlayerMain(QWidget *parent): QWidget(parent), ui(new Ui::PlayerMain) {ui->setupUi(this);// 解复用demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);demuxThread->setUrl("/Users/mac/Downloads/0911超前派对:于文文孟佳爆笑猜词 王源欧阳靖脑洞大开.mp4");// demuxThread->setUrl("/Users/mac/Downloads/23.mp4");demuxThread->start();int ret;// 解码-音频audioDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Audio),demuxThread->getCodecParameters(MediaType::Audio),&audioPacketQueue,&audioFrameQueue);audioDecodeThread->init();audioDecodeThread->start();// 解码-视频videoDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Video),demuxThread->getCodecParameters(MediaType::Video),&videoPacketQueue,&videoFrameQueue);videoDecodeThread->init();videoDecodeThread->start();// video outputthis->resize(1920/2,1080/2);videoOutPut = new VideoOutPut(videoDecodeThread->dec_ctx, &videoFrameQueue);videoOutPut->init();VideoWidget *videoWidget = new VideoWidget(this);connect(videoOutPut, &VideoOutPut::refreshImage, videoWidget, &VideoWidget::updateImage);videoWidget->show();videoWidget->initSDL();videoOutPut->start();
// videoWidget->setParent(this);
}PlayerMain::~PlayerMain() {delete ui;
}
测试运行结果
播放器开发(五):视频帧处理并用SDL渲染播放 结果
相关文章:
播放器开发(五):视频帧处理并用SDL渲染播放
目录 学习课题:逐步构建开发播放器【QT5 FFmpeg6 SDL2】 步骤 VideoOutPut模块 1、初始化【分配缓存、读取信息】 2、开始线程工作【从队列读帧->缩放->发送渲染信号到窗口】 VideoWidget自定义Widget类 1、定义内部变量 2、如果使用SDL,需要进…...
Spring MVC数据绑定的几种方法(一)
这篇文章包含spring mvc的默认数据类型绑定和简单数据类型绑定。内容来自实验。 准备: (1)在IDEA环境中从archetye创建webapp类型的maven项目exp6。 (2)在src\main目录下创建并标注java源代码文件夹和resources资源文…...
CSP-坐标变换(其二)
问题描述 对于平面直角坐标系上的坐标 (x,y),小 P 定义了如下两种操作: 拉伸 k 倍:横坐标 x 变为 kx,纵坐标 y 变为 ky; 旋转 θ:将坐标 (x,y) 绕坐标原点 (0,0) 逆时针旋转 θ 弧度(0≤θ<…...
docker 安装jekins
echo Asia/Shanghai >/etc/timezone,容器中操作报错:docker容器中 Permission denied 使用该-u选项时,可以使用root用户(ID 0),而不是用默认用户登录docker容器 docker exec -u 0 -it f8a2b3d91455 /bin/bash 或者ÿ…...
ChatGPT 问世一周年之际,开源大模型能否迎头赶上?
就在11月30日,ChatGPT 迎来了它的问世一周年,这个来自 OpenAI 的强大AI在过去一年里取得了巨大的发展,迅速吸引各个领域的用户群体。 我们首先回忆一下 OpenAI和ChatGPT这一年的大事记(表格由ChatGPT辅助生成)&#x…...
数据结构和算法-哈夫曼树以相关代码实现
文章目录 总览带权路径长度哈夫曼树的定义哈夫曼树的构造法1法2 哈夫曼编码英文字母频次总结实验内容: 哈夫曼树一、上机实验的问题和要求(需求分析):二、程序设计的基本思想,原理和算法描述:三、调试和运行…...
Kafka 的起源和背景
Apache Kafka 是一个分布式流处理平台,被广泛用于构建实时数据流应用程序和大数据处理系统。本文将深入探讨 Kafka 的起源、设计原则以及它在大数据领域中的重要作用。 大数据和实时数据处理背景 在大数据时代,处理海量数据和实时数据成为了一项关键挑…...
三极管在数字电路中的应用
一、认识三极管 三极管拥有3个引脚,分别对应3个级:基极(Base)、发射极(Emitter)、集电极(Collector),如下图所示;下图横向左侧的是基极,带箭头的那个引脚就是发射极,另一个就是集电…...
java后端自学错误总结
java后端自学错误总结 MessageSource国际化接口总结 MessageSource国际化接口 今天第一次使用MessageSource接口,比较意外遇到了一些坑 messageSource是spring中的转换消息接口,提供了国际化信息的能力。MessageSource用于解析 消息,并支持消息的参数化…...
CLion安装与配置教程
目录 一、下载并安装CLion1、下载1、官网:2、注意: 2、安装1、下载完成后,直接点击安装包安装,即可。2、开始安装,然后下一步3、可以在此处自定义地址,然后下一步4、根据系统版本选择,然后下一步…...
初识主力投资者
在股票市场中,真正赚钱的散户并不多。“七亏二平一赚”似乎已经成为了大家公认的一个股市定律。 为什么散户炒股赚的人少呢?原因很简单,就是因为市场上除了散户之外,还存在着一个重要的投资主体——主力。股市交易的过程ÿ…...
vue项目报错及解决npm run build:prod打包错误
vue项目报错及解决npm run build:prod打包错误 执行dev环境时加载失败了该变量,在package.json文件中 删掉 解决方法: 打包成功:...
Go连接mysql数据库
package main import ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" ) //go连接数据库示例 func main() {// 数据库信息dsn : "root:roottcp(192.168.169.11:3306)/sql_test"//连接数据库 数据库类型mysql,以及数据库信息d…...
⭐ Unity 里让 Shader 动画在 Scene 面板被持续刷新
写 Unity Shader的时候,只有播放状态下的 Game 面板能看到Shader 顺畅的动态效果,不方便。 想要带有动态效果的 Shader 在 Scene 面板持续更新动画,只需要打开一个开关就能让 Scene 持续刷新动画了。 感谢大家的观看,您的点赞和关…...
面试--各种场景问题总结
1.在开发过程中,你是如何保证机票系统的正常运行的? 用户、测试、监控和日志、安全措施、数据备份、系统设计、需求分析 2.在机票系统开发过程中,你最有成就的事情,为什么? 用户体验感、高可用和稳定性、客户满意度、系…...
solidity实现ERC721代币标准发布NFT
文章目录 1、非同质化货币(NFT)- 维基百科2、IERC1653、IERC7214、IERC721Receiver5、IERC721Metadata6、ERC7217、ERC721 NFT 的实现8、编译部署 1、非同质化货币(NFT)- 维基百科 非同质化代币(英语:Non-F…...
Failed building wheel for opencv-python which use PEP 517
这主要是opencv-python版本更新以后wheels也更新了,但是相关安装软件没有及时适配,所以不管是使用pip直接安装还是换源其实效果都是报错,解决方法就是直接指定安装旧版opencv-python完事儿,例如: pip3 install opencv…...
HTML5 的全局属性 hidden 和 display:none 的关系
目录 1,hidden 和 display:none 的关系2,其他隐藏元素的方式2.1,语意上的隐藏2.2,视觉上的隐藏 1,hidden 和 display:none 的关系 hidden - MDN 参考 一句话总结:hidden 是HTML5 新增的全局布尔属性&…...
CCKS2023-面向上市公司主营业务的实体链接评测-亚军方案
赛题分析 大赛地址 https://tianchi.aliyun.com/competition/entrance/532097/information 任务描述 本次任务主要针对上市公司的主营业务进行产品实体链接。需要获得主营业务中的产品实体,将该实体链接到产品数据库中的某一个标准产品实体。产品数据库将发布在竞赛…...
关于我离破500粉丝感受
嘿嘿快破500粉丝啦,加油喔,感谢支持 首先,恭喜我在CSDN上的粉丝数量即将突破500大关!这说明你在这个平台上的内容受到了很多人的关注和认可。 1. 保持高质量的内容输出:粉丝数量的增长与你在CSDN上发布的内容质量密切…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
