1 背景
在基于OBS做二次开发时,有时候需要获取OBS的缺省主窗口YUV数据,输出到自定义窗口上。本文使用Qt获取Video数据,然后在自定义窗口上进行绘制。
2 范例
1)获取主窗口的yuv数据
void CameraService::Start()
{
if (m_bExit) {
m_bExit = false;
width_ = App()->GetMainWindowWidth();
height_ = App()->GetMainWindowHeigh();
obs_add_raw_video_callback(NULL, &CameraService::RawVideo,
this);
}
}
void CameraService::Stop()
{
if (!m_bExit) {
m_bExit = true;
obs_remove_raw_video_callback(&CameraService::RawVideo, this);
}
}
void CameraService::RawVideo(void *param, video_data *frame)
{
CameraService *window = reinterpret_cast<CameraService *>(param);
if (window) {
window->Yuv2Rgb(frame);
}
}
void CameraService::Yuv2Rgb(video_data *frame)
{
// 1.将obs底层callback的yuv数据转换成rgb像素数据
uint8_t *pData =
(uint8_t *)malloc(sizeof(uint8_t) * width_ * height_ * 4);
libyuv::NV12ToARGB(frame->data[0], width_, frame->data[1], width_,
pData, width_ * 4, width_, height_);
// 2.保存在Qimage类中
QImage *image = new QImage(pData, width_, height_, QImage::Format_RGB32,
OnRgbCleanupHandler, pData);
// 3.发信号给自定义video显示窗口
emit FrameReady(image);
}
2)信号插槽函数
connect(&camera_service_, SIGNAL(FrameReady(QImage*)), this, SLOT(OnRefreshVideo(QImage*)));
3)输出到自定义窗口上
void MiddleWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter paint(this);
paint.drawImage(0, 0, m_image);
}
void MiddleWidget::OnRefreshVideo(QImage *rgb)
{
#ifdef _DEBUG
qInfo("%s", __FUNCTION__);
#endif
if (rgb && !rgb->isNull()) {
#if _DEBUG
qInfo("%s scaled", __FUNCTION__);
#endif
m_image = rgb->scaled(size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
// 触发paintEvent()回调,在自定义窗口上进行绘制
update();
}
// 此处记得delete掉QImage资源,不然有内存泄漏问题
delete rgb;
}
3 样本助解
obs主窗口显示
自定义窗口显示
由此可见,用此法,可见obs主窗口画面copy到自定义窗口显示。
4 性能问题
4.1 cpu开销
本文画面参数:3840x2160分辨率,30fps,YUV420 8bit位深,AVC/H264编码格式
Win机器配置:6核心12线程
1)单主窗口显示cpu消耗
2)增加1个自定义窗口cpu消耗
可以清楚的看到,使用此法将obs主窗口视频数据copy到自定义窗口显示,会显著提升cpu开销。
4.2 原因分析
- 分析了obs源码,原因在于obs_add_raw_video_callback回调后,有一个像素格式转换,就是调用了libyuv::NV12ToARGB(),此函数使用CPU将yuv转rgb,由于每帧都要转换,因此cpu开销显著提升;
5 解决方案
- 放弃使用libyuv库或sws_scale接口,利用OpenGL使用GPU加速来处理像素格式的转换然后绘制;