Qt+SDL 实战:多格式视频播放工具开发 —— 支持 RGBA/ARGB/RGB24/YUV420P 的完整实现
一、概述
本文将使用 Qt 和 SDL 开发一个支持多格式视频播放的工具,覆盖 RGBA、ARGB、RGB24、YUV420P 等常见像素格式。工具将实现窗口关闭事件响应、动态尺寸适配、播放参数(fps / 分辨率)自定义等核心功能。通过 Qt 处理界面交互与窗口生命周期管理(如closeEvent
和resizeEvent
),结合 SDL 完成视频解码与渲染,解决多格式数据到屏幕的高效显示问题。
架构图:
- VideoRenderViewWindow为Qt显示界面,用于展示用户操作以及显示具体的视频播放。其依赖于VideoRenderViewController
- VideoRenderViewController为控制器,控制播放器工具的大部分逻辑,其引用BinaryFileReader以及SDLVideoRenderView。其会开启一个单独的线程来循环播放是视频
- BinaryFileReader二进制文件读取工具。
- SDLRenderUtil这是SDL的渲染工具,提供Init、Close、Render方法
- IVideoRenderView渲染方式接口,由其提供渲染创建方法,其子类来实现具体的功能。此项目中其子类的视线为SDLVideoRenderView,代表使用SDL渲染,后期如果有空还会加上Qt渲染或者OpenGL渲染。
- SDLVideoRenderView SDL渲染器,实现了IVideoRenderView接口。
二、代码示例
- VideoRenderViewWindow主要代码。【源码】
- 设置关闭时自动销毁窗口
setAttribute(Qt::WA_DeleteOnClose, true); // 关闭时自动销毁窗口
- 监听窗口变化并改变视频宽高
void VideoRenderViewWindow::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); mWidth = event->size().width(); mHeight = event->size().height(); ui.labelVideo->resize(mWidth, mWidth*0.75); ui.labelVideo->move(0, 0); }
- 监听点击了关闭事件
void VideoRenderViewWindow::closeEvent(QCloseEvent* event) { event->accept(); // 接受关闭事件(允许关闭) VideoRenderViewController::Get()->Close(); qDebug() << "窗口已关闭"; }
- 设置文件像素格式
void VideoRenderViewWindow::onSelectPixFormatIndex(int pos) { switch (pos) { case 0: m_fmt = IVideoRenderView::ARGB; break; case 1: m_fmt = IVideoRenderView::RGBA; break; case 2: m_fmt = IVideoRenderView::RGB24; break; case 3: m_fmt = IVideoRenderView::YUV420P; break; } }
- 打开文件、设置参数并开始播放
void VideoRenderViewWindow::openFileAndStart() { QString filter = "RGB 文件 (*.*);;所有文件 (*.*)"; // 打开文件选择对话框,选择单个文件 QString filePath = QFileDialog::getOpenFileName(this, "选择 RGB 文件", "", filter); if (filePath.isEmpty()) { return; } mWidth = ui.lineEditWidth->text().toInt(); mHeight = ui.lineEditHeight->text().toInt(); this->resize(QSize(mWidth, mHeight)); ui.labelVideo->resize(mWidth, mHeight); VideoRenderViewController::Get()->Close(); VideoRenderViewController::Get()->mWidth = mWidth; VideoRenderViewController::Get()->mHeight = mHeight; VideoRenderViewController::Get()->fps = ui.lineEditFps->text().toInt(); VideoRenderViewController::Get()->mPixFormat = m_fmt; VideoRenderViewController::Get()->win_id = (void*)ui.labelVideo->winId(); VideoRenderViewController::Get()->mFilePath = filePath; VideoRenderViewController::Get()->Init(); VideoRenderViewController::Get()->Start(); VideoRenderViewController::Get()->Draw(); }
- 设置关闭时自动销毁窗口
- VideoRenderViewController主要代码【源码】
- 初始化
QString VideoRenderViewController::Init() { iVideoRenderView = IVideoRenderView::Create(IVideoRenderView::SDL); if (mWidth <= 0 || mHeight <= 0 || win_id == NULL) { qDebug() << "请设要播放文件的宽高"; QString errorMsg = "请设要播放文件的宽高"; return errorMsg; } BinaryFileReader::Get()->filename = mFilePath.toStdString(); BinaryFileReader::Get()->width = mWidth; BinaryFileReader::Get()->height = mHeight; BinaryFileReader::Get()->Init(); IVideoRenderView::PixFormat mFormat = IVideoRenderView::PixFormat::RGBA; switch (mPixFormat) { case 0: mFormat = IVideoRenderView::PixFormat::RGBA; data = new char[mWidth * mHeight * 4]; linesize = mWidth * mHeight * 4; break; case 1: mFormat = IVideoRenderView::PixFormat::ARGB; data = new char[mWidth * mHeight * 4]; linesize = mWidth * mHeight * 4; break; case 2: mFormat = IVideoRenderView::PixFormat::RGB24; data = new char[mWidth * mHeight * 3]; linesize = mWidth * mHeight * 3; break; case 3: mFormat = IVideoRenderView::PixFormat::YUV420P; data = new char[mWidth * mHeight * 1.5]; linesize = mWidth * mHeight * 1.5; break; } iVideoRenderView->Init(mWidth, mHeight, mFormat, win_id); return NULL; }
- 绘制
void VideoRenderViewController::Draw() { for (;;) { if (!isRunning)break; bool ret = BinaryFileReader::Get()->Read(data, linesize); if (!ret) { break; } iVideoRenderView->Draw(data); int secondMs = (int)(1000 / fps); //std::this_thread::sleep_for(33ms); std::this_thread::sleep_for(std::chrono::milliseconds(secondMs)); } /*if (data) { delete data; }*/ qDebug() << "已释放了data内存"; }
- 初始化
- BinaryFileReader【源码】
- SDLRenderUtil(上一节重点介绍过,本节只是做了一点改造)【源码】
- IVideoRenderView主要代码【源码】
- 创建渲染器
IVideoRenderView* IVideoRenderView::Create(RenderType type) { switch (type) { case IVideoRenderView::SDL: return SDLVideoRenderView::Get(); break; default: break; } return nullptr; }
- 创建渲染器
- SDLVideoRenderView 主要代码【源码】
- 初始化SDL渲染器
bool SDLVideoRenderView::Init(int w, int h, IVideoRenderView::PixFormat fmt, void* win_id) { if (w <= 0 || h <= 0)return false; //确保线程安全 unique_lock<mutex> sdl_lock(m_mutex); //初始化SDL 视频库 SDLRenderUtil::Get()->width = w; SDLRenderUtil::Get()->height = h; SDLRenderUtil::Get()->winId = win_id; SDLRenderUtil::Get()->PIX_FMT = fmt;//渲染模式,这个地方等下需要改 return SDLRenderUtil::Get()->Init(); }
- 使用SDL进行渲染
bool SDLVideoRenderView::Draw(char* data) { if (!data)return false; SDLRenderUtil::Get()->isEventQuit(); unique_lock<mutex> sdl_lock(m_mutex); if (m_scale_width > 0 && m_scale_height > 0) { SDLRenderUtil::Get()->width = m_scale_width; SDLRenderUtil::Get()->height = m_scale_height; } return SDLRenderUtil::Get()->Render(data); }
- 初始化SDL渲染器