播放器开发(五):视频帧处理并用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上发布的内容质量密切…...

锁表的原因及解决办法
引言 作为开发人员,我们经常会和数据库打交道。 当我们对数据库进行修改操作的时候,例如添加字段,更新记录等,没有正确评估该表在这一时刻的使用频率,直接进行修改,致使修改操作长时间无法响应࿰…...

Kettle 安装配置
文章目录 Kettle 安装配置Kettle 安装Kettle 配置连接 Hive Kettle 安装配置 Kettle 安装 在安装Kettle之前,需要确定已经安装Java运行环境。Kettle需要Java的支持才能运行,JDK的版本最好是8.x的太新的也会出现bug。Kettle的7.1版本的太旧了࿰…...

Webgis学习总结
前言: 作者跟随视频学习了webgis内容进行如下学习复习总结 参考:新中地学习笔记 WebGIS第一课:测试高德API并通过: 注册申请高德API成为开发者,创建自己的项目和key进行项目初始化,可以使用JS API官方文…...

【开源】基于Vue+SpringBoot的音乐平台
项目编号: S 055 ,文末获取源码。 \color{red}{项目编号:S055,文末获取源码。} 项目编号:S055,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首…...

20、Resnet 为什么这么重要
(本文已加入“计算机视觉入门与调优”专栏,点击专栏查看更多文章信息)r esnet 这一网络的重要性,上一节大概介绍了一下,可以从以下两个方面来有所体现:第一是 resnet 广泛的作为其他神经网络的 back bone;第二是 resnet 是 AI 芯片厂家对标性能时,在视觉领域尤其是图像…...

Git Bash环境下用perl脚本获取uuid值
在Linux环境下,比如在ubuntu就直接有uuidgen命令直接获取uuid值。在Windows环境下常用的git bash中没有对应的命令,略有不便。这里用脚本写一个uuidgen,模拟Linux环境下的uuidgen命令。 #! /usr/bin/perl use v5.14; use Win32;sub uuidGen {…...

linux安装部署redis
1、下载redis包2、解压3、进入解压路径编译安装4、修改配置文件使redis后台运行5、启动 1、下载redis包 https://redis.io/download/ 2、解压 tar -zxvf redis-7.2.3.tar.gz3、进入解压路径编译安装 cd redis-7.2.3 make && make install默认安装路径: …...

Redis 数据结构详解
分类 编程技术 Redis 数据类型分为:字符串类型、散列类型、列表类型、集合类型、有序集合类型。 Redis 这么火,它运行有多块?一台普通的笔记本电脑,可以在1秒钟内完成十万次的读写操作。 原子操作:最小的操作单位&a…...

03-IDEA集成Git,初始化本地库,添加远程仓库,提交,拉取,推送,分支的快捷操作
IDEA集成Git 创建Git忽略文件 不同的IDE开发工具有不同的特点文件,这些文件与项目的实际功能无关且不参与服务器上的部署运行, 把它们忽略掉能够屏蔽之间的差异 局部忽略配置文件: 在本地仓库的根目录即项目根目录下直接创建.gitignore文件, 以文件后缀或目录名的方式忽略指定…...

Python---格式化输出与%百分号----涉及转义符 \ 反斜杠的使用
相关链接Python--格式化输出中的转义符号----\t 制表符(空格的)和\n(换行的)_唯元素的博客-CSDN博客 Python---字符串(用单、双引号、 三单/双引号定义。反斜杠 \ 转义,单在双内/双在单内 )-CS…...