diff options
Diffstat (limited to 'src/QuickStreamer/item.cpp')
-rw-r--r-- | src/QuickStreamer/item.cpp | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/src/QuickStreamer/item.cpp b/src/QuickStreamer/item.cpp new file mode 100644 index 0000000..f710f04 --- /dev/null +++ b/src/QuickStreamer/item.cpp @@ -0,0 +1,623 @@ +#include "item.h" +#include "metapads.h" + +#include <gst/gst.h> + +#include <QtCore/private/qmetaobjectbuilder_p.h> +#include <QtCore/QVector> +#include <QtQml/QtQml> + +#include <functional> + +namespace QQuickStreamer { +namespace Private { + +struct PropertyInfo +{ + typedef std::function<void(GstObject *target, void *value)> ReadDelegate; + typedef std::function<void(GstObject *target, const void *value)> WriteDelegate; + + const char *name; + ReadDelegate read; + WriteDelegate write; +}; + +static bool isDash(char ch) +{ + return ch == '-' || ch == '_'; +} + +static QByteArray toCamelCase(const QByteArray &input, + char (*convertFirst)(char, const std::locale &)) +{ + int nDashes = 0; + + for (char ch: input) { + if (isDash(ch)) + ++nDashes; + } + + QByteArray output; + + output.reserve(input.size() - nDashes); + + bool wordBoundary = false; + + auto it = std::begin(input); + const auto end = std::end(input); + const auto &loc = std::locale::classic(); + + output += convertFirst(*it, loc); + + while (++it != end) { + if (isDash(*it)) { + wordBoundary = true; + continue; + } + + if (wordBoundary) { + output += std::toupper(*it, loc); + wordBoundary = false; + } else { + output += std::tolower(*it, loc); + } + } + + return output; +} + +static QByteArray makeClassName(GType type) +{ + QByteArray typeName = g_type_name(type); + + // Auto-generating element factories have such bad names... + if (std::islower(typeName.at(0))) + typeName = toCamelCase(typeName, std::toupper); + + static const auto CLASS_NAME_PREFIX = QByteArrayLiteral("Gst"); + + if (not typeName.startsWith(CLASS_NAME_PREFIX)) + typeName = CLASS_NAME_PREFIX + typeName; + + return typeName; +} + +template<typename T> +static QByteArray metaTypeName() +{ + static const QByteArray name = QMetaType::typeName(qMetaTypeId<T>()); + return name; +} + +static QByteArray metaTypeName(GType type) +{ + switch(type) { + case G_TYPE_CHAR: return metaTypeName<char>(); + case G_TYPE_UCHAR: return metaTypeName<uchar>(); + case G_TYPE_BOOLEAN: return metaTypeName<bool>(); + case G_TYPE_INT: return metaTypeName<int>(); + case G_TYPE_UINT: return metaTypeName<uint>(); + case G_TYPE_LONG: return metaTypeName<long>(); + case G_TYPE_ULONG: return metaTypeName<ulong>(); + case G_TYPE_INT64: return metaTypeName<qint64>(); + case G_TYPE_UINT64: return metaTypeName<quint64>(); + case G_TYPE_FLOAT: return metaTypeName<float>(); + case G_TYPE_DOUBLE: return metaTypeName<double>(); + case G_TYPE_STRING: return metaTypeName<QString>(); + //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12) + //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13) + //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17) + //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18) + //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19) + //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20) + //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21) + } + + qWarning("Ignoring unsupported GType: %s", g_type_name(type)); + return {}; +} + +static void readValue(const GValue *input, void *value) +{ + switch(G_VALUE_TYPE(input)) + { + case G_TYPE_CHAR: + *static_cast<char *>(value) = g_value_get_schar(input); + break; + + case G_TYPE_UCHAR: + *static_cast<uchar *>(value) = g_value_get_uchar(input); + break; + + case G_TYPE_BOOLEAN: + *static_cast<bool *>(value) = g_value_get_boolean(input); + break; + + case G_TYPE_INT: + *static_cast<int *>(value) = g_value_get_int(input); + break; + + case G_TYPE_UINT: + *static_cast<uint *>(value) = g_value_get_uint(input); + break; + + case G_TYPE_LONG: + *static_cast<long *>(value) = g_value_get_long(input); + break; + + case G_TYPE_ULONG: + *static_cast<ulong *>(value) = g_value_get_ulong(input); + break; + + case G_TYPE_INT64: + *static_cast<qint64 *>(value) = g_value_get_int64(input); + break; + + case G_TYPE_UINT64: + *static_cast<quint64 *>(value) = g_value_get_uint64(input); + break; + + case G_TYPE_FLOAT: + *static_cast<float *>(value) = g_value_get_float(input); + break; + + case G_TYPE_DOUBLE: + *static_cast<double *>(value) = g_value_get_double(input); + break; + + case G_TYPE_STRING: + *static_cast<QString *>(value) = QString::fromUtf8(g_value_get_string(input)); + break; + + //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12) + //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13) + //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17) + //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18) + //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19) + //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20) + //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21) + + default: + qWarning("Cannot convert unsupported GValue type: %s", G_VALUE_TYPE_NAME(input)); + break; + } +} + +static void writeValue(const void *input, GValue *output) +{ + switch(G_VALUE_TYPE(output)) + { + case G_TYPE_CHAR: + g_value_set_schar(output, *static_cast<const char *>(input)); + break; + + case G_TYPE_UCHAR: + g_value_set_uchar(output, *static_cast<const uchar *>(input)); + break; + + case G_TYPE_BOOLEAN: + g_value_set_boolean(output, *static_cast<const bool *>(input)); + break; + + case G_TYPE_INT: + g_value_set_int(output, *static_cast<const int *>(input)); + break; + + case G_TYPE_UINT: + g_value_set_uint(output, *static_cast<const uint *>(input)); + break; + + case G_TYPE_LONG: + g_value_set_long(output, *static_cast<const long *>(input)); + break; + + case G_TYPE_ULONG: + g_value_set_ulong(output, *static_cast<const ulong *>(input)); + break; + + case G_TYPE_INT64: + g_value_set_int64(output, *static_cast<const qint64 *>(input)); + break; + + case G_TYPE_UINT64: + g_value_set_uint64(output, *static_cast<const quint64 *>(input)); + break; + + case G_TYPE_FLOAT: + g_value_set_float(output, *static_cast<const float *>(input)); + break; + + case G_TYPE_DOUBLE: + g_value_set_double(output, *static_cast<const double *>(input)); + break; + + case G_TYPE_STRING: + g_value_set_string(output, static_cast<const QString *>(input)->toUtf8().constData()); + break; + + //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12) + //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13) + //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17) + //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18) + //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19) + //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20) + //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21) + + default: + qWarning("Cannot convert unsupported GValue type: %s", G_VALUE_TYPE_NAME(output)); + break; + } +} + +static void readGObjectProperty(GstObject *target, const QByteArray &propertyName, + GType propertyType, void *value) +{ + GValue gvalue = G_VALUE_INIT; + g_value_init(&gvalue, propertyType); + g_object_get_property(G_OBJECT(target), propertyName.constData(), &gvalue); + readValue(&gvalue, value); + g_value_unset(&gvalue); +} + +static void writeGObjectProperty(GstObject *target, const QByteArray &propertyName, + GType propertyType, const void *value) +{ + GValue gvalue = G_VALUE_INIT; + g_value_init(&gvalue, propertyType); + writeValue(value, &gvalue); + g_object_set_property(G_OBJECT(target), propertyName.constData(), &gvalue); + g_value_unset(&gvalue); +} + +static void readNothing(GstObject *target, const QByteArray &name, void *) +{ + qWarning("Cannot read \"%s::%s\" which is not declared readable", + G_OBJECT_TYPE_NAME(target), name.constData()); +} + +static void writeNothing(GstObject *target, const QByteArray &name, const void *) +{ + qWarning("Cannot write \"%s::%s\" which is not declared writable", + G_OBJECT_TYPE_NAME(target), name.constData()); +} + +template<typename... Args> +PropertyInfo::ReadDelegate bindReadDelegate(Args... args) +{ + return std::bind(args...); +} + +template<typename... Args> +PropertyInfo::WriteDelegate bindWriteDelegate(Args... args) +{ + return std::bind(args...); +} + +struct TypeInfo +{ + TypeInfo() + : parent(Q_NULLPTR) + , metaObject(Q_NULLPTR) + { + } + + static const TypeInfo *create(GType type, const MetaTypePads &metaTypePads) + { + QScopedPointer<TypeInfo> typeInfo(new TypeInfo); + + QMetaObjectBuilder objectBuilder; + objectBuilder.setClassName(makeClassName(type)); + + if (type != G_TYPE_OBJECT) + typeInfo->parent = find(g_type_parent(type)); + + objectBuilder.setSuperClass(typeInfo->parent ? typeInfo->parent->metaObject + : &QObject::staticMetaObject); + + const auto gobject_class = static_cast<GObjectClass *>(g_type_class_ref(type)); + + uint nPSpecs = 0; + auto *const pSpecs = g_object_class_list_properties(gobject_class, &nPSpecs); + + for (uint i = 0; i < nPSpecs; ++i) { + const auto *const pSpec = pSpecs[i]; + + if (pSpec->owner_type != type) + continue; + + const auto &propertyName = toCamelCase(pSpec->name, std::tolower); + const auto &propertyType = metaTypeName(pSpec->value_type); + + if (pSpec->flags & G_PARAM_DEPRECATED) { + qWarning("Ignoring deprecated property %s::%s", g_type_name(type), pSpec->name); + continue; + } + + if (propertyType.isEmpty()) { + qWarning("Ignoring property %s::%s of unsupported type %s", + g_type_name(type), pSpec->name, g_type_name(pSpec->value_type)); + continue; + } + + auto notifier = objectBuilder.addSignal(propertyName + QByteArrayLiteral("Changed()")); + auto property = objectBuilder.addProperty(propertyName, propertyType, notifier.index()); + + property.setReadable(pSpec->flags & G_PARAM_READABLE); + property.setWritable(pSpec->flags & G_PARAM_WRITABLE); + + using namespace std::placeholders; + + const auto readDelegate = property.isReadable() + ? bindReadDelegate(&readGObjectProperty, _1, pSpec->name, pSpec->value_type, _2) + : bindReadDelegate(&readNothing, _1, pSpec->name, _2); + const auto writeDelegate = property.isWritable() + ? bindWriteDelegate(&writeGObjectProperty, _1, pSpec->name, pSpec->value_type, _2) + : bindWriteDelegate(&writeNothing, _1, pSpec->name, _2); + + typeInfo->properties.append({pSpec->name, readDelegate, writeDelegate}); + } + + g_free(pSpecs); + + uint nSignalIds = 0; + auto *const signalIds = g_signal_list_ids(type, &nSignalIds); + + for (uint i = 0; i < nSignalIds; ++i) { + GSignalQuery query; + g_signal_query(signalIds[i], &query); + Q_ASSERT(signalIds[i] == query.signal_id); + qDebug("signal found: %s::%s", g_type_name(type), query.signal_name); + } + + g_type_class_unref(gobject_class); + + typedef QtMetaTypePrivate::QMetaTypeFunctionHelper<Item> MetaTypeHelper; + static const QMetaType::TypeFlags typeFlags(QtPrivate::QMetaTypeTypeFlags<Item>::Flags); + + typeInfo->metaObject = objectBuilder.toMetaObject(); + typeInfo->elementName = objectBuilder.className().mid(3); + + const int typeId = QMetaType::registerNormalizedType(typeInfo->metaObject->className(), + MetaTypeHelper::Delete, + metaTypePads.create, + MetaTypeHelper::Destruct, + metaTypePads.construct, + sizeof(Item), + typeFlags, + typeInfo->metaObject); + + // FIXME: constants.h or pass by argument + static const auto NAMESPACE_URI = QByteArrayLiteral("QuickStreamer"); + const static int MAJOR_VERSION = 1; + const static int MINOR_VERSION = 0; + + auto pointerName = objectBuilder.className() + QByteArrayLiteral(" *"); + + QQmlPrivate::RegisterType qmlType = { + 1, + + typeId, + 0 /*qRegisterNormalizedMetaType<QQmlListProperty<T> >(listName.constData())*/, + sizeof(Item), metaTypePads.createInto, + QString(), + + NAMESPACE_URI, MAJOR_VERSION, MINOR_VERSION, + typeInfo->elementName, + typeInfo->metaObject, + + QQmlPrivate::attachedPropertiesFunc<Item>(), + QQmlPrivate::attachedPropertiesMetaObject<Item>(), + + QQmlPrivate::StaticCastSelector<Item, QQmlParserStatus>::cast(), + QQmlPrivate::StaticCastSelector<Item, QQmlPropertyValueSource>::cast(), + QQmlPrivate::StaticCastSelector<Item, QQmlPropertyValueInterceptor>::cast(), + + 0, 0, + + 0, + 0 + }; + + QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &qmlType); + + cache.insert(type, typeInfo.data()); + return typeInfo.take(); + } + + static const TypeInfo *find(GType type) + { + Q_ASSERT(g_type_is_a(type, G_TYPE_OBJECT)); + Q_ASSERT(not g_type_is_a(type, GST_TYPE_ELEMENT_FACTORY)); + + const auto *typeInfo = cache.value(type); + + if (typeInfo == Q_NULLPTR) + typeInfo = create(type, MetaTypePads::forObjectType(type)); + + return typeInfo; + } + + static const TypeInfo *find(GstElementFactory *factory) + { + Q_ASSERT(GST_IS_ELEMENT_FACTORY(factory)); + + const auto type = gst_element_factory_get_element_type(factory); + const auto *typeInfo = cache.value(type); + + if (typeInfo == Q_NULLPTR) + typeInfo = create(type, MetaTypePads::forElementFactory(factory)); + + return typeInfo; + } + + int readProperty(GstObject *object, int id, QVariant *value) const + { + if (parent) + id = parent->readProperty(object, id, value); + if (id < 0) + return id; + + if (id < properties.size()) + properties.at(id).read(object, value); + + return id - properties.size(); + } + + int writeProperty(GstObject *object, int id, const void *value) const + { + if (parent) + id = parent->writeProperty(object, id, value); + if (id < 0) + return id; + + if (id < properties.size()) + properties.at(id).write(object, value); + + return id - properties.size(); + } + + void emitPropertyChanged(Item *object, const GParamSpec *pspec) const + { + for (int i = 0; i < properties.size(); ++i) { + // can compare by pointer because GParamSpec::name is interned + if (pspec->name == properties.at(i).name) { + QMetaObject::activate(object, metaObject, i, Q_NULLPTR); + return; + } + } + + if (parent) + parent->emitPropertyChanged(object, pspec); + } + + const TypeInfo *parent; + const QMetaObject *metaObject; + + QByteArray elementName; + QVector<PropertyInfo> properties; + static QHash<GType, TypeInfo *> cache; +}; + +QHash<GType, TypeInfo *> TypeInfo::cache; + +} // namespace Private + +using Private::TypeInfo; +using Private::toCamelCase; + +Item::Item(GstObject *target, QObject *parent) + : QObject(parent) + , m_target(target) +{ + Q_ASSERT(m_target != Q_NULLPTR); + + g_object_ref_sink(m_target); + g_signal_connect_swapped(target, "notify", reinterpret_cast<GCallback>(&Item::emitPropertyChanged), this); +} + +Item::Item(const Item &other) + : QObject(other.parent()) + , m_target(static_cast<GstObject *>(g_object_ref(other.m_target))) +{ +} + +Item::~Item() +{ + g_object_unref(m_target); +} + +bool Item::registerElementFactory(GstElementFactory *factory) +{ + return TypeInfo::find(factory) != Q_NULLPTR; +} + +bool Item::registerObjectClass(GType type) +{ + return TypeInfo::find(type) != Q_NULLPTR; +} + +const QMetaObject *Item::metaObject() const +{ + return TypeInfo::find(G_OBJECT_TYPE(m_target))->metaObject; +} + +void *Item::qt_metacast(const char *className) +{ + qDebug("%s: %s %p", Q_FUNC_INFO, className, this); + + if (className == Q_NULLPTR) + return Q_NULLPTR; + + if (strcmp(className, G_OBJECT_TYPE_NAME(m_target)) == 0) + return this; + if (strcmp(className, QQmlParserStatus_iid) == 0) + return static_cast<QQmlParserStatus *>(this); + + return QObject::qt_metacast(className); +} + +int Item::qt_metacall(QMetaObject::Call call, int id, void **args) +{ + qDebug("%s: %d(%d)", Q_FUNC_INFO, call, id); + id = QObject::qt_metacall(call, id, args); + + if (id < 0) + return id; + + switch(call) { + case QMetaObject::ReadProperty: + id = readProperty(id, static_cast<QVariant *>(args[0])); + break; + + case QMetaObject::WriteProperty: + id = writeProperty(id, args[0]); + break; + + case QMetaObject::InvokeMetaMethod: + case QMetaObject::ResetProperty: + case QMetaObject::QueryPropertyDesignable: + case QMetaObject::QueryPropertyScriptable: + case QMetaObject::QueryPropertyStored: + case QMetaObject::QueryPropertyEditable: + case QMetaObject::QueryPropertyUser: + case QMetaObject::CreateInstance: + case QMetaObject::IndexOfMethod: + case QMetaObject::RegisterPropertyMetaType: + case QMetaObject::RegisterMethodArgumentMetaType: + break; + } + + return id; +} + +void Item::classBegin() +{ + Q_ASSERT(parent() != Q_NULLPTR); + qDebug() << Q_FUNC_INFO << metaObject()->className() << parent()->children(); +} + +void Item::componentComplete() +{ + Q_ASSERT(parent() != Q_NULLPTR); + qDebug() << Q_FUNC_INFO << metaObject()->className() << parent()->children(); +} + +int Item::readProperty(int id, QVariant *value) +{ + const auto *const typeInfo = TypeInfo::find(G_OBJECT_TYPE(m_target)); + return typeInfo->readProperty(m_target, id, value); +} + +int Item::writeProperty(int id, const void *value) +{ + const auto *const typeInfo = TypeInfo::find(G_OBJECT_TYPE(m_target)); + return typeInfo->writeProperty(m_target, id, value); +} + +void Item::emitPropertyChanged(Item *self, GParamSpec *pspec) +{ + const auto *const typeInfo = TypeInfo::find(G_OBJECT_TYPE(self->m_target)); + typeInfo->emitPropertyChanged(self, pspec); +} + +} // namespace QQuickStreamer |