目录
4.1、从 QObject 继承并提供 videoSurface 属性给 QML
4.2、将对象注入到全局 Context 提供 QML 使用
1、简述
Qt 5.5.1版本的QCamera目前还不支持Android的情况下,于是用QML做了一个调用Android下摄像头的示例
2、效果
3、源代码
代码如下:
核心代码:
Rectangle {
width: 800
height: 600
color: "black"
MediaPlayer {
id: player
source: "file://video.webm"
autoPlay: true
}
VideoOutput {
id: videoOutput
source: player
anchors.fill: parent
}
}
4、VideoOutput
在一些传统应用中,如果想使用 Qt 在 QWidget 或者 QML 中显示自定义的视频数据流,需要引入 OpenGL 来实现。而实际 Qt 已经准备了 VideoOutput 类型可以很方便的调用系统摄像头和使用自定义数据流。在 Qt 官网中,VideoOutput 的介绍中说明,source
属性可以是一个自定义派生于 QObject 的子类,并提供一个类型为 QMediaObject 的属性命名为 mediaObject
,或者是一个派生与 QObject 的子类并提供一个类型为 QAbstractVideoSurface 的属性命名为 videoSurface。其中任意一个方法都可以实现自定义视频数据流的播放,本文介绍第二种方法。
4.1、从 QObject 继承并提供 videoSurface 属性给 QML
像 Stackoverflow 中的介绍,你需要这样一个类,该类用 Q_PROPERTY 宏提供了一个名字为 videoSurface
的属性(符合 source 属性的第二个要求)
#include <QObject>
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
class FrameProvider : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
public:
QAbstractVideoSurface* videoSurface() const { return m_surface; }
private:
QAbstractVideoSurface *m_surface = NULL;
QVideoSurfaceFormat m_format;
public:
void 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 setFormat(int width, int heigth, int format)
{
QSize size(width, heigth);
QVideoSurfaceFormat format(size, format);
m_format = format;
if (m_surface)
{
if (m_surface->isActive())
{
m_surface->stop();
}
m_format = m_surface->nearestFormat(m_format);
m_surface->start(m_format);
}
}
public slots:
void onNewVideoContentReceived(const QVideoFrame &frame)
{
if (m_surface)
m_surface->present(frame);
}
};
4.2、将对象注入到全局 Context 提供 QML 使用
Stackoverflow 的方法是将 FrameProvider 注册成一个 QML 可以使用的类型,这种方法也可以,但是你可以看到在 main 函数中需要去从 QML 中搜索该类实例化的对象句柄,然后再绑定信号和槽,这个相对麻烦一些。我们换一种方式就是先 new 对象然后绑定信号和槽函数,最后再把对象注入到全局上下文中,让 QML 在任意位置都可以访问这个对象。方法如下:
FrameProvider* provider = new FrameProvider();
CustomFramesource source;
// Set the correct format for the video surface (Make sure your selected format is supported by the surface)
provider->setFormat(source.width,source.height, source.format);
// Connect your frame source with the provider
QObject::connect(&source, SIGNAL(newFrameAvailable(const QVideoFrame &)), provider, SLOT(onNewVideoContentReceived(const QVideoFrame &)));
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("frameProvider", frameProvider);
// .... Other code
4.3、QML 中引用
由于全局注入了 frameProvider
类型,在 VideoOutput 中直接指定给 source 属性就可以了。
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtMultimedia 5.4
ApplicationWindow {
objectName: "mainWindow"
visible: true
width: 640
height: 480
VideoOutput {
id: display
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width
source: frameProvider
}
}
4.4、构造数据源
当你收到一帧 YUV 数据的时候(上面代码中 CustomFramesource 类要做的事情),你需要把这一帧数据转换为 QVideoFrame 并发送信号 newFrameAvailable
,这样 frameProvider
类收到信号后就会将这一帧的数据通知给 QML 来显示。具体转换的代码如下:
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();
emit newFrameAvailable(f);
}
代码中使用了 QVideoFrame 的第二个构造函数,先根据视频数据大小创建一个空闲位置,然后 map 这块位置到内存,拷贝数据进去,最后 unmap 并发送信号给 provider 使用。这里如果考虑性能问题,可以将 QVideoFrame 使用智能指针传递,这样可以减少数据的拷贝过程。