Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qgstvideobuffer_p.h"
#include <private/qvideotexturehelper_p.h>
#include <QtCore/qloggingcategory.h>
#include <gst/video/video.h>
#include <gst/video/video-frame.h>
#include <gst/pbutils/gstpluginsbaseversion.h>
#include <common/qgstutils_p.h>
#if QT_CONFIG(gstreamer_gl)
# include <QtGui/rhi/qrhi.h>
# include <QtGui/qopenglcontext.h>
# include <QtGui/qopenglfunctions.h>
# include <QtGui/qopengl.h>
# include <gst/gl/gstglconfig.h>
# include <gst/gl/gstglmemory.h>
# include <gst/gl/gstglsyncmeta.h>
#endif
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(qLcGstVideoBuffer, "qt.multimedia.gstreamer.videobuffer")
QGstVideoBuffer::QGstVideoBuffer(QGstBufferHandle buffer, const QGstVideoInfo &videoInfo,
const QVideoFrameFormat &frameFormat)
: m_memoryFormat(buffer.isValid() ? qMemoryFormatFromGstBuffer(buffer.get())
: QGstCaps::CpuMemory),
m_frameFormat(frameFormat),
m_videoInfo(videoInfo),
m_buffer(std::move(buffer))
{
if constexpr (QT_CONFIG(linux_dmabuf))
Q_ASSERT(m_memoryFormat != QGstCaps::DMABuf
&& "DMABuf buffers should use QGstDmaBufVideoBuffer");
}
QGstVideoBuffer::~QGstVideoBuffer()
{
Q_ASSERT(m_mode == QVideoFrame::NotMapped);
}
QAbstractVideoBuffer::MapData QGstVideoBuffer::map(QVideoFrame::MapMode mode)
{
const GstMapFlags flags = GstMapFlags(((mode & QVideoFrame::ReadOnly) ? GST_MAP_READ : 0)
| ((mode & QVideoFrame::WriteOnly) ? GST_MAP_WRITE : 0));
MapData mapData;
if (mode == QVideoFrame::NotMapped || m_mode != QVideoFrame::NotMapped)
return mapData;
const GstVideoInfo &gstVideoInfo = m_videoInfo.gstVideoInfo;
if (!gstVideoInfo.finfo || gstVideoInfo.finfo->n_planes == 0) { // Encoded
if (gst_buffer_map(m_buffer.get(), &m_frame.map[0], flags)) {
mapData.planeCount = 1;
mapData.bytesPerLine[0] = -1;
mapData.dataSize[0] = m_frame.map[0].size;
mapData.data[0] = static_cast<uchar *>(m_frame.map[0].data);
m_mode = mode;
}
} else if (gst_video_frame_map(&m_frame, &gstVideoInfo, m_buffer.get(), flags)) {
mapData.planeCount = GST_VIDEO_FRAME_N_PLANES(&m_frame);
for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES(&m_frame); ++i) {
mapData.bytesPerLine[i] = GST_VIDEO_FRAME_PLANE_STRIDE(&m_frame, i);
mapData.data[i] = static_cast<uchar *>(GST_VIDEO_FRAME_PLANE_DATA(&m_frame, i));
mapData.dataSize[i] = mapData.bytesPerLine[i]*GST_VIDEO_FRAME_COMP_HEIGHT(&m_frame, i);
}
m_mode = mode;
}
return mapData;
}
void QGstVideoBuffer::unmap()
{
if (m_mode != QVideoFrame::NotMapped) {
if (!m_videoInfo.gstVideoInfo.finfo || m_videoInfo.gstVideoInfo.finfo->n_planes == 0)
gst_buffer_unmap(m_buffer.get(), &m_frame.map[0]);
else
gst_video_frame_unmap(&m_frame);
}
m_mode = QVideoFrame::NotMapped;
}
#if QT_CONFIG(gstreamer_gl)
struct GlTextures
{
uint count = 0;
bool owned = false;
std::array<guint32, QVideoTextureHelper::TextureDescription::maxPlanes> names{};
};
class QGstQVideoFrameTextures : public QVideoFrameTextures
{
public:
QGstQVideoFrameTextures(QRhi *rhi, QSize size, QVideoFrameFormat::PixelFormat format,
GlTextures &textures)
: m_rhi(rhi), m_glTextures(textures)
{
QRhiTexture::Flags textureFlags = {};
if (QVideoTextureHelper::forceGlTextureExternalOesIsSet() && m_rhi
&& rhi->backend() == QRhi::OpenGLES2)
textureFlags = {QRhiTexture::ExternalOES};
auto desc = QVideoTextureHelper::textureDescription(format);
for (uint i = 0; i < textures.count; ++i) {
QSize planeSize = desc->rhiPlaneSize(size, i, m_rhi);
QRhiTexture::Format format = desc->rhiTextureFormat(i, m_rhi);
m_textures[i].reset(rhi->newTexture(format, planeSize, 1, textureFlags));
m_textures[i]->createFrom({textures.names[i], 0});
}
}
~QGstQVideoFrameTextures() override
{
m_rhi->makeThreadLocalNativeContextCurrent();
auto ctx = QOpenGLContext::currentContext();
if (m_glTextures.owned && ctx)
ctx->functions()->glDeleteTextures(int(m_glTextures.count), m_glTextures.names.data());
}
QRhiTexture *texture(uint plane) const override
{
return plane < m_glTextures.count ? m_textures[plane].get() : nullptr;
}
private:
QRhi *m_rhi = nullptr;
GlTextures m_glTextures;
std::unique_ptr<QRhiTexture> m_textures[QVideoTextureHelper::TextureDescription::maxPlanes];
};
static GlTextures mapFromGlTexture(const QGstBufferHandle &bufferHandle, GstVideoFrame &frame,
GstVideoInfo &videoInfo)
{
qCDebug(qLcGstVideoBuffer) << "Mapping textures from GL memory";
GstBuffer *buffer = bufferHandle.get();
auto *mem = GST_GL_BASE_MEMORY_CAST(gst_buffer_peek_memory(buffer, 0));
if (!mem)
return {};
if (!gst_video_frame_map(&frame, &videoInfo, buffer, GstMapFlags(GST_MAP_READ|GST_MAP_GL))) {
qWarning() << "Could not map GL textures";
return {};
}
auto *sync_meta = gst_buffer_get_gl_sync_meta(buffer);
GstBuffer *sync_buffer = nullptr;
if (!sync_meta) {
sync_buffer = gst_buffer_new();
sync_meta = gst_buffer_add_gl_sync_meta(mem->context, sync_buffer);
}
gst_gl_sync_meta_set_sync_point (sync_meta, mem->context);
gst_gl_sync_meta_wait (sync_meta, mem->context);
if (sync_buffer)
gst_buffer_unref(sync_buffer);
GlTextures textures;
textures.count = frame.info.finfo->n_planes;
for (uint i = 0; i < textures.count; ++i)
textures.names[i] = *(guint32 *)frame.data[i];
gst_video_frame_unmap(&frame);
return textures;
}
#endif
QVideoFrameTexturesUPtr QGstVideoBuffer::mapTextures([[maybe_unused]] QRhi &rhi)
{
switch (m_memoryFormat) {
case QGstCaps::GLTexture:
#if QT_CONFIG(gstreamer_gl)
{
GlTextures textures = mapFromGlTexture(m_buffer, m_frame, m_videoInfo.gstVideoInfo);
if (textures.count > 0)
return std::make_unique<QGstQVideoFrameTextures>(
&rhi, QSize{ m_videoInfo.gstVideoInfo.width, m_videoInfo.gstVideoInfo.height },
m_frameFormat.pixelFormat(), textures);
[[fallthrough]];
}
#endif
default:
return {};
}
}
QT_END_NAMESPACE