Qt+SDL 实战:多格式视频播放工具开发 —— 支持 RGBA/ARGB/RGB24/YUV420P 的完整实现

一、概述

  本文将使用 Qt 和 SDL 开发一个支持多格式视频播放的工具,覆盖 RGBA、ARGB、RGB24、YUV420P 等常见像素格式。工具将实现窗口关闭事件响应、动态尺寸适配、播放参数(fps / 分辨率)自定义等核心功能。通过 Qt 处理界面交互与窗口生命周期管理(如closeEventresizeEvent),结合 SDL 完成视频解码与渲染,解决多格式数据到屏幕的高效显示问题。

  架构图:

  1. VideoRenderViewWindow为Qt显示界面,用于展示用户操作以及显示具体的视频播放。其依赖于VideoRenderViewController
  2. VideoRenderViewController为控制器,控制播放器工具的大部分逻辑,其引用BinaryFileReader以及SDLVideoRenderView。其会开启一个单独的线程来循环播放是视频
  3. BinaryFileReader二进制文件读取工具。
  4. SDLRenderUtil这是SDL的渲染工具,提供Init、Close、Render方法
  5. IVideoRenderView渲染方式接口,由其提供渲染创建方法,其子类来实现具体的功能。此项目中其子类的视线为SDLVideoRenderView,代表使用SDL渲染,后期如果有空还会加上Qt渲染或者OpenGL渲染。
  6. SDLVideoRenderView SDL渲染器,实现了IVideoRenderView接口。

  

 

 

 

二、代码示例

  1. 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();
      }

       

  2. 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内存";
      }

       

  3. BinaryFileReader【源码】
  4. SDLRenderUtil(上一节重点介绍过,本节只是做了一点改造)【源码】
  5. IVideoRenderView主要代码【源码】
    • 创建渲染器
      IVideoRenderView* IVideoRenderView::Create(RenderType type)
      {
          switch (type)
          {
          case IVideoRenderView::SDL:
              return SDLVideoRenderView::Get();
              break;
          default:
              break;
          }
          return nullptr;
      }

       

  6. 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);
      }

       

posted on 2025-05-14 14:18  飘杨......  阅读(54)  评论(0)    收藏  举报