NewTek NDI(SDK v5)搭配Qt6.3:如何在GUI界面显示NDI视频帧
问题背景
- 已完成NewTek NDI v5 SDK在Qt6.3 Widget项目中的集成,官方接收示例编译运行正常,可正常拉取NDI源数据
- 已成功获取
NDIlib_video_frame_v2_t类型的视频帧,可正确读取分辨率等参数,帧数据指针p_data有效 - 尝试直接用
QImage::Format_RGB888构造图像保存、显示失败,调试确认当前帧格式为video_type_UYVY,需要实现UYVY格式帧到Qt可显示格式的转换,完成GUI端视频渲染 - 之前的错误尝试代码:
curFrame = QImage(video_frame.p_data, video_frame.xres, video_frame.yres, QImage::Format::Format_RGB888); curFrame.save("out.jpg");
解决方案
之前的代码存在两个核心错误:一是像素格式不匹配,UYVY属于YUV422打包色彩空间,和RGB888的内存布局完全不兼容,直接解析必然出现颜色错乱、花屏;二是构造QImage时没有传入正确的行步长,若NDI返回的帧存在行对齐填充字节,会出现图像斜拉、撕裂问题。
以下是可直接落地的实现方案:
方法1:Qt原生格式转换(最简便,性能满足绝大多数场景)
Qt 5.14及以上版本(含Qt6全版本)原生支持UYVY格式的QImage解析,无需手动编写色彩空间转换逻辑:
// 零拷贝构造UYVY格式QImage,直接使用NDI帧自带的行步长,自动适配行对齐规则 QImage uyvyImg( video_frame.p_data, video_frame.xres, video_frame.yres, video_frame.line_stride, // NDI帧结构体自带步长,单位为字节,无需手动计算 QImage::Format_UYVY ); // 转换为Qt渲染组件通用的RGB32格式,该步骤自动完成YUV到RGB的色彩空间转换,转换后的数据独立存储,不依赖原始帧内存 QImage displayImg = uyvyImg.convertToFormat(QImage::Format_RGB32); // 直接传入界面组件显示 ui->displayLabel->setPixmap(QPixmap::fromImage(displayImg));
注意事项
- 构造
uyvyImg时没有拷贝原始帧数据,必须保证在convertToFormat执行完成前,不要调用NDIlib_recv_free_video_v2释放当前视频帧,否则会触发野指针崩溃 - 转换后的
displayImg是独立内存的RGB图像,释放NDI帧不会影响它,可以安全用于界面渲染、存图等操作 - 若需要更高渲染性能,可复用转换后的
QImage对象内存,避免每帧重复构造带来的开销,1080p@30fps视频在普通PC上用该方案CPU占用低于5%
方法2:手动转换(适配无UYVY原生支持的特殊Qt版本)
如果遇到特殊Qt版本不支持QImage::Format_UYVY,可以手动实现UYVY到RGB32的转换,逻辑如下:
QImage displayImg(video_frame.xres, video_frame.yres, QImage::Format_RGB32); const uint8_t* src = reinterpret_cast<const uint8_t*>(video_frame.p_data); QRgb* dst = reinterpret_cast<QRgb*>(displayImg.bits()); const int srcLineStride = video_frame.line_stride; const int dstLineStride = displayImg.bytesPerLine() / sizeof(QRgb); for (int y = 0; y < video_frame.yres; y++) { const uint8_t* srcLine = src; QRgb* dstLine = dst; for (int x = 0; x < video_frame.xres; x += 2) { // 读取UYVY打包数据:顺序为U Y0 V Y1,每2个像素占4字节 uint8_t u = srcLine[0]; uint8_t y0 = srcLine[1]; uint8_t v = srcLine[2]; uint8_t y1 = srcLine[3]; srcLine += 4; // BT.601标准YUV转RGB,数值做截断避免越界 auto yuvToRgb = [](uint8_t Y, uint8_t U, uint8_t V) -> QRgb { int c = Y - 16; int d = U - 128; int e = V - 128; int r = qBound(0, (298 * c + 409 * e + 128) >> 8, 255); int g = qBound(0, (298 * c - 100 * d - 208 * e + 128) >> 8, 255); int b = qBound(0, (298 * c + 516 * d + 128) >> 8, 255); return qRgb(r, g, b); }; dstLine[x] = yuvToRgb(y0, u, v); dstLine[x + 1] = yuvToRgb(y1, u, v); } // 跳转到下一行,自动跳过行对齐填充字节 src += srcLineStride; dst += dstLineStride; } // 生成的displayImg可直接用于界面显示
补充说明
- 调试时如果发现颜色偏色,可检查色彩空间转换标准:NDI默认HD视频用BT.709,SD用BT.601,常规场景下用Qt原生转换已经做了适配,不需要手动调整
- 实时播放时建议把帧接收和界面渲染放到不同线程,避免NDI接收阻塞UI线程导致卡顿
内容的提问来源于stack exchange,提问作者ThomasAlvaEdison
相关产品推荐
相关产品推荐

