C++开发 | Qt项目踩坑记录(二)技术处理篇

1,040 阅读3分钟

前言

本文承接上文,依然是Qt开发项目的解决思路与方案。但侧重点略有不同,将围绕某些技术实现的思路展开。

展示连续帧的图像

在Qt的示例代码中,有很多和多媒体处理相关的例子,但我没法开箱即用。主要原因有:

  • 严格说来,我要做的并不是一个视频播放器,而是对经过处理的数据流的实时展示框

  • 涉及到的视频编解码技术不是Qt中提供的,而是后端代码中自研的

  • 解决思路:将后端代码提供的每一帧图像转化为符合Qt图像展示的格式(QImage),通过信号与槽机制发送至ui,ui将其展示在容器中(QLabel)。

  • 具体实现:

cv::Mat rgbImg(m_windowHight, m_windowWidth, CV_8UC3);
...
cv::Mat yuvImg(m_windowHight * 3 / 2, m_windowWidth, CV_8UC1, m_showPtr.get());
cv::cvtColor(yuvImg, rgbImg, cv::COLOR_YUV2BGR_NV12);

其中,rgbImg是单帧图片的Mat格式。

将其转为QImage格式: QImage img = Mat2QImage(rgbImg); 并封装成信号发出: emit sendImage(img);

其中,Mat2QImage()的代码参考了网上的实现:

QImage WindowManager::Mat2QImage(cv::Mat &image)
{
    QImage img;

    if (image.channels() == 3)
    {
        cvtColor(image, image, CV_BGR2RGB);
        img = QImage((const unsigned char *)(image.data), image.cols, image.rows,
                     image.cols * image.channels(), QImage::Format_RGB888);
    }
    else if (image.channels() == 1)
    {
        img = QImage((const unsigned char *)(image.data), image.cols, image.rows,
                     image.cols * image.channels(), QImage::Format_ARGB32);
    }
    else
    {
        img = QImage((const unsigned char *)(image.data), image.cols, image.rows,
                     image.cols * image.channels(), QImage::Format_RGB888);
    }

    return img;
}

解决再次运行模型时播放画面“鬼畜”的现象

  • 现象及原因:

由于模型加载与运行需要时间,当再次点击按钮后,会有3~5秒不等的空窗时间,这时,缓冲区数据还未刷新,视频展示线程不断从缓冲区读取旧数据,所以会出现前一个视频的最后图像作出“鬼畜”状的现象。

  • 解决方案:

每次启动视频处理线程时,先清空缓冲区:

void WindowManager::resetBuffer(uint8_t *p)
{
    for (int i = 0; i < VIDEO_SIZE; ++i)
    {
        *(p + i) = 0;
    }
}

也可以直接用memset()进行赋值。

解决空窗期图像闪现绿幕的问题

在上一个问题中,我们通过清空缓冲区解决了“鬼畜”画面的问题,但取而代之的是闪现绿幕,因为之前并没有从根本上解决问题,时间差依然存在。

  • 新的解决思路:由于后端的视频处理线程和视频展示线程(没错,后端也有一个展示线程,再由它发送信号让ui响应)是独立的,因此需要通过某种方式让这两个线程同步,并且是在视频的第一帧处理就绪后、视频展示的核心代码才开始运行。可选方案为:使用标志位

编码思路如下:

  • 设置一个成员变量m_start=false
  • 在视频展示线程的开始置m_start=false,这样每次重新播放时这个值都会恢复为默认值,且值未更改前处于睡眠状态
    while (!m_start)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
  • 在视频处理线程的合适位置置m_start=true

关于资源释放

资源释放要注意先后顺序,否则可能会引起流回调函数阻塞从而阻塞整个应用程序。

顺序如下:stream——>channel——>context