Revision control
Copy as Markdown
// 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