如何使用 QMediaPlayer 保存帧?
How to save a frame using QMediaPlayer?
我想保存 QMediaPlayer
中的一帧图像。看了文档,明白了应该用QVideoProbe
。我正在使用以下代码:
QMediaPlayer *player = new QMediaPlayer();
QVideoProbe *probe = new QVideoProbe;
connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
qDebug()<<probe->setSource(player); // Returns true, hopefully.
player->setVideoOutput(myVideoSurface);
player->setMedia(QUrl::fromLocalFile("observation.mp4"));
player->play(); // Start receving frames as they get presented to myVideoSurface
但不幸的是,probe->setSource(player)
对我来说总是 returns false
,因此我的插槽 processFrame
没有被触发。
我做错了什么?有人有 QVideoProbe
的工作示例吗?
Qt QVideoProbe documentation之后:
bool QVideoProbe::setSource(QMediaObject *mediaObject)
Starts monitoring the given mediaObject.
If there is no media object associated with mediaObject
, or if it is
zero, this probe will be deactivated and this function wil return
true.
If the media object instance does not support monitoring video, this
function will return false.
Any previously monitored objects will no longer be monitored. Passing
in the same object will be ignored, but monitoring will continue.
看来你的"media object instance does not support monitoring video"
你没有做错任何事。正如@DYangu 所指出的,您的媒体对象实例不支持监控视频。我有同样的问题(QAudioProbe
but it doesn't interest us here). I found a solution by looking at answer and this one.
也有同样的问题
主要思想是子类QAbstractVideoSurface。完成后,它将调用 QAbstractVideoSurface
实现的方法 QAbstractVideoSurface::present(const QVideoFrame & frame)
,您将能够处理视频的帧。
正如所说here,通常你只需要重新实现两个方法:
- supportedPixelFormats 以便制作者可以 select 为
QVideoFrame
选择合适的格式
- present 允许显示框架
但当时我在Qt源码里搜索,高兴地找到了this piece of code,帮我做了一个完整的实现。因此,这里是使用 "video frame grabber" 的完整代码。
VideoFrameGrabber.cpp :
#include "VideoFrameGrabber.h"
#include <QtWidgets>
#include <qabstractvideosurface.h>
#include <qvideosurfaceformat.h>
VideoFrameGrabber::VideoFrameGrabber(QWidget *widget, QObject *parent)
: QAbstractVideoSurface(parent)
, widget(widget)
, imageFormat(QImage::Format_Invalid)
{
}
QList<QVideoFrame::PixelFormat> VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
Q_UNUSED(handleType);
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_RGB24
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555
<< QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24
<< QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555
<< QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444
<< QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P
<< QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV
<< QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21
<< QVideoFrame::Format_IMC1
<< QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3
<< QVideoFrame::Format_IMC4
<< QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16
<< QVideoFrame::Format_Jpeg
<< QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng;
}
bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
return imageFormat != QImage::Format_Invalid
&& !size.isEmpty()
&& format.handleType() == QAbstractVideoBuffer::NoHandle;
}
bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format)
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) {
this->imageFormat = imageFormat;
imageSize = size;
sourceRect = format.viewport();
QAbstractVideoSurface::start(format);
widget->updateGeometry();
updateVideoRect();
return true;
} else {
return false;
}
}
void VideoFrameGrabber::stop()
{
currentFrame = QVideoFrame();
targetRect = QRect();
QAbstractVideoSurface::stop();
widget->update();
}
bool VideoFrameGrabber::present(const QVideoFrame &frame)
{
if (frame.isValid())
{
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(),
cloneFrame.width(),
cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));
emit frameAvailable(image); // this is very important
cloneFrame.unmap();
}
if (surfaceFormat().pixelFormat() != frame.pixelFormat()
|| surfaceFormat().frameSize() != frame.size()) {
setError(IncorrectFormatError);
stop();
return false;
} else {
currentFrame = frame;
widget->repaint(targetRect);
return true;
}
}
void VideoFrameGrabber::updateVideoRect()
{
QSize size = surfaceFormat().sizeHint();
size.scale(widget->size().boundedTo(size), Qt::KeepAspectRatio);
targetRect = QRect(QPoint(0, 0), size);
targetRect.moveCenter(widget->rect().center());
}
void VideoFrameGrabber::paint(QPainter *painter)
{
if (currentFrame.map(QAbstractVideoBuffer::ReadOnly)) {
const QTransform oldTransform = painter->transform();
if (surfaceFormat().scanLineDirection() == QVideoSurfaceFormat::BottomToTop) {
painter->scale(1, -1);
painter->translate(0, -widget->height());
}
QImage image(
currentFrame.bits(),
currentFrame.width(),
currentFrame.height(),
currentFrame.bytesPerLine(),
imageFormat);
painter->drawImage(targetRect, image, sourceRect);
painter->setTransform(oldTransform);
currentFrame.unmap();
}
}
VideoFrameGrabber.h
#ifndef VIDEOFRAMEGRABBER_H
#define VIDEOFRAMEGRABBER_H
#include <QtWidgets>
class VideoFrameGrabber : public QAbstractVideoSurface
{
Q_OBJECT
public:
VideoFrameGrabber(QWidget *widget, QObject *parent = 0);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
bool isFormatSupported(const QVideoSurfaceFormat &format) const;
bool start(const QVideoSurfaceFormat &format);
void stop();
bool present(const QVideoFrame &frame);
QRect videoRect() const { return targetRect; }
void updateVideoRect();
void paint(QPainter *painter);
private:
QWidget *widget;
QImage::Format imageFormat;
QRect targetRect;
QSize imageSize;
QRect sourceRect;
QVideoFrame currentFrame;
signals:
void frameAvailable(QImage frame);
};
#endif //VIDEOFRAMEGRABBER_H
注意:在.h中,你会看到我添加了一个signal,以图像作为参数。 这将允许您在代码中的任何位置处理您的框架。当时,此信号以 QImage
作为参数,但如果您愿意,当然可以使用 QVideoFrame
。
现在,我们准备使用这个视频帧采集器:
QMediaPlayer* player = new QMediaPlayer(this);
// no more QVideoProbe
VideoFrameGrabber* grabber = new VideoFrameGrabber(this);
player->setVideoOutput(grabber);
connect(grabber, SIGNAL(frameAvailable(QImage)), this, SLOT(processFrame(QImage)));
现在您只需声明一个名为 processFrame(QImage image)
的插槽,每次您输入 VideoFrameGrabber
的方法 present 时,您都会收到一个 QImage
。
希望对您有所帮助!
TL;DR: https://gist.github.com/JC3/a7bab65acbd7659d1e57103d2b0021ba(仅文件)
我有一个类似的问题(5.15.2;虽然在我的例子中我使用的是 Windows,但肯定是在使用 DirectShow 后端,探针附件是 returning true,样本采集器在图表中,但回调未触发)。
我 never figured it out 但需要让某些东西正常工作,所以我从 QAbstractVideoSurface
中拼凑了一个,到目前为止它一直运行良好。它比此 post 中的其他一些实现要简单一些,而且都在一个文件中。
请注意,如果您打算同时处理帧 和 回放它们,则需要 Qt 5.15 或更高版本,因为多表面 QMediaPlayer::setVideoOutput
不是添加到 5.15。如果你只想处理视频,你仍然可以使用下面的代码作为 pre-5.15 的模板,只需删除 formatSource_
部分。
代码:
VideoProbeSurface.h(唯一的文件;link 是 Gist)
#ifndef VIDEOPROBESURFACE_H
#define VIDEOPROBESURFACE_H
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
class VideoProbeSurface : public QAbstractVideoSurface {
Q_OBJECT
public:
VideoProbeSurface (QObject *parent = nullptr)
: QAbstractVideoSurface(parent)
, formatSource_(nullptr)
{
}
void setFormatSource (QAbstractVideoSurface *source) {
formatSource_ = source;
}
QList<QVideoFrame::PixelFormat> supportedPixelFormats (QAbstractVideoBuffer::HandleType type) const override {
return formatSource_ ? formatSource_->supportedPixelFormats(type)
: QList<QVideoFrame::PixelFormat>();
}
QVideoSurfaceFormat nearestFormat (const QVideoSurfaceFormat &format) const override {
return formatSource_ ? formatSource_->nearestFormat(format)
: QAbstractVideoSurface::nearestFormat(format);
}
bool present (const QVideoFrame &frame) override {
emit videoFrameProbed(frame);
return true;
}
signals:
void videoFrameProbed (const QVideoFrame &frame);
private:
QAbstractVideoSurface *formatSource_;
};
#endif // VIDEOPROBESURFACE_H
我寻求最快的写入实现,因此它只是从另一个表面转发支持的像素格式(我的目的是探测 和 回放到 QVideoWidget
) 然后你得到你得到的任何格式。不过,我只需要将子图像抓取到 QImage
s 中,它可以处理最常见的格式。但是您可以修改它以强制使用您想要的任何格式(例如,您可能只想 QImage
支持的 return 格式或过滤掉 不 支持的源格式 QImage)
, 等等).
示例设置:
QMediaPlayer *player = ...;
QVideoWidget *widget = ...;
// forward surface formats provided by the video widget:
VideoProbeSurface *probe = new VideoProbeSurface(...);
probe->setFormatSource(widget->videoSurface());
// same signal signature as QVideoProbe's signal:
connect(probe, &VideoProbeSurface::videoFrameProbed, ...);
// the key move is to render to both the widget (for viewing)
// and probe (for processing). fortunately, QMediaPlayer can
// take a list:
player->setVideoOutput({ widget->videoSurface(), probe });
注释
我必须做的唯一真正粗略的事情是 const_cast
接收端的 QVideoFrame
(用于只读访问),因为 QVideoFrame::map()
不是 const
:
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
...;
const_cast<QVideoFrame&>(frame).unmap();
}
但真正的 QVideoProbe
会让你做同样的事情,所以我不知道那是怎么回事 -- 这是一个 st运行ge API。我 运行 对软件、本机硬件和回写硬件渲染器和解码器进行了一些测试,并且 map
/unmap
在读取模式下似乎运行正常,所以,无论如何。
在性能方面,回调时间过长视频会卡顿,请相应设计。但是,我没有测试 QueuedConnection
,所以我不知道那是否仍然存在问题(尽管信号参数是参考这一事实会让我对尝试它保持警惕,并且可以想象GPU 在插槽结束被调用之前释放内存的问题)。我也不知道 QVideoProbe
在这方面的表现如何。我知道,至少在我的机器上,我可以将全高清 (1920 x 1080) 分辨率 QImage
s 打包并排队到线程池进行处理,而不会减慢视频速度。
您可能还想为异常安全 unmap()
等实现某种自动取消映射实用程序对象。但同样,这不是唯一的,您必须对 QVideoProbe
.
希望这对其他人有所帮助。
QImage 使用示例
PS,将任意格式的QVideoFrame
s打包成一个QImage
的例子:
void MyVideoProcessor::onFrameProbed(const QVideoFrame &frame) {
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
auto imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), frame.bytesPerLine(), imageFormat);
// *if* you want to use this elsewhere you must force detach:
image = image.copy();
// but if you don't need to use it past unmap(), you can just
// use the original image instead of a copy.
// <---- now do whatever with the image, e.g. save() it.
// if you *haven't* copied the image, then, before unmapping,
// kill any internal data pointers just to be safe:
image = QImage();
const_cast<QVideoFrame&>(frame).unmap();
}
}
注意事项:
- 直接从数据构建
QImage
速度快,而且基本免费:无需复制。
- 数据缓冲区仅在
map
和 unmap
之间在技术上有效,因此如果您打算在该范围之外使用 QImage
,则需要使用 copy()
(或任何其他强制分离的东西)强制进行深度复制。
- 您可能还想确保在调用
unmap
之前销毁原始未复制的 QImage
。它不太可能引起问题,但在任何给定时间尽量减少无效指针的数量总是一个好主意,而且 QImage
文档说“缓冲区必须在 QImage 和所有副本的整个生命周期内保持有效没有被修改或以其他方式从原始缓冲区分离”。最好严格一点。
我想保存 QMediaPlayer
中的一帧图像。看了文档,明白了应该用QVideoProbe
。我正在使用以下代码:
QMediaPlayer *player = new QMediaPlayer();
QVideoProbe *probe = new QVideoProbe;
connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
qDebug()<<probe->setSource(player); // Returns true, hopefully.
player->setVideoOutput(myVideoSurface);
player->setMedia(QUrl::fromLocalFile("observation.mp4"));
player->play(); // Start receving frames as they get presented to myVideoSurface
但不幸的是,probe->setSource(player)
对我来说总是 returns false
,因此我的插槽 processFrame
没有被触发。
我做错了什么?有人有 QVideoProbe
的工作示例吗?
Qt QVideoProbe documentation之后:
bool QVideoProbe::setSource(QMediaObject *mediaObject)
Starts monitoring the given mediaObject.
If there is no media object associated with
mediaObject
, or if it is zero, this probe will be deactivated and this function wil return true.If the media object instance does not support monitoring video, this function will return false.
Any previously monitored objects will no longer be monitored. Passing in the same object will be ignored, but monitoring will continue.
看来你的"media object instance does not support monitoring video"
你没有做错任何事。正如@DYangu 所指出的,您的媒体对象实例不支持监控视频。我有同样的问题(QAudioProbe
but it doesn't interest us here). I found a solution by looking at
主要思想是子类QAbstractVideoSurface。完成后,它将调用 QAbstractVideoSurface
实现的方法 QAbstractVideoSurface::present(const QVideoFrame & frame)
,您将能够处理视频的帧。
正如所说here,通常你只需要重新实现两个方法:
- supportedPixelFormats 以便制作者可以 select 为
QVideoFrame
选择合适的格式
- present 允许显示框架
但当时我在Qt源码里搜索,高兴地找到了this piece of code,帮我做了一个完整的实现。因此,这里是使用 "video frame grabber" 的完整代码。
VideoFrameGrabber.cpp :
#include "VideoFrameGrabber.h"
#include <QtWidgets>
#include <qabstractvideosurface.h>
#include <qvideosurfaceformat.h>
VideoFrameGrabber::VideoFrameGrabber(QWidget *widget, QObject *parent)
: QAbstractVideoSurface(parent)
, widget(widget)
, imageFormat(QImage::Format_Invalid)
{
}
QList<QVideoFrame::PixelFormat> VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
Q_UNUSED(handleType);
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_RGB24
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555
<< QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24
<< QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555
<< QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444
<< QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P
<< QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV
<< QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21
<< QVideoFrame::Format_IMC1
<< QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3
<< QVideoFrame::Format_IMC4
<< QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16
<< QVideoFrame::Format_Jpeg
<< QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng;
}
bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
return imageFormat != QImage::Format_Invalid
&& !size.isEmpty()
&& format.handleType() == QAbstractVideoBuffer::NoHandle;
}
bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format)
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) {
this->imageFormat = imageFormat;
imageSize = size;
sourceRect = format.viewport();
QAbstractVideoSurface::start(format);
widget->updateGeometry();
updateVideoRect();
return true;
} else {
return false;
}
}
void VideoFrameGrabber::stop()
{
currentFrame = QVideoFrame();
targetRect = QRect();
QAbstractVideoSurface::stop();
widget->update();
}
bool VideoFrameGrabber::present(const QVideoFrame &frame)
{
if (frame.isValid())
{
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(),
cloneFrame.width(),
cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));
emit frameAvailable(image); // this is very important
cloneFrame.unmap();
}
if (surfaceFormat().pixelFormat() != frame.pixelFormat()
|| surfaceFormat().frameSize() != frame.size()) {
setError(IncorrectFormatError);
stop();
return false;
} else {
currentFrame = frame;
widget->repaint(targetRect);
return true;
}
}
void VideoFrameGrabber::updateVideoRect()
{
QSize size = surfaceFormat().sizeHint();
size.scale(widget->size().boundedTo(size), Qt::KeepAspectRatio);
targetRect = QRect(QPoint(0, 0), size);
targetRect.moveCenter(widget->rect().center());
}
void VideoFrameGrabber::paint(QPainter *painter)
{
if (currentFrame.map(QAbstractVideoBuffer::ReadOnly)) {
const QTransform oldTransform = painter->transform();
if (surfaceFormat().scanLineDirection() == QVideoSurfaceFormat::BottomToTop) {
painter->scale(1, -1);
painter->translate(0, -widget->height());
}
QImage image(
currentFrame.bits(),
currentFrame.width(),
currentFrame.height(),
currentFrame.bytesPerLine(),
imageFormat);
painter->drawImage(targetRect, image, sourceRect);
painter->setTransform(oldTransform);
currentFrame.unmap();
}
}
VideoFrameGrabber.h
#ifndef VIDEOFRAMEGRABBER_H
#define VIDEOFRAMEGRABBER_H
#include <QtWidgets>
class VideoFrameGrabber : public QAbstractVideoSurface
{
Q_OBJECT
public:
VideoFrameGrabber(QWidget *widget, QObject *parent = 0);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
bool isFormatSupported(const QVideoSurfaceFormat &format) const;
bool start(const QVideoSurfaceFormat &format);
void stop();
bool present(const QVideoFrame &frame);
QRect videoRect() const { return targetRect; }
void updateVideoRect();
void paint(QPainter *painter);
private:
QWidget *widget;
QImage::Format imageFormat;
QRect targetRect;
QSize imageSize;
QRect sourceRect;
QVideoFrame currentFrame;
signals:
void frameAvailable(QImage frame);
};
#endif //VIDEOFRAMEGRABBER_H
注意:在.h中,你会看到我添加了一个signal,以图像作为参数。 这将允许您在代码中的任何位置处理您的框架。当时,此信号以 QImage
作为参数,但如果您愿意,当然可以使用 QVideoFrame
。
现在,我们准备使用这个视频帧采集器:
QMediaPlayer* player = new QMediaPlayer(this);
// no more QVideoProbe
VideoFrameGrabber* grabber = new VideoFrameGrabber(this);
player->setVideoOutput(grabber);
connect(grabber, SIGNAL(frameAvailable(QImage)), this, SLOT(processFrame(QImage)));
现在您只需声明一个名为 processFrame(QImage image)
的插槽,每次您输入 VideoFrameGrabber
的方法 present 时,您都会收到一个 QImage
。
希望对您有所帮助!
TL;DR: https://gist.github.com/JC3/a7bab65acbd7659d1e57103d2b0021ba(仅文件)
我有一个类似的问题(5.15.2;虽然在我的例子中我使用的是 Windows,但肯定是在使用 DirectShow 后端,探针附件是 returning true,样本采集器在图表中,但回调未触发)。
我 never figured it out 但需要让某些东西正常工作,所以我从 QAbstractVideoSurface
中拼凑了一个,到目前为止它一直运行良好。它比此 post 中的其他一些实现要简单一些,而且都在一个文件中。
请注意,如果您打算同时处理帧 和 回放它们,则需要 Qt 5.15 或更高版本,因为多表面 QMediaPlayer::setVideoOutput
不是添加到 5.15。如果你只想处理视频,你仍然可以使用下面的代码作为 pre-5.15 的模板,只需删除 formatSource_
部分。
代码:
VideoProbeSurface.h(唯一的文件;link 是 Gist)
#ifndef VIDEOPROBESURFACE_H
#define VIDEOPROBESURFACE_H
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
class VideoProbeSurface : public QAbstractVideoSurface {
Q_OBJECT
public:
VideoProbeSurface (QObject *parent = nullptr)
: QAbstractVideoSurface(parent)
, formatSource_(nullptr)
{
}
void setFormatSource (QAbstractVideoSurface *source) {
formatSource_ = source;
}
QList<QVideoFrame::PixelFormat> supportedPixelFormats (QAbstractVideoBuffer::HandleType type) const override {
return formatSource_ ? formatSource_->supportedPixelFormats(type)
: QList<QVideoFrame::PixelFormat>();
}
QVideoSurfaceFormat nearestFormat (const QVideoSurfaceFormat &format) const override {
return formatSource_ ? formatSource_->nearestFormat(format)
: QAbstractVideoSurface::nearestFormat(format);
}
bool present (const QVideoFrame &frame) override {
emit videoFrameProbed(frame);
return true;
}
signals:
void videoFrameProbed (const QVideoFrame &frame);
private:
QAbstractVideoSurface *formatSource_;
};
#endif // VIDEOPROBESURFACE_H
我寻求最快的写入实现,因此它只是从另一个表面转发支持的像素格式(我的目的是探测 和 回放到 QVideoWidget
) 然后你得到你得到的任何格式。不过,我只需要将子图像抓取到 QImage
s 中,它可以处理最常见的格式。但是您可以修改它以强制使用您想要的任何格式(例如,您可能只想 QImage
支持的 return 格式或过滤掉 不 支持的源格式 QImage)
, 等等).
示例设置:
QMediaPlayer *player = ...;
QVideoWidget *widget = ...;
// forward surface formats provided by the video widget:
VideoProbeSurface *probe = new VideoProbeSurface(...);
probe->setFormatSource(widget->videoSurface());
// same signal signature as QVideoProbe's signal:
connect(probe, &VideoProbeSurface::videoFrameProbed, ...);
// the key move is to render to both the widget (for viewing)
// and probe (for processing). fortunately, QMediaPlayer can
// take a list:
player->setVideoOutput({ widget->videoSurface(), probe });
注释
我必须做的唯一真正粗略的事情是 const_cast
接收端的 QVideoFrame
(用于只读访问),因为 QVideoFrame::map()
不是 const
:
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
...;
const_cast<QVideoFrame&>(frame).unmap();
}
但真正的 QVideoProbe
会让你做同样的事情,所以我不知道那是怎么回事 -- 这是一个 st运行ge API。我 运行 对软件、本机硬件和回写硬件渲染器和解码器进行了一些测试,并且 map
/unmap
在读取模式下似乎运行正常,所以,无论如何。
在性能方面,回调时间过长视频会卡顿,请相应设计。但是,我没有测试 QueuedConnection
,所以我不知道那是否仍然存在问题(尽管信号参数是参考这一事实会让我对尝试它保持警惕,并且可以想象GPU 在插槽结束被调用之前释放内存的问题)。我也不知道 QVideoProbe
在这方面的表现如何。我知道,至少在我的机器上,我可以将全高清 (1920 x 1080) 分辨率 QImage
s 打包并排队到线程池进行处理,而不会减慢视频速度。
您可能还想为异常安全 unmap()
等实现某种自动取消映射实用程序对象。但同样,这不是唯一的,您必须对 QVideoProbe
.
希望这对其他人有所帮助。
QImage 使用示例
PS,将任意格式的QVideoFrame
s打包成一个QImage
的例子:
void MyVideoProcessor::onFrameProbed(const QVideoFrame &frame) {
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
auto imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), frame.bytesPerLine(), imageFormat);
// *if* you want to use this elsewhere you must force detach:
image = image.copy();
// but if you don't need to use it past unmap(), you can just
// use the original image instead of a copy.
// <---- now do whatever with the image, e.g. save() it.
// if you *haven't* copied the image, then, before unmapping,
// kill any internal data pointers just to be safe:
image = QImage();
const_cast<QVideoFrame&>(frame).unmap();
}
}
注意事项:
- 直接从数据构建
QImage
速度快,而且基本免费:无需复制。 - 数据缓冲区仅在
map
和unmap
之间在技术上有效,因此如果您打算在该范围之外使用QImage
,则需要使用copy()
(或任何其他强制分离的东西)强制进行深度复制。 - 您可能还想确保在调用
unmap
之前销毁原始未复制的QImage
。它不太可能引起问题,但在任何给定时间尽量减少无效指针的数量总是一个好主意,而且QImage
文档说“缓冲区必须在 QImage 和所有副本的整个生命周期内保持有效没有被修改或以其他方式从原始缓冲区分离”。最好严格一点。