2024-07-13 Qt6.5版本后视频渲染


前言

Qt 6版本中,视频播放能力得到了质的飞越,相对于qt5,变化也很多,具体变化可以看官方说明 https://www.qt.io/blog/qt-multimedia-in-qt-6

在Qt5版本中,如果需要播放一个视频,由于后端的解码能力很弱,绝大多数需要写解码的代码,但是在Qt6中,后端加入了ffmpeg,解码已经不是我们所需解决的问题,Qt Multimedia 可以很好的去播放视频
但是,在项目中大家可能不止会播放本地视频,很多情况下需要播放在线视频,比较低延时的rtsp流,udp裸流等等。对于需要实时性高的同学来说,qt的后端能力还是没有做好。这时还是需要自己处理视频流。

在Qt5时代,要播放一个低延时视频流,通常是使用ffmpeg自己解封装,解码视频流后,再将yuv/nv12等视频数据交给qt去渲染,问题是qt渲染能力低,很早大家使用sdl等工具去渲染视频,但由于sdl和qt各自使用自己的渲染事件,常会导致闪烁等问题,后来开始使用opengl,手写shader自己处理yuv数据,这个方案确实可行,效率也很高,但是得学习opengl相关知识。

但到了Qt6,新的QVideoSink,完全具备各视频的渲染,yuv,nv12等常见格式一点都不在话下,而且支持多种渲染方式,比如opengl和d3d11,d3d9等等,它主要是使用了Qt6的QRHI技术,有兴趣的同学可以去了解了解。

我这里主要讲讲如何使用ffmpeg解码+QVideoSink进行视频渲染播放


一、先上代码

1.将yuv/nv12等解码后的视频数据放入QVideoSink内

class VideoSourceRenderer : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVideoSink* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged)

public:
    VideoSourceRenderer()
    {
    }
    QVideoSink* videoSink() const
    {
	return m_videoSink.get();
     }
    void  setVideoSink(QVideoSink* videoSink)
    {
	if (m_videoSink == videoSink)
		return;
	m_videoSink = videoSink;
	emit videoSinkChanged();
    }

signals:
    void videoSinkChanged();
private:
    QPointer<QVideoSink> m_videoSink;
    QVideoFrameFormat    m_videoFrameFormat;
public:
    bool init(int width, int height,QVideoFrameFormat::PixelFormat fmt)
    {
		QSize _size(width, height);
		m_videoFrameFormat = QVideoFrameFormat(_size, fmt);
		m_videoSink->setVideoFrame(m_videoFrameFormat);
		return true;
     }
     // 这里怎么操作都可以,只要将解码后的yuv或者nv12等格式的数据放入QVideoFrame内,再设置给videoSink即可
    bool draw(const unsigned char* y, int y_pitch, const unsigned char* u, int u_pitch,
              const unsigned char* v, int v_pitch) 
              {
					bool        re = false;
					QVideoFrame frame(QVideoFrameFormat(QSize(m_width, m_height), 
								m_videoFrameFormat.pixelFormat()));
					if (!frame.isValid() || !frame.map(QVideoFrame::WriteOnly)) {
						return false;
					}
					if (y && y_pitch > 0)
						memcpy(frame.bits(0), y, y_pitch * m_height);
					if (u && u_pitch > 0)
						memcpy(frame.bits(1), u, u_pitch * m_height / 2);
					if (v && v_pitch > 0)
						memcpy(frame.bits(2), v, v_pitch * m_height / 2);
					frame.setStartTime(0);
					frame.unmap();
					m_videoSink->setVideoFrame(frame);
					return re;
				}
};

2.将AVFrame数据保存进QVideoSink

我这里是从项目中摘出来的代码,如果有同学在使用中,可不必这样,原则上只是拷贝AVFrame的数据

class TiVideoEngineManager : public QObject{
    Q_OBJECT
public:
	Q_PROPERTY(VideoSourceRenderer* videoRenderer READ videoRenderer CONSTANT)
	TiVideoEngineManager()
	{
		m_videoRenderer = new VideoSourceRenderer();
		startTimer(1);
	}
	
protected:
    void timerEvent(QTimerEvent* event) override
    {
		//Get AVFrame from ffmpeg decodec,
		AVFrame* frame = m_decodec.getFrame();
		if (frame && m_videoRenderer) {
			m_videoRenderer->draw(frame->data[0], 
									frame->linesize[0], 
									frame->data[1], 
									frame->linesize[1],
									frame->data[2], 
									frame->linesize[2]);
			
		}
    }
    
private:
    VideoSourceRenderer* m_videoRenderer = nullptr;
};

3.在qml中的显示

在QML中,使用VideoOutput即可完美显示
在qml中时,需要提前进行注册TiVideoEngineManager ,VideoSourceRenderer 不然qml会无法访问

// in qml
VideoOutput {
	id:videoutput
    Component.onCompleted: {
			//需要提前注册好TiVideoEngineManager
           TiVideoEngineManager.videoRenderer.videoSink = videoutput.videoSink
     }
}

4.同理,在widget中也可显示

widget中我并未测试,但是QVideoWidget内也同样有videoSink,要在QVideoWidget显示视频,只需将yuv数据设置进videoSink后即可。

二、QVideoFarme支持的格式

可以查看官方手册https://doc.qt.io/qt-6/qvideoframeformat.html#PixelFormat-enum
它格式很多,不确实是否都可以使用,但是常见的yuv数据格式都是支持的

三、说说渲染效率问题

我在渲染YUV数据时,界面使用的是qml,会发现渲染会比纯使用opengl慢100ms,找了找发现,在Windows上,Qt6.5后,Qt整个界面的渲染默认是根据系统平台决定的,在win11上默认使用的是D3D11,这会导致QVideoSink也是使用D3D11进行yuv的渲染,在理想中,windows中d3d11渲染yuv视频应该会更快才对,但实际并没有。

在尝试将Qt整个界面的渲染改为Opengl后,整个渲染速度即可手写的opengl渲染速度一样。
所以我认为是Qt的RHI中,d3d11的视频渲染没有写好,在Qt官方提交过bug,官方人员只说是关注,不知道后续能不能解决

如果真的需要关心这100ms的渲染效率,可以尝试在main函数的第一行设置

QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值