TL;DR: https ://gist.github.com/JC3/a7bab65acbd7659d1e57103d2b0021ba (仅文件)
我有一个类似的问题(5.15.2;虽然在我的情况下我在 Windows 上,但肯定在使用 DirectShow 后端,探针附件返回 true,样本采集器在图中,但回调没有触发)。
我从来没有想出来,但需要让一些东西工作,所以我从 a 中拼凑出一个QAbstractVideoSurface
,到目前为止它运行良好。它比本文中的其他一些实现要简单一些,而且都在一个文件中。
请注意,如果您打算同时处理帧并以此回放它们,则需要 Qt 5.15 或更高版本,因为QMediaPlayer::setVideoOutput
直到 5.15 才添加多表面。如果您只想处理视频,您仍然可以使用下面的代码作为 5.15 之前的模板,只需将formatSource_
部分内容删除即可。
代码:
VideoProbeSurface.h(唯一的文件;链接指向 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
我选择了最快的编写实现,所以它只是从另一个表面转发支持的像素格式(我的目的是探测和回放到 a QVideoWidget
),你会得到你得到的任何格式。我只需要将子图像抓取到QImage
s 中,它可以处理最常见的格式。但是您可以修改它以强制使用您想要的任何格式(例如,您可能只想返回支持的格式QImage
或过滤掉不支持的源格式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
会让你做同样的事情,所以我不知道这是怎么回事——这是一个奇怪的 API。我用 sw、本机硬件和复制回硬件渲染器和解码器运行了一些测试,并且map
/unmap
在读取模式下似乎运行正常,所以,无论如何。
在性能方面,如果您在回调中花费太多时间,视频将陷入困境,因此请相应地进行设计。但是,我没有测试QueuedConnection
,所以我不知道这是否仍然存在问题(尽管信号参数是参考的事实会让我对尝试它持谨慎态度,以及 GPU 发布时可能出现的问题插槽最终被调用之前的内存)。我也不知道QVideoProbe
在这方面表现如何。我确实知道,至少在我的机器上,我可以将全高清 (1920 x 1080) 分辨率打包并排队QImage
到线程池进行处理,而不会减慢视频速度。
您可能还想为异常安全unmap()
等实现某种自动取消映射实用程序对象。但同样,这不是唯一的,您必须与QVideoProbe
.
所以希望这对其他人有帮助。
示例 QImage 使用
PS,将任意格式QVideoFrame
的s打包成QImage
in的例子:
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
and之间在技术上有效unmap
,因此如果您打算使用该QImage
范围的外部,您将希望使用copy()
(或任何其他强制分离的东西)来强制进行深层复制。
- 您可能还想确保
QImage
在调用unmap
. 这不太可能引起问题,但在任何给定时间尽量减少挂起的无效指针的数量总是一个好主意,而且QImage
文档还说“缓冲区必须在 QImage 的整个生命周期以及所有未修改的副本中保持有效或以其他方式与原始缓冲区分离”。最好严格一点。