创建qml自定义视频源(Qt5.15.2+设备帧)

前言

若是看过笔者之前的文档,应该会注意到,笔者显示自定义视频,使用的是QQuickImageProvider和Image类型实现。本次笔者记录另一种显示方式,创建视频源,然后使用VideoOutput显示

因为Qt5和Qt6多媒体模块发生了很大的变化,故本次先记录下Qt5.15.2下的测试结果

一、查看官方文档

1.查看VideoOutput文档

https://doc.qt.io/qt-5/qml-qtmultimedia-videooutput.html#source-prop

在文档中,找到属性source,会发现如下内空

If you are extending your own C++ classes to interoperate with VideoOutput, you can either provide a QObject based class with a mediaObject property that exposes a QMediaObject derived class that has a QVideoRendererControl available, or you can provide a QObject based class with a writable videoSurface property that can accept a QAbstractVideoSurface based class and can follow the correct protocol to deliver QVideoFrames to it.

翻译过来,意思是source 属性可以是一个自定义派生于 QObject 的子类,并提供一个类型为 QMediaObject 的属性命名为 mediaObject,或者是一个派生与 QObject 的子类并提供一个类型为 QAbstractVideoSurface 的属性命名为 videoSurface。其中任意一个方法都可以实现自定义视频数据流的播放,笔者使用第二种方法实现

2.查看Video Overview文档

https://doc.qt.io/qt-5/videooverview.html#monitoring-video-frames

找到Working with Low Level Video Frames

可以看到官方文档清楚地介绍了,如何创建一个视频源,笔者依照官方文档进行测试

二、创建视频源的类

笔者本次只挑重点部分,关于如何获取视频帧,笔者在QR扫码综合示例教程中,已经介绍过了,有需要的读者可以自己查找下

笔者创建了一个类Tool_FrameProvider,继承自QObject

class Tool_FrameProvider : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
public:
    explicit Tool_FrameProvider(QObject *parent = nullptr);

    QAbstractVideoSurface *videoSurface() const;

    /*!
     * \brief 可设置外部自定义QAbstractVideoSurface
     * \param surface
     */
    void setVideoSurface(QAbstractVideoSurface *surface);

    /*!
     * \brief 设置视频格式
     * \param width     视频宽
     * \param heigth    视频高
     * \param format    enum QVideoFrame::PixelFormat
     */
    void setFormat(int width, int heigth, QVideoFrame::PixelFormat format);

public slots:
    /*!
     * \brief 接收外部数据源,视频帧
     * \param frame
     */
    void onNewVideoContentReceived(const QVideoFrame &frame);

signals:

private:
    QAbstractVideoSurface *m_surface = nullptr;
    QVideoSurfaceFormat m_format;

};

该类用 Q_PROPERTY 宏提供了一个名字为 videoSurface 的属性(符合 VideoOutput文档中source 属性的第二个要求) 。

onNewVideoContentReceived()函数用来传入已经封装好的视频帧。

具体实现如下

QAbstractVideoSurface *Tool_FrameProvider::videoSurface() const
{
    return m_surface;
}

void Tool_FrameProvider::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);
    }

}
//设置当前帧的视频格式
void Tool_FrameProvider::setFormat(int width, int heigth, QVideoFrame::PixelFormat format)
{

    QSize size(width, heigth);
    QVideoSurfaceFormat vsformat(size, format);
    m_format = vsformat;

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

    }
}
//添加已经封装好的视频帧
void Tool_FrameProvider::onNewVideoContentReceived(const QVideoFrame &frame)
{
    //按照视频帧设置格式
    setFormat(frame.width(),frame.height(),frame.pixelFormat());

    if (m_surface)
        m_surface->present(frame);

}

三、生成视频帧

生成视频帧的关键代码如下

QImage image ;
QVideoFrame f(image.sizeInBytes(),
              image.size(), image.bytesPerLine(), QVideoFrame::Format_RGB32);
if (f.map(QAbstractVideoBuffer::WriteOnly)) {
    memcpy(f.bits(), image.bits(), image.width()*image.height()*4);
    f.setStartTime(0);
    f.unmap();
    m_surface->present(f);
}

 笔者给出的代码,是从QImage生成QVideoFrame的参考代码,格式不限于RGB32,也可以是其他格式,如yuv,参考代码如下

QVideoFrame f(size, QSize(width, height), width, QVideoFrame::Format_YUV420P);
if (f.map(QAbstractVideoBuffer::WriteOnly)) {
    memcpy(f.bits(), data, size);
    f.setStartTime(0);
    f.unmap();
}

 具体的格式是否支持,可以文档中的枚举量QVideoFrame::PixelFormat

注:

笔者在测试时发现,可以直接使用QVideoProbe::videoFrameProbed获得的QVideoFrame,但有的格式无法正常显示,如QVideoFrame::Format_Jpeg,笔者将其转换成QImage,然后再生成QVideoFrame后,显示就正常了。关键代码如下

setFormat(frame.width(),frame.height(),QVideoFrame::Format_RGB32);
QImage image = frame.image();
QVideoFrame f(image.sizeInBytes(),
              image.size(), image.bytesPerLine(), QVideoFrame::Format_RGB32);
if (f.map(QAbstractVideoBuffer::WriteOnly)) {
    memcpy(f.bits(), image.bits(), image.sizeInBytes());
    f.setStartTime(0);
    f.unmap();
    m_surface->present(f);
}

四、qml调用

在qml中使用主要有两种方法

1.在C++中实例化,之后使用 QQmlContext::setContextProperty()注册到qml中,然后VideoOutput.source赋值实例化的对象就可以

2.使用qmlRegisterType()将C++类型注册到qml,直接在qml中实例化,然后VideoOutput.source赋值实例化的id就可以。关键代码如下

    Tool_FrameProvider {
        id: tool_frameProvider

    }
    videoOutput.source:  tool_frameProvider

五、运行测试

笔者在win10下的运行结果

笔者在android下的运行结果

 

 发现的问题:

1.在win10下运行时,内存先是不断增加,达到一定值后,再减小。笔者测试未发生异常退出问题

2.在鸿蒙平板上测试时,需要自行处理旋转问题

3.在鸿蒙平板上测试时,取到1000帧左右时总是异常退出。笔者简单查看打印了下日志,发现是在获取视频帧QVideoFrame时,就已经出问题了

本次测试使用的源码

 后记

从本次测试看,有点让笔者失望。笔者打算继续测试下取景器时的视频帧

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

꧁白杨树下꧂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值