前言
本文承接上文,依然是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