Qt Qml 开发超高清 4K、8K 视频直播视频客户端

Qt Qml 中提供了丰富的多媒体相关的模块:

类型

描述

​MediaPlayer​

为场景添加音频/视频播放功能。

​CaptureSession​

创建一个用于捕获音频/视频的会话。

​Camera​

访问连接到系统的相机。

​AudioInput​

访问连接到系统的音频输入。

​AudioOutput​

访问连接到系统的音频输出(扬声器、耳机)。

​VideoOutput​

显示视频内容。

​MediaRecorder​

记录来自 CaptureSession 的音频/视频。

​ImageCapture​

从相机中捕捉静止图像。

​Video​

将视频播放功能添加到场景中, 使用 MediaPlayer 和 VideoOutput 类型来提供视频播放功能。

想要实现视频客户端,这里只需使用 ​​VideoOutput​​​ 和 ​​MediaPlayer​​ 即可。

一般本地播放的简单使用如下:

Rectangle {
width: 800
height: 600
color: "black"

MediaPlayer {
id: player
source: "file://video.webm"
videoOutput: videoOutput
}

VideoOutput {
id: videoOutput
anchors.fill: parent
}
}

然而这种方式依赖 ​​MediaPlayer​​​,而 ​​MediaPlayer​​​ 依赖系统 ( 本机平台 ) 提供的编解码器,效果不佳且没有扩展性,因此我们采用​​SkeyePlayerPro​​ 做为后端播放器。

​关于SkeyePlayerPro​

       是视开科技开发和维护的全功能的流媒体播放器,支持 RTSP、RTMP、HTTP、HLS、UDP、RTP、File 等多种流媒体协议播放、支持本地文件播放,支持本地抓拍、本地录像、播放旋转、多屏播放、倍数播放等多种功能特性,核心基于 FFmpeg,稳定、高效、可靠、可控,支持 Windows、Android、iOS 等多个平台,目前在多家教育、安防、行业型公司,都得到的应用,广受好评!

  • 支持高效 4K / 8K 解码。
  • 支持 CPU 软解 / GPU 硬解。
  • 支持视频如 H.264,H.265,MPEG4,MJPEG。
  • API 简单好用且易于集成。

Qt 中集成相当容易,​​.pro​​ 加入下面命令即可:

#SkeyePlayerPro相关
LIBS += -L$$PWD/lib/Player \
-llibSkeyePlayer \
-llibSkeyePlayerPro

INCLUDEPATH += $$PWD/lib/Player/Src \
$$PWD/lib/SkeyePlayer

另一方面,想要在 QML 中播放视频我们需要自己实现一个提供视频帧的 QML 接口类,有两种方法:

  • 提供 ​​QMediaObject​​​ 派生类属性,该属性需要具有可用的 ​​QVideoRenderControl​​ ,类似于下面:

class MyMediaPlayer : QObject 
{
public:
QMediaObject *mediaObject();
};

  • 基于 ​​QObject​​​ 的类提供可写 ​​videoSurface​​​ 属性,可以接受基于 ​​QAbstractVideoSurface​​​ 的类,然后传递自己的 ​​QVideoFrame​​ 即可,这也正是我使用的方法:
class VideoFrameProvider : public QObject
{
Q_OBJECT

Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
Q_PROPERTY(QString videoUrl READ videoUrl WRITE setVideoUrl NOTIFY videoUrlChanged)

public:
VideoFrameProvider(QObject *parent = nullptr);
~VideoFrameProvider();

QAbstractVideoSurface *videoSurface();
void setVideoSurface(QAbstractVideoSurface *surface);

QString videoUrl() const;
void setVideoUrl(const QString &url);

void setFormat(int width, int heigth, QVideoFrame::PixelFormat pixFormat);

signals:
void newVideoFrame(const char *frame);

void videoUrlChanged();

private slots:
void onNewVideoFrameReceived(const char *frame);

private:
//SkeyePlayerPro提供的回调
static int VideoFrameProvider::playCallback(SKEYE_CALLBACK_TYPE_ENUM callbackType, int channelId, void *userPtr, int mediaType, char *buf, SKEYE_FRAME_INFO *frameInfo);

QAbstractVideoSurface *m_surface = nullptr;
QVideoSurfaceFormat m_format;
QString m_videoUrl;
bool m_initFormat = false;
}

关键实现,这里省略了一些 ​​SkeyePlayerPro​​ 的初始化和回调等等设置:

VideoFrameProvider::VideoFrameProvider(QObject *parent)
: QObject(parent)
{
connect(this, &VideoFrameProvider::newVideoFrame, this, &VideoFrameProvider::onNewVideoFrameReceived, Qt::QueuedConnection);
}

QAbstractVideoSurface *VideoFrameProvider::videoSurface()
{
return m_surface;
}

void VideoFrameProvider::setVideoSurface(QAbstractVideoSurface *surface)
{
if (m_surface && m_surface != surface && m_surface->isActive()) {
m_surface->stop();
}

m_surface = surface;

if (m_surface && m_format.isValid()) {
m_format = m_surface->nearestFormat(m_format);
m_surface->start(m_format);
}
}

QString VideoFrameProvider::videoUrl() const
{
return m_videoUrl;
}

void VideoFrameProvider::setVideoUrl(const QString &url)
{
if (m_videoUrl != url) {
m_videoUrl = url;
emit videoUrlChanged();
}
}

void VideoFrameProvider::setFormat(int width, int heigth, QVideoFrame::PixelFormat pixFormat)
{
QVideoSurfaceFormat format(QSize(width, heigth), pixFormat);
m_format = format;

if (m_surface) {
if (m_surface->isActive()) {
m_surface->stop();
}
m_format = m_surface->nearestFormat(format);
m_surface->start(m_format);
}
}

void VideoFrameProvider::onNewVideoFrameReceived(const char *frame)
{
int size = 0;
int width = m_format.frameWidth();
int height = m_format.frameHeight();
if (m_format.pixelFormat() == QVideoFrame::Format_YUV420P) {
size = width * height * 3 / 2;
}

QVideoFrame videoFrame(size, QSize(width, height), width, m_format.pixelFormat());
if (videoFrame.map(QAbstractVideoBuffer::WriteOnly)) {
memmove(videoFrame.bits(), frame, size);
videoFrame.unmap();
}

if (m_surface && m_surface->isActive()) {
if (!m_surface->present(videoFrame)) {
qDebug() << "VideoFrameProvider Suface Error:" << m_surface->error();
}
}
}

int VideoFrameProvider::playCallback(SKEYE_CALLBACK_TYPE_ENUM callbackType, int channelId, void *userPtr, int mediaType, char *buf, SKEYE_FRAME_INFO *frameInfo)
{
Q_UNUSED(channelId);

VideoFrameProvider *_this = reinterpret_cast<VideoFrameProvider *>(userPtr);

if (callbackType == SKEYE_TYPE_DECODE_DATA && mediaType == MEDIA_TYPE_VIDEO) {
auto frameWidth = frameInfo->width ;
auto frameHeight = frameInfo->height;

if (buf) {
if (!_this->m_initFormat) {
_this->setFormat(frameWidth, frameHeight, QVideoFrame::Format_YUV420P);
_this->m_initFormat = true;
}
emit _this->newVideoFrame(buf);
}
}

return 0;
}

最后将上面的 ​​MediaPlayer​​​ 替换为 ​​VideoProvider​​ 即可:

Rectangle {
width: 800
height: 600
color: "black"

VideoFrameProvider {
id: provider
source: "rtsp://192.168.0.33:8554/channel=1"
}

VideoOutput {
id: videoOutput
anchors.fill: parent
source: provider
}
}

关于SkeyeARS

SkeyeARS全景AR增强监视系统, 是视开科技开发的一款基于宽场景多路视频无缝拼接、视频实时增强、监视目标增强显示、目标自动跟踪、视频存储回放、远程数据传输和多通道全景视频同步显示等功能的综合视频AR增强监视系统,广泛应用于智慧交通、智慧城市、智慧机场等大场景智能监控领域。