Qt+FFmpeg+SDL 实现多路多格式原始视频播放工具:从像素处理到实时渲染

一、概述

    本文基于 Qt、FFmpeg AVFrame 和 SDL 库构建的高性能多路多格式原始视频播放工具。系统支持同时播放 RGBA、ARGB、RGB24、YUV420P 等多种像素格式的原始视频流,核心技术包括:
  多格式播放:直接播放的是原始视频文件,例如:RGBA、ARGB、YUV420P、RGB24
  像素格式转换:利用 libswscale 实现 YUV420P 到 RGB 系列格式的高效转换,支持以上4中视频文件格式的互转。
  多路同步渲染:基于 SDL2 的渲染器,为每路视频创建独立纹理,通过 Qt 界面实现统一管理。

  界面如下:

  

   制作步骤:

  • 首先是界面的编写(如上图)。可显示两路视频,原始视频的格式可以是YUV420P、RGBA、ARGB、RGB24中的任意两种
  • 编写多路多格式视频播放控制类进行统一管理。此处使用C++11的thread线程用于渲染,防止占用主线程导致程序卡死

二、代码示例

  1. 界面类:SDLRenderAVFrameWindow【源码】
    • 选择视频1然后开始播放
      void SDLRenderAVFrameWindow::selectVideo1Func()
      {
          QString filter = "RGB 文件 (*.*);;所有文件 (*.*)";
          // 打开文件选择对话框,选择单个文件
          QString filePath = QFileDialog::getOpenFileName(this, "选择 RGB 文件", "", filter);
          if (filePath.isEmpty()) {
              return;
          }
          if (playController1)
          {
              delete playController1;
              playController1 = nullptr;
          }
          playController1 = MultipleVideosPlayController::Create();
          playController1->Close();
          playController1->mWidth = ui.video1Width->text().toInt();
          playController1->mHeight = ui.video1Height->text().toInt();
          playController1->mPixFormat = m_fmt1;
          playController1->win_id = (void*)ui.labelVideo1->winId();
          playController1->fps = ui.editFps1->text().toInt();
          playController1->mFilePath = filePath;
          playController1->Init();
          playController1->callback = [this](int fps) {
              qDebug() << "执行了回调函数1";
              ui.labelFps1->setText(QString::number(fps));
              };
          playController1->Start();
          //playController1->Draw();
      }

       

    • 选择视频2然后开始播放
      void SDLRenderAVFrameWindow::selectVideo2Func()
      {
          QString filter = "RGB 文件 (*.*);;所有文件 (*.*)";
          // 打开文件选择对话框,选择单个文件
          QString filePath = QFileDialog::getOpenFileName(this, "选择 RGB 文件", "", filter);
          if (filePath.isEmpty()) {
              return;
          }
          if (playController2)
          {
              delete playController2;
              playController2 = nullptr;
          }
          playController2 = MultipleVideosPlayController::Create();
          playController2->Close();
          playController2->mWidth = ui.video2Width->text().toInt();
          playController2->mHeight = ui.video2Height->text().toInt();
          playController2->mPixFormat = m_fmt2;
          playController2->win_id = (void*)ui.labelVideo2->winId();
          playController2->mFilePath = filePath;
          playController2->fps = ui.editFps2->text().toInt();
          playController2->Init();
          playController2->callback = [this](int fps) {
              qDebug() << "执行了回调函数2";
              ui.labelFps2->setText(QString::number(fps));
              };
          playController2->Start();
          //playController2->Draw();
      }

       

  2. 控制器类:SDLRenderAVFrameWindow【源码】
    • 初始化参数及SDL2渲染器
      QString MultipleVideosPlayController::Init()
      {
          binaryFileReader = BinaryFileReader::Create();
          iVideoRenderView = IVideoRenderView::Create(IVideoRenderView::SDL);
          if (mWidth <= 0 || mHeight <= 0 || win_id == NULL)
          {
              qDebug() << "请设要播放文件的宽高";
              QString errorMsg = "请设要播放文件的宽高";
              return errorMsg;
          }
      
          binaryFileReader->filename = mFilePath.toStdString();
          binaryFileReader->width = mWidth;
          binaryFileReader->height = mHeight;
          binaryFileReader->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;
      }

       

    • 开启线程,并指定Draw方法运行在子线程中
      void MultipleVideosPlayController::Start()
      {
          // 创建并启动后台线程
          renderThread = std::make_unique<std::thread>(&MultipleVideosPlayController::Draw, this);
      }

       

    • 把Frame绘制到SDL中
      /// <summary>
      /// 线程函数,执行了线程
      /// </summary>
      void MultipleVideosPlayController::Draw()
      {
          for (;;)
          {
              if (!isRunning)break;
              bool ret = binaryFileReader->Read(data, linesize);
              AVFrame* frame = GetFrame(data);
              if (!ret) {
                  break;
              }
              iVideoRenderView->DrawFrame(frame);
              av_frame_free(&frame);//释放掉AVFrame
              if (fps <= 0)
              {
                  fps = 25;
              }
              int secondMs = (int)(1000 / fps);
              //std::this_thread::sleep_for(33ms);
              std::this_thread::sleep_for(std::chrono::milliseconds(secondMs));
              int renderFps = iVideoRenderView->RenderFps();
              callback(renderFps);
          }
          qDebug() << "已释放了data内存";
      }

       

    • SDL2的DrawFrame具体如下
      bool SDLVideoRenderView::DrawFrame(AVFrame* frame)
      {
          if (!frame || !frame->data[0])return false;
          unique_lock<mutex> sdl_lock(m_mutex);
          m_count++;
          if (m_beg_ms <= 0)
          {
              m_beg_ms = clock();
          }
          //计算显示帧率
          else if ((clock() - m_beg_ms) / (CLOCKS_PER_SEC / 1000) >= 1000) //一秒计算一次fps
          {
              m_render_fps = m_count;
              m_count = 0;
              m_beg_ms = clock();
          }
      
          switch (frame->format)
          {
          case AV_PIX_FMT_YUV420P:
              return renderUtil->DrawFrame(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], frame->linesize[1], frame->linesize[2]);
          case AV_PIX_FMT_BGRA:
          case AV_PIX_FMT_ARGB:
          case AV_PIX_FMT_RGBA:
          case AV_PIX_FMT_RGB24:
              return renderUtil->DrawFrame(frame->data[0], frame->linesize[0]);
          }
          return true;
      }

       

 

posted on 2025-05-19 17:51  飘杨......  阅读(92)  评论(0)    收藏  举报