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

498 阅读3分钟

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

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

类型描述
MediaPlayer为场景添加音频/视频播放功能。
CaptureSession创建一个用于捕获音频/视频的会话。
Camera访问连接到系统的相机。
AudioInput访问连接到系统的音频输入。
AudioOutput访问连接到系统的音频输出(扬声器、耳机)。
VideoOutput显示视频内容。
MediaRecorder记录来自 CaptureSession 的音频/视频。
ImageCapture从相机中捕捉静止图像。
Video将视频播放功能添加到场景中, 使用 MediaPlayer 和 VideoOutput 类型来提供视频播放功能。

想要实现视频客户端,这里只需使用 VideoOutputMediaPlayer 即可。

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

 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增强监视系统,广泛应用于智慧交通、智慧城市、智慧机场等大场景智能监控领域。

详细说明:www.openskeye.cn/web/product…