Revision control
++ // Copyright (C) 2024 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 <common/qgst_p.h>
++
++ #include <common/qgst_debug_p.h>
++ #include <common/qgstpipeline_p.h>
++ #include <common/qgstreamermessage_p.h>
++ #include <common/qgstutils_p.h>
++ #include <common/qgstvideobuffer_p.h>
++
++ #if QT_CONFIG(linux_dmabuf)
++ # include <common/qgstdmabufvideobuffer_p.h>
++ #endif
++
++ #include <QtCore/qdebug.h>
++ #include <QtMultimedia/private/qvideoframe_p.h>
++ #include <QtMultimedia/qcameradevice.h>
++
++ #if QT_CONFIG(gstreamer_gl)
++ # include <gst/gl/gstglmemory.h>
++ #endif
++
++ #include <array>
++ #include <thread>
++
++ // DMA support
++ #if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
++ # include <gst/allocators/gstdmabuf.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ struct VideoFormat
++ {
++ QVideoFrameFormat::PixelFormat pixelFormat;
++ GstVideoFormat gstFormat;
++ };
++
++ constexpr std::array<VideoFormat, 19> qt_videoFormatLookup{ {
++ { QVideoFrameFormat::Format_YUV420P, GST_VIDEO_FORMAT_I420 },
++ { QVideoFrameFormat::Format_YUV422P, GST_VIDEO_FORMAT_Y42B },
++ { QVideoFrameFormat::Format_YV12, GST_VIDEO_FORMAT_YV12 },
++ { QVideoFrameFormat::Format_UYVY, GST_VIDEO_FORMAT_UYVY },
++ { QVideoFrameFormat::Format_YUYV, GST_VIDEO_FORMAT_YUY2 },
++ { QVideoFrameFormat::Format_NV12, GST_VIDEO_FORMAT_NV12 },
++ { QVideoFrameFormat::Format_NV21, GST_VIDEO_FORMAT_NV21 },
++ { QVideoFrameFormat::Format_AYUV, GST_VIDEO_FORMAT_AYUV },
++ { QVideoFrameFormat::Format_Y8, GST_VIDEO_FORMAT_GRAY8 },
++ { QVideoFrameFormat::Format_XRGB8888, GST_VIDEO_FORMAT_xRGB },
++ { QVideoFrameFormat::Format_XBGR8888, GST_VIDEO_FORMAT_xBGR },
++ { QVideoFrameFormat::Format_RGBX8888, GST_VIDEO_FORMAT_RGBx },
++ { QVideoFrameFormat::Format_BGRX8888, GST_VIDEO_FORMAT_BGRx },
++ { QVideoFrameFormat::Format_ARGB8888, GST_VIDEO_FORMAT_ARGB },
++ { QVideoFrameFormat::Format_ABGR8888, GST_VIDEO_FORMAT_ABGR },
++ { QVideoFrameFormat::Format_RGBA8888, GST_VIDEO_FORMAT_RGBA },
++ { QVideoFrameFormat::Format_BGRA8888, GST_VIDEO_FORMAT_BGRA },
++ #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
++ { QVideoFrameFormat::Format_Y16, GST_VIDEO_FORMAT_GRAY16_LE },
++ { QVideoFrameFormat::Format_P010, GST_VIDEO_FORMAT_P010_10LE },
++ #else
++ { QVideoFrameFormat::Format_Y16, GST_VIDEO_FORMAT_GRAY16_BE },
++ { QVideoFrameFormat::Format_P010, GST_VIDEO_FORMAT_P010_10BE },
++ #endif
++ } };
++
++ #if QT_GSTREAMER_SUPPORTS_GST_VIDEO_FORMAT_DMA_DRM
++ void appendDmaDrmPixelFormats(QGstCaps &caps, const QList<QVideoFrameFormat::PixelFormat> &formats)
++ {
++ GValue drmFormatList = {};
++ g_value_init(&drmFormatList, GST_TYPE_LIST);
++
++ for (QVideoFrameFormat::PixelFormat format : formats) {
++ const GstVideoFormat gstFormat = qGstVideoFormatFromPixelFormat(format);
++ if (gstFormat == GST_VIDEO_FORMAT_UNKNOWN)
++ continue;
++
++ const guint32 fourcc =
++ gst_video_dma_drm_fourcc_from_format(gstFormat);
++ if (!fourcc)
++ continue;
++
++ GValue drmFormat = {};
++ g_value_init(&drmFormat, G_TYPE_STRING);
++ g_value_take_string(&drmFormat, gst_video_dma_drm_fourcc_to_string(fourcc, 0));
++ gst_value_list_append_value(&drmFormatList, &drmFormat);
++ g_value_unset(&drmFormat);
++ }
++
++ QGstStructure dmaDrmStructure{ "video/x-raw" };
++ dmaDrmStructure.setString("format", gst_video_format_to_string(GST_VIDEO_FORMAT_DMA_DRM));
++ dmaDrmStructure.setFractionRange("framerate", Fraction{ 0, 1 }, Fraction{ INT_MAX, 1 });
++ dmaDrmStructure.setIntRange("width", 1, INT_MAX);
++ dmaDrmStructure.setIntRange("height", 1, INT_MAX);
++ dmaDrmStructure.setValue("drm-format", &drmFormatList);
++
++ gst_caps_append_structure(caps.get(), dmaDrmStructure.release());
++ gst_caps_set_features(caps.get(), caps.size() - 1,
++ gst_caps_features_from_string(GST_CAPS_FEATURE_MEMORY_DMABUF));
++ g_value_unset(&drmFormatList);
++ }
++ #endif
++
++ } // namespace
++
++ GstVideoFormat qGstVideoFormatFromPixelFormat(QVideoFrameFormat::PixelFormat format)
++ {
++ for (const VideoFormat &formatPair : qt_videoFormatLookup)
++ if (formatPair.pixelFormat == format)
++ return formatPair.gstFormat;
++
++ return GST_VIDEO_FORMAT_UNKNOWN;
++ }
++
++ QVideoFrameFormat::PixelFormat qPixelFormatFromGstVideoFormat(GstVideoFormat format)
++ {
++ for (const VideoFormat &formatPair : qt_videoFormatLookup)
++ if (formatPair.gstFormat == format)
++ return formatPair.pixelFormat;
++
++ return QVideoFrameFormat::Format_Invalid;
++ }
++
++ // QGValue
++
++ QGValue::QGValue(const GValue *v) : value(v) { }
++
++ bool QGValue::isNull() const
++ {
++ return !value;
++ }
++
++ std::optional<bool> QGValue::toBool() const
++ {
++ if (!G_VALUE_HOLDS_BOOLEAN(value))
++ return std::nullopt;
++ return g_value_get_boolean(value);
++ }
++
++ std::optional<int> QGValue::toInt() const
++ {
++ if (!G_VALUE_HOLDS_INT(value))
++ return std::nullopt;
++ return g_value_get_int(value);
++ }
++
++ std::optional<int> QGValue::toInt64() const
++ {
++ if (!G_VALUE_HOLDS_INT64(value))
++ return std::nullopt;
++ return g_value_get_int64(value);
++ }
++
++ const char *QGValue::toString() const
++ {
++ return value ? g_value_get_string(value) : nullptr;
++ }
++
++ std::optional<float> QGValue::getFraction() const
++ {
++ if (!GST_VALUE_HOLDS_FRACTION(value))
++ return std::nullopt;
++ return (float)gst_value_get_fraction_numerator(value)
++ / (float)gst_value_get_fraction_denominator(value);
++ }
++
++ std::optional<QGRange<float>> QGValue::getFractionRange() const
++ {
++ if (!GST_VALUE_HOLDS_FRACTION_RANGE(value))
++ return std::nullopt;
++ QGValue min = QGValue{ gst_value_get_fraction_range_min(value) };
++ QGValue max = QGValue{ gst_value_get_fraction_range_max(value) };
++ return QGRange<float>{ *min.getFraction(), *max.getFraction() };
++ }
++
++ std::optional<QGRange<int>> QGValue::toIntRange() const
++ {
++ if (!GST_VALUE_HOLDS_INT_RANGE(value))
++ return std::nullopt;
++ return QGRange<int>{ gst_value_get_int_range_min(value), gst_value_get_int_range_max(value) };
++ }
++
++ QGstStructureView QGValue::toStructure() const
++ {
++ if (!value || !GST_VALUE_HOLDS_STRUCTURE(value))
++ return QGstStructureView(nullptr);
++ return QGstStructureView(gst_value_get_structure(value));
++ }
++
++ QGstCaps QGValue::toCaps() const
++ {
++ if (!value || !GST_VALUE_HOLDS_CAPS(value))
++ return {};
++ return QGstCaps(gst_caps_copy(gst_value_get_caps(value)), QGstCaps::HasRef);
++ }
++
++ bool QGValue::isList() const
++ {
++ return value && GST_VALUE_HOLDS_LIST(value);
++ }
++
++ int QGValue::listSize() const
++ {
++ return gst_value_list_get_size(value);
++ }
++
++ QGValue QGValue::at(int index) const
++ {
++ return QGValue{ gst_value_list_get_value(value, index) };
++ }
++
++ // QGstStructureView
++
++ QGstStructureView::QGstStructureView(const GstStructure *s) : structure(s) { }
++
++ QGstStructureView::QGstStructureView(const QUniqueGstStructureHandle &handle)
++ : QGstStructureView{ handle.get() }
++ {
++ }
++
++ QUniqueGstStructureHandle QGstStructureView::clone() const
++ {
++ return QUniqueGstStructureHandle{ gst_structure_copy(structure) };
++ }
++
++ bool QGstStructureView::isNull() const
++ {
++ return !structure;
++ }
++
++ QByteArrayView QGstStructureView::name() const
++ {
++ return gst_structure_get_name(structure);
++ }
++
++ QGValue QGstStructureView::operator[](const char *fieldname) const
++ {
++ return QGValue{ gst_structure_get_value(structure, fieldname) };
++ }
++
++ QGstCaps QGstStructureView::caps() const
++ {
++ return operator[]("caps").toCaps();
++ }
++
++ QGstTagListHandle QGstStructureView::tags() const
++ {
++ QGValue tags = operator[]("tags");
++ if (tags.isNull())
++ return {};
++
++ QGstTagListHandle tagList;
++ gst_structure_get(structure, "tags", GST_TYPE_TAG_LIST, &tagList, nullptr);
++ return tagList;
++ }
++
++ QSize QGstStructureView::resolution() const
++ {
++ QSize size;
++
++ int w, h;
++ if (structure && gst_structure_get_int(structure, "width", &w)
++ && gst_structure_get_int(structure, "height", &h)) {
++ size.rwidth() = w;
++ size.rheight() = h;
++ }
++
++ return size;
++ }
++
++ QList<QVideoFrameFormat::PixelFormat> QGstStructureView::pixelFormats() const
++ {
++ QList<QVideoFrameFormat::PixelFormat> pixelFormats;
++
++ if (!structure)
++ return pixelFormats;
++
++ auto appendFromGstVideoFormat = [&](GstVideoFormat format) {
++ const QVideoFrameFormat::PixelFormat pixelFormat = qPixelFormatFromGstVideoFormat(format);
++ if (pixelFormat == QVideoFrameFormat::Format_Invalid)
++ return;
++
++ if (!pixelFormats.contains(pixelFormat))
++ pixelFormats.append(pixelFormat);
++ };
++
++ if (gst_structure_has_name(structure, "video/x-raw")) {
++ #if QT_GSTREAMER_SUPPORTS_GST_VIDEO_FORMAT_DMA_DRM
++ if (const GValue *drmFormatValue = gst_structure_get_value(structure, "drm-format")) {
++ auto parseDrmFormat = [&](const GValue *value) {
++ if (!value || !G_VALUE_HOLDS_STRING(value))
++ return;
++
++ const char *drmFormatString = g_value_get_string(value);
++ if (!drmFormatString)
++ return;
++
++ guint64 modifier = 0;
++ const guint32 fourcc = gst_video_dma_drm_fourcc_from_string(drmFormatString,
++ &modifier);
++ if (fourcc) {
++ if (modifier != 0)
++ qWarning() << "Ignoring drm-format modifier when mapping to Qt PixelFormat:"
++ << drmFormatString;
++ appendFromGstVideoFormat(gst_video_dma_drm_fourcc_to_format(fourcc));
++ }
++ };
++
++ if (GST_VALUE_HOLDS_LIST(drmFormatValue)) {
++ const guint listSize = gst_value_list_get_size(drmFormatValue);
++ for (guint i = 0; i < listSize; ++i)
++ parseDrmFormat(gst_value_list_get_value(drmFormatValue, i));
++ } else {
++ parseDrmFormat(drmFormatValue);
++ }
++
++ if (!pixelFormats.isEmpty())
++ return pixelFormats; // Skip checking "format" field when "drm-format" succeeds
++ }
++ #endif
++ if (const GValue *formatValue = gst_structure_get_value(structure, "format")) {
++ auto parseFormat = [&](const GValue *value) {
++ if (!value || !G_VALUE_HOLDS_STRING(value))
++ return;
++ const char *formatString = g_value_get_string(value);
++ if (!formatString)
++ return;
++ appendFromGstVideoFormat(gst_video_format_from_string(formatString));
++ };
++
++ if (GST_VALUE_HOLDS_LIST(formatValue)) {
++ const guint listSize = gst_value_list_get_size(formatValue);
++ for (guint i = 0; i < listSize; ++i)
++ parseFormat(gst_value_list_get_value(formatValue, i));
++ } else {
++ parseFormat(formatValue);
++ }
++ }
++ } else if (gst_structure_has_name(structure, "image/jpeg")) {
++ pixelFormats.append(QVideoFrameFormat::Format_Jpeg);
++ }
++
++ return pixelFormats;
++ }
++
++ QGRange<float> QGstStructureView::frameRateRange() const
++ {
++ if (!structure)
++ return { 0.f, 0.f };
++
++ std::optional<float> minRate;
++ std::optional<float> maxRate;
++
++ auto extractFraction = [](const GValue *v) -> float {
++ return (float)gst_value_get_fraction_numerator(v)
++ / (float)gst_value_get_fraction_denominator(v);
++ };
++ auto extractFrameRate = [&](const GValue *v) {
++ auto insert = [&](float min, float max) {
++ if (!maxRate || max > maxRate)
++ maxRate = max;
++ if (!minRate || min < minRate)
++ minRate = min;
++ };
++
++ if (GST_VALUE_HOLDS_FRACTION(v)) {
++ float rate = extractFraction(v);
++ insert(rate, rate);
++ } else if (GST_VALUE_HOLDS_FRACTION_RANGE(v)) {
++ const GValue *min = gst_value_get_fraction_range_min(v);
++ const GValue *max = gst_value_get_fraction_range_max(v);
++ insert(extractFraction(min), extractFraction(max));
++ }
++ };
++
++ const GValue *gstFrameRates = gst_structure_get_value(structure, "framerate");
++ if (gstFrameRates) {
++ if (GST_VALUE_HOLDS_LIST(gstFrameRates)) {
++ guint nFrameRates = gst_value_list_get_size(gstFrameRates);
++ for (guint f = 0; f < nFrameRates; ++f) {
++ extractFrameRate(gst_value_list_get_value(gstFrameRates, f));
++ }
++ } else {
++ extractFrameRate(gstFrameRates);
++ }
++ } else {
++ const GValue *min = gst_structure_get_value(structure, "min-framerate");
++ const GValue *max = gst_structure_get_value(structure, "max-framerate");
++ if (min && max) {
++ minRate = extractFraction(min);
++ maxRate = extractFraction(max);
++ }
++ }
++
++ if (!minRate || !maxRate)
++ return { 0.f, 0.f };
++
++ return {
++ minRate.value_or(*maxRate),
++ maxRate.value_or(*minRate),
++ };
++ }
++
++ std::optional<QGRange<QSize>> QGstStructureView::resolutionRange() const
++ {
++ if (!structure)
++ return std::nullopt;
++
++ const GValue *width = gst_structure_get_value(structure, "width");
++ const GValue *height = gst_structure_get_value(structure, "height");
++
++ if (!width || !height)
++ return std::nullopt;
++
++ for (const GValue *v : { width, height })
++ if (!GST_VALUE_HOLDS_INT_RANGE(v))
++ return std::nullopt;
++
++ int minWidth = gst_value_get_int_range_min(width);
++ int maxWidth = gst_value_get_int_range_max(width);
++ int minHeight = gst_value_get_int_range_min(height);
++ int maxHeight = gst_value_get_int_range_max(height);
++
++ return QGRange<QSize>{
++ QSize(minWidth, minHeight),
++ QSize(maxWidth, maxHeight),
++ };
++ }
++
++ QGstreamerMessage QGstStructureView::getMessage()
++ {
++ GstMessage *message = nullptr;
++ gst_structure_get(structure, "message", GST_TYPE_MESSAGE, &message, nullptr);
++ return QGstreamerMessage(message, QGstreamerMessage::HasRef);
++ }
++
++ std::optional<Fraction> QGstStructureView::pixelAspectRatio() const
++ {
++ gint numerator;
++ gint denominator;
++ if (gst_structure_get_fraction(structure, "pixel-aspect-ratio", &numerator, &denominator)) {
++ return Fraction{
++ numerator,
++ denominator,
++ };
++ }
++
++ return std::nullopt;
++ }
++
++ QSize QGstStructureView::nativeSize() const
++ {
++ QSize size = resolution();
++ if (!size.isValid()) {
++ qWarning() << Q_FUNC_INFO << "invalid resolution when querying nativeSize";
++ return size;
++ }
++
++ std::optional<Fraction> par = pixelAspectRatio();
++ if (par)
++ size = QGstUtils::qCalculateFrameSizeGStreamer(size, *par);
++ return size;
++ }
++
++ // QGstCaps
++
++ std::optional<QGstVideoInfo> QGstCaps::videoInfo() const
++ {
++ GstVideoInfo vidInfo;
++ std::optional<guint64> dmaDrmModifier;
++
++ #if QT_GSTREAMER_SUPPORTS_GST_VIDEO_FORMAT_DMA_DRM
++ GstVideoInfoDmaDrm videoInfoDmaDrm;
++ if (gst_video_info_dma_drm_from_caps(&videoInfoDmaDrm, get())) {
++ dmaDrmModifier = videoInfoDmaDrm.drm_modifier;
++ // Sets vidInfo‘s format based on drm_fourcc from videoInfoDmaDrm:
++ if (!gst_video_info_dma_drm_to_video_info(&videoInfoDmaDrm, &vidInfo))
++ return std::nullopt;
++ } else
++ #endif
++ {
++ if (!gst_video_info_from_caps(&vidInfo, get()))
++ return std::nullopt;
++ }
++
++ return QGstVideoInfo{vidInfo, dmaDrmModifier};
++ }
++
++ void QGstCaps::addPixelFormats(const QList<QVideoFrameFormat::PixelFormat> &formats,
++ const char *capsFeatures)
++ {
++ if (!gst_caps_is_writable(get()))
++ *this = QGstCaps(gst_caps_make_writable(release()), QGstCaps::RefMode::HasRef);
++
++ #if QT_GSTREAMER_SUPPORTS_GST_VIDEO_FORMAT_DMA_DRM
++ if (qstrcmp(capsFeatures, GST_CAPS_FEATURE_MEMORY_DMABUF) == 0)
++ appendDmaDrmPixelFormats(*this, formats);
++ #endif
++
++ GValue list = {};
++ g_value_init(&list, GST_TYPE_LIST);
++
++ for (QVideoFrameFormat::PixelFormat format : formats) {
++ const GstVideoFormat gstFormat = qGstVideoFormatFromPixelFormat(format);
++ if (gstFormat == GST_VIDEO_FORMAT_UNKNOWN)
++ continue;
++ GValue item = {};
++
++ g_value_init(&item, G_TYPE_STRING);
++ g_value_set_string(&item, gst_video_format_to_string(gstFormat));
++ gst_value_list_append_value(&list, &item);
++ g_value_unset(&item);
++ }
++
++ QGstStructure structure{ "video/x-raw" };
++ structure.setFractionRange("framerate", Fraction{ 0, 1 }, Fraction{ INT_MAX, 1 });
++ structure.setIntRange("width", 1, INT_MAX);
++ structure.setIntRange("height", 1, INT_MAX);
++ structure.setValue("format", &list);
++
++ gst_caps_append_structure(get(), structure.release());
++ g_value_unset(&list);
++
++ if (capsFeatures)
++ gst_caps_set_features(get(), size() - 1,
++ gst_caps_features_from_string(capsFeatures));
++ }
++
++ void QGstCaps::setResolution(QSize resolution)
++ {
++ Q_ASSERT(resolution.isValid());
++ GValue width{};
++ g_value_init(&width, G_TYPE_INT);
++ g_value_set_int(&width, resolution.width());
++ GValue height{};
++ g_value_init(&height, G_TYPE_INT);
++ g_value_set_int(&height, resolution.height());
++
++ gst_caps_set_value(caps(), "width", &width);
++ gst_caps_set_value(caps(), "height", &height);
++ }
++
++ QGstCaps QGstCaps::fromCameraFormat(const QCameraFormat &format)
++ {
++ QSize size = format.resolution();
++ auto caps = QGstCaps::create();
++
++ if (format.pixelFormat() == QVideoFrameFormat::Format_Jpeg) {
++ QGstStructure jpegStructure("image/jpeg");
++ jpegStructure.setInt("width", size.width());
++ jpegStructure.setInt("height", size.height());
++
++ gst_caps_append_structure(caps.get(), jpegStructure.release());
++ return caps;
++ }
++
++ const GstVideoFormat gstFormat = qGstVideoFormatFromPixelFormat(format.pixelFormat());
++ if (gstFormat == GST_VIDEO_FORMAT_UNKNOWN)
++ return {};
++
++ QGstStructure rawStructure("video/x-raw");
++ rawStructure.setString("format", gst_video_format_to_string(gstFormat));
++ rawStructure.setInt("width", size.width());
++ rawStructure.setInt("height", size.height());
++
++ gst_caps_append_structure(caps.get(), rawStructure.release());
++
++ #if QT_GSTREAMER_SUPPORTS_GST_VIDEO_FORMAT_DMA_DRM
++ if (const guint32 fourcc = gst_video_dma_drm_fourcc_from_format(gstFormat)) {
++ if (QGString drmFormat{gst_video_dma_drm_fourcc_to_string(fourcc, 0)}) {
++ QGstStructure drmFormatDmabufRawStructure("video/x-raw");
++ drmFormatDmabufRawStructure.setString(
++ "format", gst_video_format_to_string(GST_VIDEO_FORMAT_DMA_DRM));
++ drmFormatDmabufRawStructure.setString("drm-format", drmFormat.get());
++ drmFormatDmabufRawStructure.setInt("width", size.width());
++ drmFormatDmabufRawStructure.setInt("height", size.height());
++
++ gst_caps_append_structure(caps.get(), drmFormatDmabufRawStructure.release());
++ gst_caps_set_features(
++ caps.get(), caps.size() - 1,
++ gst_caps_features_from_string(GST_CAPS_FEATURE_MEMORY_DMABUF));
++ }
++ }
++ #endif
++
++ #if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
++ QGstStructure dmabufRawStructure("video/x-raw");
++ dmabufRawStructure.setString("format", gst_video_format_to_string(gstFormat));
++ dmabufRawStructure.setInt("width", size.width());
++ dmabufRawStructure.setInt("height", size.height());
++
++ gst_caps_append_structure(caps.get(), dmabufRawStructure.release());
++ gst_caps_set_features(caps.get(), caps.size() - 1,
++ gst_caps_features_from_string(GST_CAPS_FEATURE_MEMORY_DMABUF));
++ #endif
++
++ return caps;
++ }
++
++ QGstCaps QGstCaps::copy() const
++ {
++ return QGstCaps{
++ gst_caps_copy(caps()),
++ QGstCaps::HasRef,
++ };
++ }
++
++ QGstCaps::MemoryFormat QGstCaps::memoryFormat() const
++ {
++ auto *features = gst_caps_get_features(get(), 0);
++ if (gst_caps_features_contains(features, "memory:GLMemory"))
++ return GLTexture;
++ if (gst_caps_features_contains(features, "memory:DMABuf"))
++ return DMABuf;
++ return CpuMemory;
++ }
++
++ int QGstCaps::size() const
++ {
++ return int(gst_caps_get_size(get()));
++ }
++
++ QGstStructureView QGstCaps::at(int index) const
++ {
++ return QGstStructureView{
++ gst_caps_get_structure(get(), index),
++ };
++ }
++
++ GstCaps *QGstCaps::caps() const
++ {
++ return get();
++ }
++
++ QGstCaps QGstCaps::create()
++ {
++ return QGstCaps(gst_caps_new_empty(), HasRef);
++ }
++
++ // QGstObject
++
++ void QGstObject::set(const char *property, const char *str)
++ {
++ g_object_set(get(), property, str, nullptr);
++ }
++
++ void QGstObject::set(const char *property, bool b)
++ {
++ g_object_set(get(), property, gboolean(b), nullptr);
++ }
++
++ void QGstObject::set(const char *property, uint32_t i)
++ {
++ g_object_set(get(), property, guint(i), nullptr);
++ }
++
++ void QGstObject::set(const char *property, int32_t i)
++ {
++ g_object_set(get(), property, gint(i), nullptr);
++ }
++
++ void QGstObject::set(const char *property, int64_t i)
++ {
++ g_object_set(get(), property, gint64(i), nullptr);
++ }
++
++ void QGstObject::set(const char *property, uint64_t i)
++ {
++ g_object_set(get(), property, guint64(i), nullptr);
++ }
++
++ void QGstObject::set(const char *property, double d)
++ {
++ g_object_set(get(), property, gdouble(d), nullptr);
++ }
++
++ void QGstObject::set(const char *property, const QGstObject &o)
++ {
++ g_object_set(get(), property, o.object(), nullptr);
++ }
++
++ void QGstObject::set(const char *property, const QGstCaps &c)
++ {
++ g_object_set(get(), property, c.caps(), nullptr);
++ }
++
++ void QGstObject::set(const char *property, void *object, GDestroyNotify destroyFunction)
++ {
++ g_object_set_data_full(qGstCheckedCast<GObject>(get()), property, object, destroyFunction);
++ }
++
++ QGString QGstObject::getString(const char *property) const
++ {
++ char *s = nullptr;
++ g_object_get(get(), property, &s, nullptr);
++ return QGString(s);
++ }
++
++ QGstStructureView QGstObject::getStructure(const char *property) const
++ {
++ GstStructure *s = nullptr;
++ g_object_get(get(), property, &s, nullptr);
++ return QGstStructureView(s);
++ }
++
++ bool QGstObject::getBool(const char *property) const
++ {
++ gboolean b = false;
++ g_object_get(get(), property, &b, nullptr);
++ return b;
++ }
++
++ uint QGstObject::getUInt(const char *property) const
++ {
++ guint i = 0;
++ g_object_get(get(), property, &i, nullptr);
++ return i;
++ }
++
++ int QGstObject::getInt(const char *property) const
++ {
++ gint i = 0;
++ g_object_get(get(), property, &i, nullptr);
++ return i;
++ }
++
++ quint64 QGstObject::getUInt64(const char *property) const
++ {
++ guint64 i = 0;
++ g_object_get(get(), property, &i, nullptr);
++ return i;
++ }
++
++ qint64 QGstObject::getInt64(const char *property) const
++ {
++ gint64 i = 0;
++ g_object_get(get(), property, &i, nullptr);
++ return i;
++ }
++
++ float QGstObject::getFloat(const char *property) const
++ {
++ gfloat d = 0;
++ g_object_get(get(), property, &d, nullptr);
++ return d;
++ }
++
++ double QGstObject::getDouble(const char *property) const
++ {
++ gdouble d = 0;
++ g_object_get(get(), property, &d, nullptr);
++ return d;
++ }
++
++ QGstObject QGstObject::getGstObject(const char *property) const
++ {
++ GstObject *o = nullptr;
++ g_object_get(get(), property, &o, nullptr);
++ return QGstObject(o, HasRef);
++ }
++
++ void *QGstObject::getObject(const char *property) const
++ {
++ return g_object_get_data(qGstCheckedCast<GObject>(get()), property);
++ }
++
++ QGObjectHandlerConnection QGstObject::connect(const char *name, GCallback callback,
++ gpointer userData)
++ {
++ return QGObjectHandlerConnection{
++ *this,
++ g_signal_connect(get(), name, callback, userData),
++ };
++ }
++
++ void QGstObject::disconnect(gulong handlerId)
++ {
++ g_signal_handler_disconnect(get(), handlerId);
++ }
++
++ GType QGstObject::type() const
++ {
++ return G_OBJECT_TYPE(get());
++ }
++
++ QLatin1StringView QGstObject::typeName() const
++ {
++ return QLatin1StringView{
++ g_type_name(type()),
++ };
++ }
++
++ GstObject *QGstObject::object() const
++ {
++ return get();
++ }
++
++ QLatin1StringView QGstObject::name() const
++ {
++ using namespace Qt::StringLiterals;
++
++ return get() ? QLatin1StringView{ GST_OBJECT_NAME(get()) } : "(null)"_L1;
++ }
++
++ // QGObjectHandlerConnection
++
++ QGObjectHandlerConnection::QGObjectHandlerConnection(QGstObject object, gulong handlerId)
++ : object{ std::move(object) }, handlerId{ handlerId }
++ {
++ }
++
++ void QGObjectHandlerConnection::disconnect()
++ {
++ if (!object)
++ return;
++
++ object.disconnect(handlerId);
++ object = {};
++ handlerId = invalidHandlerId;
++ }
++
++ // QGObjectHandlerScopedConnection
++
++ QGObjectHandlerScopedConnection::QGObjectHandlerScopedConnection(
++ QGObjectHandlerConnection connection)
++ : connection{
++ std::move(connection),
++ }
++ {
++ }
++
++ QGObjectHandlerScopedConnection::~QGObjectHandlerScopedConnection()
++ {
++ connection.disconnect();
++ }
++
++ void QGObjectHandlerScopedConnection::disconnect()
++ {
++ connection.disconnect();
++ }
++
++ // QGstPad
++
++ QGstPad::QGstPad(const QGstObject &o)
++ : QGstPad{
++ qGstSafeCast<GstPad>(o.object()),
++ QGstElement::NeedsRef,
++ }
++ {
++ }
++
++ QGstPad::QGstPad(GstPad *pad, RefMode mode)
++ : QGstObject{
++ qGstCheckedCast<GstObject>(pad),
++ mode,
++ }
++ {
++ }
++
++ QGstCaps QGstPad::currentCaps() const
++ {
++ return QGstCaps(gst_pad_get_current_caps(pad()), QGstCaps::HasRef);
++ }
++
++ QGstCaps QGstPad::queryCaps() const
++ {
++ return QGstCaps(gst_pad_query_caps(pad(), nullptr), QGstCaps::HasRef);
++ }
++
++ QGstTagListHandle QGstPad::tags() const
++ {
++ QGstTagListHandle tagList;
++ g_object_get(object(), "tags", &tagList, nullptr);
++ return tagList;
++ }
++
++ QGString QGstPad::streamId() const
++ {
++ return QGString{
++ gst_pad_get_stream_id(pad()),
++ };
++ }
++
++ std::optional<QPlatformMediaPlayer::TrackType> QGstPad::inferTrackTypeFromName() const
++ {
++ using namespace Qt::Literals;
++ using TrackType = QPlatformMediaPlayer::TrackType;
++
++ QLatin1StringView padName = name();
++
++ if (padName.startsWith("video_"_L1))
++ return TrackType::VideoStream;
++ if (padName.startsWith("audio_"_L1))
++ return TrackType::AudioStream;
++ if (padName.startsWith("text_"_L1))
++ return TrackType::SubtitleStream;
++
++ return std::nullopt;
++ }
++
++ bool QGstPad::isLinked() const
++ {
++ return gst_pad_is_linked(pad());
++ }
++
++ bool QGstPad::link(const QGstPad &sink) const
++ {
++ return gst_pad_link(pad(), sink.pad()) == GST_PAD_LINK_OK;
++ }
++
++ bool QGstPad::unlink(const QGstPad &sink) const
++ {
++ return gst_pad_unlink(pad(), sink.pad());
++ }
++
++ bool QGstPad::unlinkPeer() const
++ {
++ QGstPad peerPad = peer();
++ if (peerPad)
++ return GST_PAD_IS_SRC(pad()) ? unlink(peerPad) : peerPad.unlink(*this);
++
++ return true;
++ }
++
++ QGstPad QGstPad::peer() const
++ {
++ return QGstPad(gst_pad_get_peer(pad()), HasRef);
++ }
++
++ QGstElement QGstPad::parent() const
++ {
++ return QGstElement(gst_pad_get_parent_element(pad()), HasRef);
++ }
++
++ GstPad *QGstPad::pad() const
++ {
++ return qGstCheckedCast<GstPad>(object());
++ }
++
++ GstEvent *QGstPad::stickyEvent(GstEventType type)
++ {
++ return gst_pad_get_sticky_event(pad(), type, 0);
++ }
++
++ bool QGstPad::sendEvent(GstEvent *event)
++ {
++ return gst_pad_send_event(pad(), event);
++ }
++
++ void QGstPad::sendFlushStartStop(bool resetTime)
++ {
++ GstEvent *flushStart = gst_event_new_flush_start();
++ gboolean ret = sendEvent(flushStart);
++ if (!ret) {
++ qWarning("failed to send flush-start event");
++ return;
++ }
++
++ GstEvent *flushStop = gst_event_new_flush_stop(resetTime);
++ ret = sendEvent(flushStop);
++ if (!ret)
++ qWarning("failed to send flush-stop event");
++ }
++
++ void QGstPad::sendFlushIfPaused()
++ {
++ using namespace std::chrono_literals;
++
++ GstState state = parent().state(1s);
++
++ if (state != GST_STATE_PAUSED)
++ return;
++
++ sendFlushStartStop(/*resetTime=*/true);
++ }
++
++ // QGstClock
++
++ QGstClock::QGstClock(const QGstObject &o)
++ : QGstClock{
++ qGstSafeCast<GstClock>(o.object()),
++ QGstElement::NeedsRef,
++ }
++ {
++ }
++
++ QGstClock::QGstClock(GstClock *clock, RefMode mode)
++ : QGstObject{
++ qGstCheckedCast<GstObject>(clock),
++ mode,
++ }
++ {
++ }
++
++ GstClock *QGstClock::clock() const
++ {
++ return qGstCheckedCast<GstClock>(object());
++ }
++
++ GstClockTime QGstClock::time() const
++ {
++ return gst_clock_get_time(clock());
++ }
++
++ // QGstElement
++
++ QGstElement::QGstElement(GstElement *element, RefMode mode)
++ : QGstObject{
++ qGstCheckedCast<GstObject>(element),
++ mode,
++ }
++ {
++ }
++
++ QGstElement QGstElement::createFromFactory(const char *factory, const char *name)
++ {
++ GstElement *element = gst_element_factory_make(factory, name);
++
++ #ifndef QT_NO_DEBUG
++ if (!element) {
++ qWarning() << "Failed to make element" << name << "from factory" << factory;
++ return QGstElement{};
++ }
++ #endif
++
++ return QGstElement{
++ element,
++ NeedsRef,
++ };
++ }
++
++ QGstElement QGstElement::createFromFactory(GstElementFactory *factory, const char *name)
++ {
++ return QGstElement{
++ gst_element_factory_create(factory, name),
++ NeedsRef,
++ };
++ }
++
++ QGstElement QGstElement::createFromFactory(const QGstElementFactoryHandle &factory,
++ const char *name)
++ {
++ return createFromFactory(factory.get(), name);
++ }
++
++ QGstElement QGstElement::createFromDevice(const QGstDeviceHandle &device, const char *name)
++ {
++ return createFromDevice(device.get(), name);
++ }
++
++ QGstElement QGstElement::createFromDevice(GstDevice *device, const char *name)
++ {
++ return QGstElement{
++ gst_device_create_element(device, name),
++ QGstElement::NeedsRef,
++ };
++ }
++
++ QGstElement QGstElement::createFromPipelineDescription(const char *str)
++ {
++ QUniqueGErrorHandle error;
++ QGstElement element{
++ gst_parse_launch(str, &error),
++ QGstElement::NeedsRef,
++ };
++
++ if (error) // error does not mean that the element could not be constructed
++ qWarning() << "gst_parse_launch error:" << error;
++
++ return element;
++ }
++
++ QGstElement QGstElement::createFromPipelineDescription(const QByteArray &str)
++ {
++ return createFromPipelineDescription(str.constData());
++ }
++
++ QGstElementFactoryHandle QGstElement::findFactory(const char *name)
++ {
++ return QGstElementFactoryHandle{
++ gst_element_factory_find(name),
++ QGstElementFactoryHandle::HasRef,
++ };
++ }
++
++ QGstElementFactoryHandle QGstElement::findFactory(const QByteArray &name)
++ {
++ return findFactory(name.constData());
++ }
++
++ QByteArrayView QGstElement::factoryName() const
++ {
++ GstElementFactory *factory = gst_element_get_factory(element());
++ if (!factory)
++ return {};
++
++ const char *name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
++ return name ? QByteArrayView{name} : QByteArrayView{};
++ }
++
++ QGstPad QGstElement::staticPad(const char *name) const
++ {
++ return QGstPad(gst_element_get_static_pad(element(), name), HasRef);
++ }
++
++ QGstPad QGstElement::src() const
++ {
++ return staticPad("src");
++ }
++
++ QGstPad QGstElement::sink() const
++ {
++ return staticPad("sink");
++ }
++
++ QGstPad QGstElement::getRequestPad(const char *name) const
++ {
++ return QGstPad(gst_element_request_pad_simple(element(), name), HasRef);
++ }
++
++ void QGstElement::releaseRequestPad(const QGstPad &pad) const
++ {
++ return gst_element_release_request_pad(element(), pad.pad());
++ }
++
++ GstState QGstElement::state(std::chrono::nanoseconds timeout) const
++ {
++ using namespace std::chrono_literals;
++
++ GstState state;
++ GstStateChangeReturn change =
++ gst_element_get_state(element(), &state, nullptr, timeout.count());
++
++ if (Q_UNLIKELY(change == GST_STATE_CHANGE_ASYNC))
++ qWarning() << "QGstElement::state detected an asynchronous state change. Return value not "
++ "reliable";
++
++ return state;
++ }
++
++ GstStateChangeReturn QGstElement::setState(GstState state)
++ {
++ return gst_element_set_state(element(), state);
++ }
++
++ bool QGstElement::setStateSync(GstState state, std::chrono::nanoseconds timeout)
++ {
++ GstStateChangeReturn change = gst_element_set_state(element(), state);
++ if (change == GST_STATE_CHANGE_ASYNC)
++ change = gst_element_get_state(element(), nullptr, &state, timeout.count());
++
++ if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) {
++ qWarning() << "Could not change state of" << name() << "to" << state << change;
++ dumpPipelineGraph("setStateSyncFailure");
++ }
++ return change == GST_STATE_CHANGE_SUCCESS || change == GST_STATE_CHANGE_NO_PREROLL;
++ }
++
++ bool QGstElement::syncStateWithParent()
++ {
++ Q_ASSERT(element());
++ return gst_element_sync_state_with_parent(element()) == TRUE;
++ }
++
++ bool QGstElement::finishStateChange(std::chrono::nanoseconds timeout)
++ {
++ GstState state, pending;
++ GstStateChangeReturn change =
++ gst_element_get_state(element(), &state, &pending, timeout.count());
++
++ if (change != GST_STATE_CHANGE_SUCCESS && change != GST_STATE_CHANGE_NO_PREROLL) {
++ qWarning() << "Could not finish change state of" << name() << change << state << pending;
++ dumpPipelineGraph("finishStateChangeFailure");
++ }
++ return change == GST_STATE_CHANGE_SUCCESS;
++ }
++
++ bool QGstElement::hasAsyncStateChange(std::chrono::nanoseconds timeout) const
++ {
++ GstState state;
++ GstStateChangeReturn change =
++ gst_element_get_state(element(), &state, nullptr, timeout.count());
++ return change == GST_STATE_CHANGE_ASYNC;
++ }
++
++ bool QGstElement::waitForAsyncStateChangeComplete(std::chrono::nanoseconds timeout) const
++ {
++ using namespace std::chrono_literals;
++ for (;;) {
++ if (!hasAsyncStateChange())
++ return true;
++ timeout -= 10ms;
++ if (timeout < 0ms)
++ return false;
++ std::this_thread::sleep_for(10ms);
++ }
++ }
++
++ void QGstElement::lockState(bool locked)
++ {
++ gst_element_set_locked_state(element(), locked);
++ }
++
++ bool QGstElement::isStateLocked() const
++ {
++ return gst_element_is_locked_state(element());
++ }
++
++ void QGstElement::sendEvent(GstEvent *event) const
++ {
++ gst_element_send_event(element(), event);
++ }
++
++ void QGstElement::sendEos() const
++ {
++ sendEvent(gst_event_new_eos());
++ }
++
++ std::optional<std::chrono::nanoseconds> QGstElement::duration() const
++ {
++ gint64 d;
++ if (!gst_element_query_duration(element(), GST_FORMAT_TIME, &d)) {
++ qDebug() << "QGstElement: failed to query duration";
++ return std::nullopt;
++ }
++ return std::chrono::nanoseconds{ d };
++ }
++
++ std::optional<std::chrono::milliseconds> QGstElement::durationInMs() const
++ {
++ using namespace std::chrono;
++ auto dur = duration();
++ if (dur)
++ return round<milliseconds>(*dur);
++ return std::nullopt;
++ }
++
++ std::optional<std::chrono::nanoseconds> QGstElement::position() const
++ {
++ QGstQueryHandle &query = positionQuery();
++
++ gint64 pos;
++ if (gst_element_query(element(), query.get())) {
++ gst_query_parse_position(query.get(), nullptr, &pos);
++ return std::chrono::nanoseconds{ pos };
++ }
++
++ qDebug() << "QGstElement: failed to query position";
++ return std::nullopt;
++ }
++
++ std::optional<std::chrono::milliseconds> QGstElement::positionInMs() const
++ {
++ using namespace std::chrono;
++ auto pos = position();
++ if (pos)
++ return round<milliseconds>(*pos);
++ return std::nullopt;
++ }
++
++ std::optional<bool> QGstElement::canSeek() const
++ {
++ QGstQueryHandle query{
++ gst_query_new_seeking(GST_FORMAT_TIME),
++ QGstQueryHandle::HasRef,
++ };
++ gboolean canSeek = false;
++ gst_query_parse_seeking(query.get(), nullptr, &canSeek, nullptr, nullptr);
++
++ if (gst_element_query(element(), query.get())) {
++ gst_query_parse_seeking(query.get(), nullptr, &canSeek, nullptr, nullptr);
++ return canSeek;
++ }
++ return std::nullopt;
++ }
++
++ GstClockTime QGstElement::baseTime() const
++ {
++ return gst_element_get_base_time(element());
++ }
++
++ void QGstElement::setBaseTime(GstClockTime time) const
++ {
++ gst_element_set_base_time(element(), time);
++ }
++
++ GstElement *QGstElement::element() const
++ {
++ return GST_ELEMENT_CAST(get());
++ }
++
++ QGstElement QGstElement::getParent() const
++ {
++ return QGstElement{
++ qGstCheckedCast<GstElement>(gst_element_get_parent(object())),
++ QGstElement::HasRef,
++ };
++ }
++
++ QGstBin QGstElement::getParentBin() const
++ {
++ return QGstBin{
++ qGstCheckedCast<GstBin>(gst_element_get_parent(object())),
++ QGstElement::HasRef,
++ };
++ }
++
++ QGstBin QGstElement::getRootBin() const
++ {
++ QGstElement ancestor = *this;
++ for (;;) {
++ QGstElement greatAncestor = ancestor.getParent();
++ if (greatAncestor) {
++ ancestor = std::move(greatAncestor);
++ continue;
++ }
++ if (GST_IS_BIN(ancestor.element())) {
++ return QGstBin{
++ qGstSafeCast<GstBin>(ancestor.element()),
++ QGstBin::NeedsRef,
++ };
++ } else {
++ return QGstBin{};
++ }
++ }
++ }
++
++ QGstPipeline QGstElement::getPipeline() const
++ {
++ QGstBin rootBin = getRootBin();
++ if (GST_IS_PIPELINE(rootBin.get())) {
++ return QGstPipeline{
++ qGstSafeCast<GstPipeline>(rootBin.element()),
++ QGstPipeline::NeedsRef,
++ };
++ } else {
++ qWarning() << "QGstElement::getPipeline failed for element:" << *this;
++ return QGstPipeline{};
++ }
++ }
++
++ void QGstElement::removeFromParent()
++ {
++ if (QGstBin parent = getParentBin())
++ parent.remove(*this);
++ }
++
++ void QGstElement::dumpPipelineGraph(const char *filename) const
++ {
++ static const bool dumpEnabled = qEnvironmentVariableIsSet("GST_DEBUG_DUMP_DOT_DIR");
++ if (dumpEnabled) {
++ getRootBin().dumpGraph(filename);
++ }
++ }
++
++ QGstQueryHandle &QGstElement::positionQuery() const
++ {
++ if (Q_UNLIKELY(!m_positionQuery))
++ m_positionQuery = QGstQueryHandle{
++ gst_query_new_position(GST_FORMAT_TIME),
++ QGstQueryHandle::HasRef,
++ };
++
++ return m_positionQuery;
++ }
++
++ // QGstBin
++
++ QGstBin QGstBin::create(const char *name)
++ {
++ return QGstBin(gst_bin_new(name), NeedsRef);
++ }
++
++ QGstBin QGstBin::createFromFactory(const char *factory, const char *name)
++ {
++ QGstElement element = QGstElement::createFromFactory(factory, name);
++ Q_ASSERT(GST_IS_BIN(element.element()));
++ return QGstBin{
++ GST_BIN(element.release()),
++ RefMode::HasRef,
++ };
++ }
++
++ QGstBin QGstBin::createFromPipelineDescription(const QByteArray &pipelineDescription,
++ const char *name, bool ghostUnlinkedPads)
++ {
++ return createFromPipelineDescription(pipelineDescription.constData(), name, ghostUnlinkedPads);
++ }
++
++ QGstBin QGstBin::createFromPipelineDescription(const char *pipelineDescription, const char *name,
++ bool ghostUnlinkedPads)
++ {
++ QUniqueGErrorHandle error;
++
++ GstElement *element =
++ gst_parse_bin_from_description_full(pipelineDescription, ghostUnlinkedPads,
++ /*context=*/nullptr, GST_PARSE_FLAG_NONE, &error);
++
++ if (!element) {
++ qWarning() << "Failed to make element from pipeline description" << pipelineDescription
++ << error;
++ return QGstBin{};
++ }
++
++ if (name)
++ gst_element_set_name(element, name);
++
++ return QGstBin{
++ element,
++ NeedsRef,
++ };
++ }
++
++ QGstBin::QGstBin(GstBin *bin, RefMode mode)
++ : QGstElement{
++ qGstCheckedCast<GstElement>(bin),
++ mode,
++ }
++ {
++ }
++
++ GstBin *QGstBin::bin() const
++ {
++ return qGstCheckedCast<GstBin>(object());
++ }
++
++ void QGstBin::addGhostPad(const QGstElement &child, const char *name)
++ {
++ addGhostPad(name, child.staticPad(name));
++ }
++
++ void QGstBin::addGhostPad(const char *name, const QGstPad &pad)
++ {
++ gst_element_add_pad(element(), gst_ghost_pad_new(name, pad.pad()));
++ }
++
++ void QGstBin::addUnlinkedGhostPads(GstPadDirection direction)
++ {
++ Q_ASSERT(direction != GstPadDirection::GST_PAD_UNKNOWN);
++
++ for (;;) {
++ QGstPad unlinkedPad{
++ gst_bin_find_unlinked_pad(bin(), direction),
++ QGstPad::HasRef,
++ };
++
++ if (!unlinkedPad)
++ return;
++
++ addGhostPad(unlinkedPad.name().constData(), unlinkedPad);
++ }
++ }
++
++ bool QGstBin::syncChildrenState()
++ {
++ return gst_bin_sync_children_states(bin());
++ }
++
++ void QGstBin::dumpGraph(const char *fileNamePrefix, bool includeTimestamp) const
++ {
++ if (!get())
++ return;
++
++ if (includeTimestamp)
++ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin(), GST_DEBUG_GRAPH_SHOW_VERBOSE, fileNamePrefix);
++ else
++ GST_DEBUG_BIN_TO_DOT_FILE(bin(), GST_DEBUG_GRAPH_SHOW_VERBOSE, fileNamePrefix);
++ }
++
++ QGstElement QGstBin::findByName(const char *name)
++ {
++ return QGstElement{
++ gst_bin_get_by_name(bin(), name),
++ QGstElement::NeedsRef,
++ };
++ }
++
++ void QGstBin::recalculateLatency()
++ {
++ gst_bin_recalculate_latency(bin());
++ }
++
++ // QGstBaseSink
++
++ QGstBaseSink::QGstBaseSink(GstBaseSink *element, RefMode mode)
++ : QGstElement{
++ qGstCheckedCast<GstElement>(element),
++ mode,
++ }
++ {
++ }
++
++ void QGstBaseSink::setSync(bool arg)
++ {
++ gst_base_sink_set_sync(baseSink(), arg ? TRUE : FALSE);
++ }
++
++ GstBaseSink *QGstBaseSink::baseSink() const
++ {
++ return qGstCheckedCast<GstBaseSink>(element());
++ }
++
++ // QGstBaseSrc
++
++ QGstBaseSrc::QGstBaseSrc(GstBaseSrc *element, RefMode mode)
++ : QGstElement{
++ qGstCheckedCast<GstElement>(element),
++ mode,
++ }
++ {
++ }
++
++ GstBaseSrc *QGstBaseSrc::baseSrc() const
++ {
++ return qGstCheckedCast<GstBaseSrc>(element());
++ }
++
++ // QGstAppSink
++
++ QGstAppSink::QGstAppSink(GstAppSink *element, RefMode mode)
++ : QGstBaseSink{
++ qGstCheckedCast<GstBaseSink>(element),
++ mode,
++ }
++ {
++ }
++
++ QGstAppSink QGstAppSink::create(const char *name)
++ {
++ QGstElement created = QGstElement::createFromFactory("appsink", name);
++ return QGstAppSink{
++ qGstCheckedCast<GstAppSink>(created.element()),
++ QGstAppSink::NeedsRef,
++ };
++ }
++
++ GstAppSink *QGstAppSink::appSink() const
++ {
++ return qGstCheckedCast<GstAppSink>(element());
++ }
++
++ # if GST_CHECK_VERSION(1, 24, 0)
++ void QGstAppSink::setMaxBufferTime(std::chrono::nanoseconds ns)
++ {
++ gst_app_sink_set_max_time(appSink(), qGstClockTimeFromChrono(ns));
++ }
++ # endif
++
++ void QGstAppSink::setMaxBuffers(int n)
++ {
++ gst_app_sink_set_max_buffers(appSink(), n);
++ }
++
++ void QGstAppSink::setCaps(const QGstCaps &caps)
++ {
++ gst_app_sink_set_caps(appSink(), caps.caps());
++ }
++
++ void QGstAppSink::setCallbacks(GstAppSinkCallbacks &callbacks, gpointer user_data,
++ GDestroyNotify notify)
++ {
++ gst_app_sink_set_callbacks(appSink(), &callbacks, user_data, notify);
++ }
++
++ QGstSampleHandle QGstAppSink::pullSample()
++ {
++ return QGstSampleHandle{
++ gst_app_sink_pull_sample(appSink()),
++ QGstSampleHandle::HasRef,
++ };
++ }
++
++ // QGstAppSrc
++
++ QGstAppSrc::QGstAppSrc(GstAppSrc *element, RefMode mode)
++ : QGstBaseSrc{
++ qGstCheckedCast<GstBaseSrc>(element),
++ mode,
++ }
++ {
++ }
++
++ QGstAppSrc QGstAppSrc::create(const char *name)
++ {
++ QGstElement created = QGstElement::createFromFactory("appsrc", name);
++ return QGstAppSrc{
++ qGstCheckedCast<GstAppSrc>(created.element()),
++ QGstAppSrc::NeedsRef,
++ };
++ }
++
++ GstAppSrc *QGstAppSrc::appSrc() const
++ {
++ return qGstCheckedCast<GstAppSrc>(element());
++ }
++
++ void QGstAppSrc::setCallbacks(GstAppSrcCallbacks &callbacks, gpointer user_data,
++ GDestroyNotify notify)
++ {
++ gst_app_src_set_callbacks(appSrc(), &callbacks, user_data, notify);
++ }
++
++ GstFlowReturn QGstAppSrc::pushBuffer(GstBuffer *buffer)
++ {
++ return gst_app_src_push_buffer(appSrc(), buffer);
++ }
++
++ QString qGstErrorMessageCannotFindElement(std::string_view element)
++ {
++ return QStringLiteral("Could not find the %1 GStreamer element")
++ .arg(QLatin1StringView(element));
++ }
++
++ QVideoFrameFormat qVideoFrameFormatFromGstVideoInfo(const QGstVideoInfo &qtVideoInfo)
++ {
++ auto &vidInfo = qtVideoInfo.gstVideoInfo;
++ GstVideoFormat gstFormat = GST_VIDEO_INFO_FORMAT(&vidInfo);
++
++ auto pixelFormat = qPixelFormatFromGstVideoFormat(gstFormat);
++ if (pixelFormat == QVideoFrameFormat::Format_Invalid)
++ return QVideoFrameFormat();
++
++ QVideoFrameFormat format(QSize(vidInfo.width, vidInfo.height), pixelFormat);
++
++ if (vidInfo.fps_d > 0)
++ format.setStreamFrameRate(qreal(vidInfo.fps_n) / vidInfo.fps_d);
++
++ QVideoFrameFormat::ColorRange range = QVideoFrameFormat::ColorRange_Unknown;
++ switch (vidInfo.colorimetry.range) {
++ case GST_VIDEO_COLOR_RANGE_UNKNOWN:
++ break;
++ case GST_VIDEO_COLOR_RANGE_0_255:
++ range = QVideoFrameFormat::ColorRange_Full;
++ break;
++ case GST_VIDEO_COLOR_RANGE_16_235:
++ range = QVideoFrameFormat::ColorRange_Video;
++ break;
++ }
++ format.setColorRange(range);
++
++ QVideoFrameFormat::ColorSpace colorSpace = QVideoFrameFormat::ColorSpace_Undefined;
++ switch (vidInfo.colorimetry.matrix) {
++ case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
++ case GST_VIDEO_COLOR_MATRIX_RGB:
++ case GST_VIDEO_COLOR_MATRIX_FCC:
++ break;
++ case GST_VIDEO_COLOR_MATRIX_BT709:
++ colorSpace = QVideoFrameFormat::ColorSpace_BT709;
++ break;
++ case GST_VIDEO_COLOR_MATRIX_BT601:
++ colorSpace = QVideoFrameFormat::ColorSpace_BT601;
++ break;
++ case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
++ colorSpace = QVideoFrameFormat::ColorSpace_AdobeRgb;
++ break;
++ case GST_VIDEO_COLOR_MATRIX_BT2020:
++ colorSpace = QVideoFrameFormat::ColorSpace_BT2020;
++ break;
++ }
++ format.setColorSpace(colorSpace);
++
++ QVideoFrameFormat::ColorTransfer transfer = QVideoFrameFormat::ColorTransfer_Unknown;
++ switch (vidInfo.colorimetry.transfer) {
++ case GST_VIDEO_TRANSFER_UNKNOWN:
++ break;
++ case GST_VIDEO_TRANSFER_GAMMA10:
++ transfer = QVideoFrameFormat::ColorTransfer_Linear;
++ break;
++ case GST_VIDEO_TRANSFER_GAMMA22:
++ case GST_VIDEO_TRANSFER_SMPTE240M:
++ case GST_VIDEO_TRANSFER_SRGB:
++ case GST_VIDEO_TRANSFER_ADOBERGB:
++ transfer = QVideoFrameFormat::ColorTransfer_Gamma22;
++ break;
++ case GST_VIDEO_TRANSFER_GAMMA18:
++ case GST_VIDEO_TRANSFER_GAMMA20:
++ // not quite, but best fit
++ case GST_VIDEO_TRANSFER_BT709:
++ case GST_VIDEO_TRANSFER_BT2020_12:
++ transfer = QVideoFrameFormat::ColorTransfer_BT709;
++ break;
++ case GST_VIDEO_TRANSFER_GAMMA28:
++ transfer = QVideoFrameFormat::ColorTransfer_Gamma28;
++ break;
++ case GST_VIDEO_TRANSFER_LOG100:
++ case GST_VIDEO_TRANSFER_LOG316:
++ break;
++ case GST_VIDEO_TRANSFER_SMPTE2084:
++ transfer = QVideoFrameFormat::ColorTransfer_ST2084;
++ break;
++ case GST_VIDEO_TRANSFER_ARIB_STD_B67:
++ transfer = QVideoFrameFormat::ColorTransfer_STD_B67;
++ break;
++ case GST_VIDEO_TRANSFER_BT2020_10:
++ transfer = QVideoFrameFormat::ColorTransfer_BT709;
++ break;
++ case GST_VIDEO_TRANSFER_BT601:
++ transfer = QVideoFrameFormat::ColorTransfer_BT601;
++ break;
++ }
++ format.setColorTransfer(transfer);
++
++ return format;
++ }
++
++ QGstCaps::MemoryFormat qMemoryFormatFromGstBuffer(GstBuffer *buffer)
++ {
++ Q_ASSERT(buffer);
++
++ QGstCaps::MemoryFormat memoryFormat = QGstCaps::CpuMemory;
++
++ [[maybe_unused]] GstMemory *mem = gst_buffer_peek_memory(buffer, 0);
++
++ #if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
++ if (gst_is_dmabuf_memory(mem))
++ memoryFormat = QGstCaps::DMABuf;
++ #endif
++ #if QT_CONFIG(gstreamer_gl)
++ if (gst_is_gl_memory(mem))
++ memoryFormat = QGstCaps::GLTexture;
++ #endif
++ return memoryFormat;
++ }
++
++ QVideoFrame qCreateFrameFromGstBuffer(QGstBufferHandle buffer, const QGstVideoInfo &videoInfo,
++ const QVideoFrameFormat &format)
++ {
++ #if QT_CONFIG(linux_dmabuf)
++ if (auto dmaBufBuffer = QGstDmaBufVideoBuffer::create(buffer, videoInfo, format))
++ return QVideoFramePrivate::createFrame(std::move(dmaBufBuffer), format);
++ #endif
++ auto videoBuffer = std::make_unique<QGstVideoBuffer>(std::move(buffer), videoInfo, format);
++ return QVideoFramePrivate::createFrame(std::move(videoBuffer), format);
++ }
++
++ QVideoFrame qCreateFrameFromGstBuffer(QGstBufferHandle buffer, const QGstVideoInfo &videoInfo)
++ {
++ auto format = qVideoFrameFormatFromGstVideoInfo(videoInfo);
++ return qCreateFrameFromGstBuffer(std::move(buffer), videoInfo, format);
++ }
++
++ QT_END_NAMESPACE