Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgstreamer_qiodevice_handler.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qglobal.h>
8#include <QtCore/qiodevice.h>
9#include <QtCore/qmap.h>
10#include <QtCore/qmutex.h>
11#include <QtCore/qobject.h>
12#include <QtCore/qspan.h>
13#include <QtCore/qurl.h>
14#include <QtCore/quuid.h>
15
16#include <gst/base/gstbasesrc.h>
17#include <map>
18#include <memory>
19#include <utility>
20
21QT_BEGIN_NAMESPACE
22
23namespace {
24
25using namespace Qt::Literals;
26
27// QIODeviceRegistry
28
30{
31public:
32 struct Record
33 {
34 explicit Record(QByteArray, QIODevice *);
35
37 bool isValid() const;
38
40
41 template <typename Functor>
42 auto runWhileLocked(Functor &&f)
43 {
44 QMutexLocker guard(&mutex);
45 return f(device);
46 }
47
48 private:
49 QIODevice *device;
50 mutable QMutex mutex;
51 };
52 using SharedRecord = std::shared_ptr<Record>;
53
56
57private:
58 void unregisterDevice(QIODevice *);
59 void deviceDestroyed(QIODevice *);
60
61 QMutex m_registryMutex;
63 QMap<QIODevice *, QByteArray> m_reverseLookupTable;
64};
65
67{
68 Q_ASSERT(device);
69
70 if (device->isSequential())
71 qWarning() << "GStreamer: sequential QIODevices are not fully supported";
72
73 QMutexLocker lock(&m_registryMutex);
74
75 auto it = m_reverseLookupTable.find(device);
76 if (it != m_reverseLookupTable.end())
77 return it.value();
78
79 QByteArray identifier =
80 "qiodevice:/"_ba + QUuid::createUuid().toByteArray(QUuid::StringFormat::Id128);
81
82 m_registry.emplace(identifier, std::make_shared<Record>(identifier, device));
83
84 QMetaObject::Connection destroyedConnection = QObject::connect(
85 device, &QObject::destroyed, this,
86 [this, device] {
87 // Caveat: if the QIODevice has not been closed, we unregister the device, however gstreamer
88 // worker threads have a chance to briefly read from a partially destroyed QIODevice.
89 // There's nothing we can do about it
90 unregisterDevice(device);
91 },
92 Qt::DirectConnection);
93
94 QObject::connect(
95 device, &QIODevice::aboutToClose, this,
96 [this, device, destroyedConnection = std::move(destroyedConnection)] {
97 unregisterDevice(device);
98 disconnect(destroyedConnection);
99 },
100 Qt::DirectConnection);
101
102 m_reverseLookupTable.insert(device, identifier);
103 return identifier;
104}
105
106QIODeviceRegistry::SharedRecord QIODeviceRegistry::findRecord(QByteArrayView id)
107{
108 QMutexLocker registryLock(&m_registryMutex);
109
110 auto it = m_registry.find(id);
111 if (it != m_registry.end())
112 return it->second;
113 return {};
114}
115
116void QIODeviceRegistry::unregisterDevice(QIODevice *device)
117{
118 QMutexLocker registryLock(&m_registryMutex);
119 auto reverseLookupIt = m_reverseLookupTable.find(device);
120 if (reverseLookupIt == m_reverseLookupTable.end())
121 return;
122
123 auto it = m_registry.find(reverseLookupIt.value());
124 Q_ASSERT(it != m_registry.end());
125
126 it->second->unsetDevice();
127 m_reverseLookupTable.erase(reverseLookupIt);
128 m_registry.erase(it);
129}
130
131QIODeviceRegistry::Record::Record(QByteArray id, QIODevice *device)
132 : id {
133 std::move(id),
134 },
135 device {
136 device,
137 }
138{
139 if (!device->isOpen())
140 device->open(QIODevice::ReadOnly);
141}
142
144{
145 QMutexLocker lock(&mutex);
146 device = nullptr;
147}
148
150{
151 QMutexLocker lock(&mutex);
152 return device;
153}
154
156
157// qt helpers
158
159// glib / gstreamer object
164
166{
167 void getProperty(guint propId, GValue *value, const GParamSpec *pspec) const;
168 void setProperty(guint propId, const GValue *value, const GParamSpec *pspec);
169 auto lockObject() const;
170
171 bool start();
172 bool stop();
173
176 GstFlowReturn fill(guint64 offset, guint length, GstBuffer *buf);
177 void getURI(GValue *value) const;
178 bool setURI(const char *location, GError **err = nullptr);
179
182};
183
184void QGstQIODeviceSrc::getProperty(guint propId, GValue *value, const GParamSpec *pspec) const
185{
186 switch (propId) {
187 case PROP_URI:
188 return getURI(value);
189
190 default:
191 G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec);
192 break;
193 }
194}
195
196void QGstQIODeviceSrc::setProperty(guint propId, const GValue *value, const GParamSpec *pspec)
197{
198 switch (propId) {
199 case PROP_URI:
200 setURI(g_value_get_string(value));
201 break;
202 default:
203 G_OBJECT_WARN_INVALID_PROPERTY_ID(this, propId, pspec);
204 break;
205 }
206}
207
209{
210 GST_OBJECT_LOCK(this);
211 return qScopeGuard([this] {
212 GST_OBJECT_UNLOCK(this);
213 });
214}
215
217{
218 auto lock = lockObject();
219
220 return record && record->isValid();
221}
222
224{
225 return true;
226}
227
229{
230 auto lock = lockObject();
231 return record->runWhileLocked([&](QIODevice *device) {
232 return !device->isSequential();
233 });
234}
235
237{
238 auto lock = lockObject();
239 if (!record)
240 return std::nullopt;
241
242 qint64 size = record->runWhileLocked([&](QIODevice *device) {
243 return device->size();
244 });
245
246 if (size == -1)
247 return std::nullopt;
248 return size;
249}
250
251GstFlowReturn QGstQIODeviceSrc::fill(guint64 offset, guint length, GstBuffer *buf)
252{
253 auto lock = lockObject();
254
255 if (!record)
256 return GST_FLOW_ERROR;
257
258 GstMapInfo info;
259 if (!gst_buffer_map(buf, &info, GST_MAP_WRITE)) {
260 GST_ELEMENT_ERROR(this, RESOURCE, WRITE, (nullptr), ("Can't map buffer for writing"));
261 return GST_FLOW_ERROR;
262 }
263
264 int64_t totalRead = 0;
265 GstFlowReturn ret = record->runWhileLocked([&](QIODevice *device) -> GstFlowReturn {
266 if (device->pos() != qint64(offset)) {
267 bool success = device->seek(offset);
268 if (!success) {
269 qWarning() << "seek on iodevice failed";
270 return GST_FLOW_ERROR;
271 }
272 }
273
274 int64_t remain = length;
275 while (remain) {
276 int64_t bytesRead =
277 device->read(reinterpret_cast<char *>(info.data + totalRead), remain);
278 if (bytesRead == -1) {
279 if (device->atEnd()) {
280 return GST_FLOW_EOS;
281 }
282 GST_ELEMENT_ERROR(this, RESOURCE, READ, (nullptr), GST_ERROR_SYSTEM);
283 return GST_FLOW_ERROR;
284 }
285
286 remain -= bytesRead;
287 totalRead += bytesRead;
288 }
289
290 return GST_FLOW_OK;
291 });
292
293 if (ret != GST_FLOW_OK) {
294 gst_buffer_unmap(buf, &info);
295 gst_buffer_resize(buf, 0, 0);
296 return ret;
297 }
298
299 gst_buffer_unmap(buf, &info);
300 if (totalRead != length)
301 gst_buffer_resize(buf, 0, totalRead);
302
303 GST_BUFFER_OFFSET(buf) = offset;
304 GST_BUFFER_OFFSET_END(buf) = offset + totalRead;
305
306 return GST_FLOW_OK;
307}
308
309void QGstQIODeviceSrc::getURI(GValue *value) const
310{
311 auto lock = lockObject();
312 if (record)
313 g_value_set_string(value, record->id);
314 else
315 g_value_set_string(value, nullptr);
316}
317
318bool QGstQIODeviceSrc::setURI(const char *location, GError **err)
319{
320 Q_ASSERT(QLatin1StringView(location).startsWith("qiodevice:/"_L1));
321
322 {
323 auto lock = lockObject();
324 GstState state = GST_STATE(this);
325 if (state != GST_STATE_READY && state != GST_STATE_NULL) {
326 g_warning(
327 "Changing the `uri' property on qiodevicesrc when the resource is open is not "
328 "supported.");
329 if (err)
330 g_set_error(err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
331 "Changing the `uri' property on qiodevicesrc when the resource is open "
332 "is not supported.");
333 return false;
334 }
335 }
336
337 auto newRecord = gQIODeviceRegistry->findRecord(QByteArrayView{ location });
338
339 {
340 auto lock = lockObject();
341 record = std::move(newRecord);
342 }
343
344 g_object_notify(G_OBJECT(this), "uri");
345
346 return true;
347}
348
353
354// GObject
357
359
360template <typename T>
362{
363 return (G_TYPE_CHECK_INSTANCE_CAST((obj), gst_qiodevice_src_get_type(), QGstQIODeviceSrc));
364}
365
366// URI handler
368
369#define gst_qiodevice_src_parent_class parent_class
371 G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, qGstInitQIODeviceURIHandler));
372
373// implementations
374
376{
377 using SharedRecord = QIODeviceRegistry::SharedRecord;
378
379 new (reinterpret_cast<void *>(&self->record)) SharedRecord;
380
381 static constexpr guint defaultBlockSize = 16384;
382 gst_base_src_set_blocksize(&self->baseSrc, defaultBlockSize);
383}
384
386{
387 // GObject
388 GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
389 gobjectClass->set_property = [](GObject *instance, guint propId, const GValue *value,
390 GParamSpec *pspec) {
391 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(instance);
392 return src->setProperty(propId, value, pspec);
393 };
394
395 gobjectClass->get_property = [](GObject *instance, guint propId, GValue *value,
396 GParamSpec *pspec) {
397 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(instance);
398 return src->getProperty(propId, value, pspec);
399 };
400
401 g_object_class_install_property(
402 gobjectClass, PROP_URI,
403 g_param_spec_string("uri", "QRC Location", "Path of the qrc to read", nullptr,
404 GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
405 | GST_PARAM_MUTABLE_READY)));
406
407 gobjectClass->finalize = [](GObject *instance) {
408 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(instance);
409 using SharedRecord = QIODeviceRegistry::SharedRecord;
410 src->record.~SharedRecord();
411 G_OBJECT_CLASS(parent_class)->finalize(instance);
412 };
413
414 // GstElement
415 GstElementClass *gstelementClass = GST_ELEMENT_CLASS(klass);
416 gst_element_class_set_static_metadata(gstelementClass, "QRC Source", "Source/QRC",
417 "Read from arbitrary point in QRC resource",
418 "Tim Blechmann <tim.blechmann@qt.io>");
419
420 static GstStaticPadTemplate srctemplate =
421 GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
422
423 gst_element_class_add_static_pad_template(gstelementClass, &srctemplate);
424
425 // GstBaseSrc
426 GstBaseSrcClass *gstbasesrcClass = GST_BASE_SRC_CLASS(klass);
427 gstbasesrcClass->start = [](GstBaseSrc *basesrc) -> gboolean {
428 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(basesrc);
429 return src->start();
430 };
431 gstbasesrcClass->stop = [](GstBaseSrc *basesrc) -> gboolean {
432 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(basesrc);
433 return src->stop();
434 };
435
436 gstbasesrcClass->is_seekable = [](GstBaseSrc *basesrc) -> gboolean {
437 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(basesrc);
438 return src->isSeekable();
439 };
440 gstbasesrcClass->get_size = [](GstBaseSrc *basesrc, guint64 *size) -> gboolean {
441 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(basesrc);
442 auto optionalSize = src->size();
443 if (!optionalSize)
444 return false;
445 *size = optionalSize.value();
446 return true;
447 };
448 gstbasesrcClass->fill = [](GstBaseSrc *basesrc, guint64 offset, guint length,
449 GstBuffer *buf) -> GstFlowReturn {
450 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(basesrc);
451 return src->fill(offset, length, buf);
452 };
453}
454
455void qGstInitQIODeviceURIHandler(gpointer g_handlerInterface, gpointer)
456{
457 GstURIHandlerInterface *iface = (GstURIHandlerInterface *)g_handlerInterface;
458
459 iface->get_type = [](GType) {
460 return GST_URI_SRC;
461 };
462 iface->get_protocols = [](GType) {
463 static constexpr const gchar *protocols[] = {
464 "qiodevice",
465 nullptr,
466 };
467 return protocols;
468 };
469 iface->get_uri = [](GstURIHandler *handler) -> gchar * {
470 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(handler);
471 auto lock = src->lockObject();
472 if (src->record)
473 return g_strdup(src->record->id.constData());
474 return nullptr;
475 };
476 iface->set_uri = [](GstURIHandler *handler, const gchar *uri, GError **err) -> gboolean {
477 QGstQIODeviceSrc *src = asQGstQIODeviceSrc(handler);
478 return src->setURI(uri, err);
479 };
480}
481
482} // namespace
483
484// plugin registration
485
486void qGstRegisterQIODeviceHandler(GstPlugin *plugin)
487{
488 gst_element_register(plugin, "qiodevicesrc", GST_RANK_PRIMARY, gst_qiodevice_src_get_type());
489}
490
491QUrl qGstRegisterQIODevice(QIODevice *device)
492{
493 return QUrl{
494 QString::fromLatin1(gQIODeviceRegistry->registerQIODevice(device)),
495 };
496}
497
498QT_END_NAMESPACE
Q_GLOBAL_STATIC(QIODeviceRegistry, gQIODeviceRegistry)
static void gst_qiodevice_src_init(QGstQIODeviceSrc *self)
QGstQIODeviceSrc * asQGstQIODeviceSrc(T *obj)
void qGstInitQIODeviceURIHandler(gpointer, gpointer)
static void gst_qiodevice_src_class_init(QGstQIODeviceSrcClass *klass)
GType gst_qiodevice_src_get_type()
G_DEFINE_TYPE_WITH_CODE(QGstQIODeviceSrc, gst_qiodevice_src, GST_TYPE_BASE_SRC, G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, qGstInitQIODeviceURIHandler))
QUrl qGstRegisterQIODevice(QIODevice *)
void qGstRegisterQIODeviceHandler(GstPlugin *plugin)
GstFlowReturn fill(guint64 offset, guint length, GstBuffer *buf)
bool setURI(const char *location, GError **err=nullptr)
void getProperty(guint propId, GValue *value, const GParamSpec *pspec) const
void setProperty(guint propId, const GValue *value, const GParamSpec *pspec)