播放器开发(五):视频帧处理并用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渲染播放 结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/181403.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Matplotlib直方图的创建_Python数据分析与可视化

Matplotlib直方图的创建 概念区分绘制直方图 概念区分 什么是直方图&#xff1f; 直方图&#xff08;Histogram&#xff09;又称质量分布图&#xff0c;是统计报告图的一种&#xff0c;由一系列高度不等的纵向条纹或线段表示数据分布的情况&#xff0c;一般用横轴表示数据所属…

探索性数据分析(EDA):从数据中发现洞察力

写在开头 在数据科学的世界中,探索性数据分析(Exploratory Data Analysis,EDA)是一项至关重要的任务,它能够帮助我们深入了解数据、发现潜在的模式,并为进一步的分析和建模提供基础。本篇博客将介绍探索性数据分析的基本技术和方法,通过具体的数字、场景和代码,帮助读…

解锁文件安全新境界!迅软DSE带您领略数据加密的魅力!

随着信息技术的不断发展&#xff0c;企业数据信息的安全与保护受到愈发广泛的关注。而文件加密软件得益于其强大的系统功能能够有效地保护企业重要数据的隐私和安全&#xff0c;成为越来越多企事业单位在进行内部数据安全防护工作时的优选。 一、文件加密软件的作用 文件加密软…

国产数据库

当今世界&#xff0c;数据已成为重要的生产要素&#xff0c;数据库管理系统更是广泛应用于信息化行业各领域&#xff0c;国内数据库产业能否健康可持续的发展&#xff0c;在很大程度上影响着国民经济发展和网络空间安全。 当前&#xff0c;国产数据库行业竞争非常激烈&#xf…

c++基础----new

c基础----new 在C中&#xff0c;new是一个运算符&#xff0c;用于动态分配内存并返回指向该内存的指针。它可以用于创建单个对象、数组以及动态分配的对象。 下面是new的几种常见用法&#xff1a; 动态分配单个对象&#xff1a; int* ptr new int; // 动态分配一个int类型…

Wi-Fi标准

Wi-Fi 标准 记录关于Wi-Fi 标准 Wi-Fi 6&#xff08;802.11ax&#xff09;&#xff1a; 这是目前最新的无线网络标准&#xff0c;提供了更高的速度、更好的性能和更高的容量。Wi-Fi 6支持更多的设备连接、更快的速度和更低的延迟&#xff0c;因此是为未来的高密度无线网络设…

HCIE 01:基于前缀列表的BGP ORF功能

当运行BGP协议的某台设备上&#xff0c;针对入方向配置了基于ip-prefix的路由过滤&#xff0c;过滤了邻居发送的路由&#xff1b; 目前想&#xff0c;通过在peer关系的两端设备上都配置ORF功能&#xff0c;实现路由发送端只能送路由接收端过滤后的路由&#xff1b; ORF功能的说…

总结vue3 的一些知识点:MySQL 排序

MySQL 排序 我们知道从 MySQL 表中使用 SQL SELECT 语句来读取数据。 如果我们需要对读取的数据进行排序&#xff0c;我们就可以使用 MySQL 的 ORDER BY 子句来设定你想按哪个字段哪种方式来进行排序&#xff0c;再返回搜索结果。 语法 以下是 SQL SELECT 语句使用 ORDER B…

Sedex 验厂有证书的吗

【Sedex 验厂有证书的吗】 SEDEX是一家总部设在英国伦敦的非赢利组织&#xff0c;世界上任何地点的公司都可以申请会员资格。SEDEX已获得了许多大型零售商和生产商的青睐&#xff0c;许多零售商、超市、品牌商、供应商和其它组织都要求与之合作的农场、工厂和制造商参加SEDEX成…

Leetcode—1670.设计前中后队列【中等】

2023每日刷题&#xff08;四十三&#xff09; Leetcode—1670.设计前中后队列 实现代码 erase(iterator position)在删除vector中的元素后&#xff0c;会将该元素的后面所有元素都往前挪一位。因此&#xff0c;原先的迭代器指向的元素就不是原来那个了&#xff0c;而是它的后…

2023年05月 Scratch图形化(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共10题,每题3分,共30分) 第1题 下列积木运行后的结果是?( )(说明:逗号后面无空格) A:我 B:爱 C:中 D:国 答案:B 两个字符串连接后的第8个字符是“爱”。 第2题 接鸡蛋游戏中,天空掉下来有鸡蛋、石…

Flink Flink中的合流

一、Flink中的基本合流操作 在实际应用中&#xff0c;我们经常会遇到来源不同的多条流&#xff0c;需要将它们的数据进行联合处理。所以 Flink 中合流的操作会更加普遍&#xff0c;对应的 API 也更加丰富。 二、联合&#xff08;Union&#xff09; 最简单的合流操作&#xf…

开源免费跨平台数据同步工具-Syncthing

Syncthing是一款开源免费跨平台的文件同步工具&#xff0c;是基于P2P技术实现设备间的文件同步&#xff0c;所以它的同步是去中心化的&#xff0c;即你并不需要一个服务器&#xff0c;故不需要担心这个中心的服务器给你带来的种种限制&#xff0c;而且类似于torrent协议&#x…

卡码网语言基础课 | 16. 出现频率最高的字母

目录 一、 哈希表 二、 编写解题 2.1 统计出现次数 2.2 解答 通过本次练习&#xff0c;将学习到C中哈希表的基础知识 题目&#xff1a; 给定一个只包含小写字母的字符串&#xff0c;统计字符串中每个字母出现的频率&#xff0c;并找出出现频率最高的字母&#xff0c;如果…

代洋集团:借力太阳能监测系统,推动绿色能源的未来

在当今这个能源短缺、环境问题日益严峻的时代&#xff0c;寻找清洁、可再生的能源成为了全球的共同使命。代洋集团作为国内领先的企业&#xff0c;积极响应国家号召&#xff0c;引入太阳能监测系统&#xff0c;大力推广绿色能源&#xff0c;为建设可持续发展的未来世界贡献力量…

React 基础使用

react 是一个开源的 JavaScript 库&#xff0c;用于将数据渲染为 HTML 界面&#xff08;只关注视图&#xff09;。 react 使用了虚拟 DOM 和 Diff 算法。当数据更新后&#xff0c;Diff 算法会将新生成的虚拟 DOM 和之前的虚拟 DOM 进行对比&#xff0c;只将不同的地方更新到页…

比尔盖茨:GPT-5不会比GPT-4好多少,生成式AI已达到极限

比尔盖茨一句爆料&#xff0c;成为机器学习社区热议焦点&#xff1a; “GPT-5不会比GPT-4好多少。” 虽然他已不再正式参与微软的日常运营&#xff0c;但仍在担任顾问&#xff0c;并且熟悉OpenAI领导团队的想法。 消息来自德国《商报》&#xff08;Handelsblatt&#xff09;对…

搞定这三个问题 伦敦金止损就没问题

笔者多次强调&#xff0c;做伦敦金交易&#xff0c;重要的是风险控制。而止损是我们风险控制中一个很重要的概念。设定好止损&#xff0c;就是风险控制的好开始。下面我们通过三个问题&#xff0c;来解决止损的问题。 问题一&#xff0c;你的止损位在哪里&#xff1f;要做止损&…

算法之选择排序

算法之选择排序 简单选择排序 选择排序 每一趟两两比较大小&#xff0c;找出极值(极大值或极小值)并放置到有序区的位置。 核心算法 结果可为升序或降序排列&#xff0c;默认升序排列。以降序为例扩大有序区&#xff0c;减小无序区。图中红色部分就是增大的有序区&#xf…

数据结构与算法之美学习笔记:27 | 递归树:如何借助树来求解递归算法的时间复杂度?

目录 前言递归树与时间复杂度分析实战一&#xff1a;分析快速排序的时间复杂度实战二&#xff1a;分析斐波那契数列的时间复杂度实战三&#xff1a;分析全排列的时间复杂度内容小结 前言 本节课程思维导图&#xff1a; 今天&#xff0c;我们来讲这种数据结构的一种特殊应用&am…