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 "qgstreamer_qrc_handler_p.h"
++
++ #include <QtCore/qfile.h>
++ #include <QtCore/qglobal.h>
++ #include <QtCore/qstring.h>
++ #include <QtCore/qurl.h>
++
++ #include <gst/base/gstbasesrc.h>
++
++ #include <mutex>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ // qt helpers
++
++ using namespace Qt::Literals;
++
++ std::optional<QString> qQUrlToQrcPath(const QUrl &url)
++ {
++ if (url.scheme() == "qrc"_L1)
++ return ':'_L1 + url.path();
++ return std::nullopt;
++ }
++
++ std::optional<QUrl> qQrcPathToQUrl(QStringView path)
++ {
++ if (!path.empty() && path[0] == ':'_L1)
++ return QUrl(u"qrc://"_s + path.mid(1));
++ return std::nullopt;
++ }
++
++ // glib / gstreamer object
++
++ enum PropertyId : uint8_t {
++ PROP_NONE,
++ PROP_URI,
++ };
++
++ struct QGstQrcSrc
++ {
++ void getProperty(guint propId, GValue *value, const GParamSpec *pspec) const;
++ void setProperty(guint propId, const GValue *value, const GParamSpec *pspec);
++
++ bool start();
++ bool stop();
++
++ std::optional<guint64> size();
++ GstFlowReturn fill(guint64 offset, guint length, GstBuffer *buf);
++ void getURI(GValue *value) const;
++ bool setURI(const char *location, GError **err = nullptr);
++
++ // lockable
++ void lock() const { GST_OBJECT_LOCK(&baseSrc); }
++ void unlock() const { GST_OBJECT_UNLOCK(&baseSrc); }
++
++ GstBaseSrc baseSrc;
++ QFile file;
++ };
++
++ void QGstQrcSrc::getProperty(guint propId, GValue *value, const GParamSpec *pspec) const
++ {
++ switch (propId) {
++ case PROP_URI:
++ return getURI(value);
++
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec);
++ break;
++ }
++ }
++
++ void QGstQrcSrc::setProperty(guint propId, const GValue *value, const GParamSpec *pspec)
++ {
++ switch (propId) {
++ case PROP_URI:
++ setURI(g_value_get_string(value));
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec);
++ break;
++ }
++ }
++
++ bool QGstQrcSrc::start()
++ {
++ std::unique_lock lock{ *this };
++ if (file.fileName().isEmpty()) {
++ lock.unlock();
++ GST_ELEMENT_ERROR(&baseSrc, RESOURCE, NOT_FOUND,
++ ("No resource name specified for reading."), (nullptr));
++ return false;
++ }
++
++ bool opened = file.open(QIODevice::ReadOnly);
++ if (!opened) {
++ lock.unlock();
++ GST_ELEMENT_ERROR(&baseSrc, RESOURCE, NOT_FOUND, (nullptr),
++ ("No such resource \"%s\"", file.fileName().toUtf8().constData()));
++ return false;
++ }
++
++ gst_base_src_set_dynamic_size(&baseSrc, false);
++
++ Q_ASSERT(file.isOpen());
++ return true;
++ }
++
++ bool QGstQrcSrc::stop()
++ {
++ std::lock_guard guard{ *this };
++ file.close();
++ return true;
++ }
++
++ std::optional<guint64> QGstQrcSrc::size()
++ {
++ std::lock_guard guard{ *this };
++ if (!file.isOpen())
++ return std::nullopt;
++ return file.size();
++ }
++
++ GstFlowReturn QGstQrcSrc::fill(guint64 offset, guint length, GstBuffer *buf)
++ {
++ std::unique_lock guard{ *this };
++
++ if (!file.isOpen())
++ return GST_FLOW_ERROR;
++
++ if (G_UNLIKELY(offset != guint64(-1) && guint64(file.pos()) != offset)) {
++ bool success = file.seek(int64_t(offset));
++ if (!success) {
++ guard.unlock();
++ GST_ELEMENT_ERROR(&baseSrc, RESOURCE, READ, (nullptr), GST_ERROR_SYSTEM);
++ return GST_FLOW_ERROR;
++ }
++ }
++
++ GstMapInfo info;
++ if (!gst_buffer_map(buf, &info, GST_MAP_WRITE)) {
++ guard.unlock();
++ GST_ELEMENT_ERROR(&baseSrc, RESOURCE, WRITE, (nullptr), ("Can't map buffer for writing"));
++ return GST_FLOW_ERROR;
++ }
++
++ int64_t remain = length;
++ int64_t totalRead = 0;
++ while (remain) {
++ int64_t bytesRead = file.read(reinterpret_cast<char *>(info.data) + totalRead, remain);
++ if (bytesRead == -1) {
++ if (file.atEnd()) {
++ gst_buffer_unmap(buf, &info);
++ gst_buffer_resize(buf, 0, 0);
++ return GST_FLOW_EOS;
++ }
++ guard.unlock();
++ GST_ELEMENT_ERROR(&baseSrc, RESOURCE, READ, (nullptr), GST_ERROR_SYSTEM);
++ gst_buffer_unmap(buf, &info);
++ gst_buffer_resize(buf, 0, 0);
++ return GST_FLOW_ERROR;
++ }
++
++ remain -= bytesRead;
++ totalRead += bytesRead;
++ }
++
++ gst_buffer_unmap(buf, &info);
++ if (totalRead != length)
++ gst_buffer_resize(buf, 0, totalRead);
++
++ GST_BUFFER_OFFSET(buf) = offset;
++ GST_BUFFER_OFFSET_END(buf) = offset + totalRead;
++
++ return GST_FLOW_OK;
++ }
++
++ void QGstQrcSrc::getURI(GValue *value) const
++ {
++ std::lock_guard guard{ *this };
++ std::optional<QUrl> url = qQrcPathToQUrl(file.fileName());
++ if (url)
++ g_value_set_string(value, url->toString().toUtf8().constData());
++ else
++ g_value_set_string(value, nullptr);
++ }
++
++ bool QGstQrcSrc::setURI(const char *location, GError **err)
++ {
++ Q_ASSERT(QLatin1StringView(location).startsWith("qrc:/"_L1));
++
++ {
++ std::lock_guard guard{ *this };
++ GstState state = GST_STATE(this);
++ if (state != GST_STATE_READY && state != GST_STATE_NULL) {
++ g_warning("Changing the `uri' property on qrcsrc when the resource is open is not "
++ "supported.");
++ if (err)
++ g_set_error(err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
++ "Changing the `uri' property on qrcsrc when the resource is open "
++ "is not supported.");
++ return false;
++ }
++ }
++
++ std::optional<QString> path = qQUrlToQrcPath(QUrl::fromEncoded(location));
++
++ {
++ std::lock_guard guard{ *this };
++ file.close();
++ file.setFileName(path.value_or(u""_s));
++ }
++
++ g_object_notify(G_OBJECT(this), "uri");
++
++ return true;
++ }
++
++ struct QGstQrcSrcClass
++ {
++ GstBaseSrcClass parent_class;
++ };
++
++ // GObject
++ static void gst_qrc_src_class_init(QGstQrcSrcClass *klass);
++ static void gst_qrc_src_init(QGstQrcSrc *self);
++
++ GType gst_qrc_src_get_type();
++
++ template <typename T>
++ QGstQrcSrc *asQGstQrcSrc(T *obj)
++ {
++ return (G_TYPE_CHECK_INSTANCE_CAST((obj), gst_qrc_src_get_type(), QGstQrcSrc));
++ }
++
++ // URI handler
++ void qGstInitURIHandler(gpointer, gpointer);
++
++ #define gst_qrc_src_parent_class parent_class
++ G_DEFINE_TYPE_WITH_CODE(QGstQrcSrc, gst_qrc_src, GST_TYPE_BASE_SRC,
++ G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, qGstInitURIHandler));
++
++ // implementations
++
++ void gst_qrc_src_class_init(QGstQrcSrcClass *klass)
++ {
++ // GObject
++ GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
++ gobjectClass->set_property = [](GObject *instance, guint propId, const GValue *value,
++ GParamSpec *pspec) {
++ QGstQrcSrc *src = asQGstQrcSrc(instance);
++ return src->setProperty(propId, value, pspec);
++ };
++
++ gobjectClass->get_property = [](GObject *instance, guint propId, GValue *value,
++ GParamSpec *pspec) {
++ QGstQrcSrc *src = asQGstQrcSrc(instance);
++ return src->getProperty(propId, value, pspec);
++ };
++
++ g_object_class_install_property(
++ gobjectClass, PROP_URI,
++ g_param_spec_string("uri", "QRC Location", "Path of the qrc to read", nullptr,
++ GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
++ | GST_PARAM_MUTABLE_READY)));
++
++ gobjectClass->finalize = [](GObject *instance) {
++ QGstQrcSrc *src = asQGstQrcSrc(instance);
++ src->file.~QFile();
++ G_OBJECT_CLASS(parent_class)->finalize(instance);
++ };
++
++ // GstElement
++ GstElementClass *gstelementClass = GST_ELEMENT_CLASS(klass);
++ gst_element_class_set_static_metadata(gstelementClass, "QRC Source", "Source/QRC",
++ "Read from arbitrary point in QRC resource",
++ "Tim Blechmann <tim.blechmann@qt.io>");
++
++ static GstStaticPadTemplate srctemplate =
++ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
++
++ gst_element_class_add_static_pad_template(gstelementClass, &srctemplate);
++
++ // GstBaseSrc
++ GstBaseSrcClass *gstbasesrcClass = GST_BASE_SRC_CLASS(klass);
++ gstbasesrcClass->start = [](GstBaseSrc *basesrc) -> gboolean {
++ QGstQrcSrc *src = asQGstQrcSrc(basesrc);
++ return src->start();
++ };
++ gstbasesrcClass->stop = [](GstBaseSrc *basesrc) -> gboolean {
++ QGstQrcSrc *src = asQGstQrcSrc(basesrc);
++ return src->stop();
++ };
++
++ gstbasesrcClass->is_seekable = [](GstBaseSrc *) -> gboolean {
++ return true;
++ };
++ gstbasesrcClass->get_size = [](GstBaseSrc *basesrc, guint64 *size) -> gboolean {
++ QGstQrcSrc *src = asQGstQrcSrc(basesrc);
++ auto optionalSize = src->size();
++ if (!optionalSize)
++ return false;
++ *size = optionalSize.value();
++ return true;
++ };
++ gstbasesrcClass->fill = [](GstBaseSrc *basesrc, guint64 offset, guint length,
++ GstBuffer *buf) -> GstFlowReturn {
++ QGstQrcSrc *src = asQGstQrcSrc(basesrc);
++ return src->fill(offset, length, buf);
++ };
++ }
++
++ void gst_qrc_src_init(QGstQrcSrc *self)
++ {
++ new (reinterpret_cast<void *>(&self->file)) QFile;
++
++ static constexpr guint defaultBlockSize = 16384;
++ gst_base_src_set_blocksize(&self->baseSrc, defaultBlockSize);
++ }
++
++ void qGstInitURIHandler(gpointer g_handlerInterface, gpointer)
++ {
++ GstURIHandlerInterface *iface = (GstURIHandlerInterface *)g_handlerInterface;
++
++ iface->get_type = [](GType) {
++ return GST_URI_SRC;
++ };
++ iface->get_protocols = [](GType) {
++ static constexpr const gchar *protocols[] = {
++ "qrc",
++ nullptr,
++ };
++ return protocols;
++ };
++ iface->get_uri = [](GstURIHandler *handler) -> gchar * {
++ QGstQrcSrc *src = asQGstQrcSrc(handler);
++ std::lock_guard guard{ *src };
++ std::optional<QUrl> url = qQrcPathToQUrl(src->file.fileName());
++ if (url)
++ return g_strdup(url->toString().toUtf8().constData());
++
++ return nullptr;
++ };
++ iface->set_uri = [](GstURIHandler *handler, const gchar *uri, GError **err) -> gboolean {
++ QGstQrcSrc *src = asQGstQrcSrc(handler);
++ return src->setURI(uri, err);
++ };
++ }
++
++ } // namespace
++
++ // plugin registration
++
++ void qGstRegisterQRCHandler(GstPlugin *plugin)
++ {
++ gst_element_register(plugin, "qrcsrc", GST_RANK_PRIMARY, gst_qrc_src_get_type());
++ }
++
++ QT_END_NAMESPACE