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线程用于渲染,防止占用主线程导致程序卡死
二、代码示例
- 界面类: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(); }
- 选择视频1然后开始播放
- 控制器类: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; }
- 初始化参数及SDL2渲染器
浙公网安备 33010602011771号