| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include "qgstcodecsinfo_p.h" |
| 41 | #include "qgstutils_p.h" |
| 42 | #include <QtCore/qset.h> |
| 43 | |
| 44 | #include <gst/pbutils/pbutils.h> |
| 45 | |
| 46 | static QSet<QString> streamTypes(GstElementFactory *factory, GstPadDirection direction) |
| 47 | { |
| 48 | QSet<QString> types; |
| 49 | const GList *pads = gst_element_factory_get_static_pad_templates(factory); |
| 50 | for (const GList *pad = pads; pad; pad = g_list_next(pad)) { |
| 51 | GstStaticPadTemplate *templ = reinterpret_cast<GstStaticPadTemplate *>(pad->data); |
| 52 | if (templ->direction == direction) { |
| 53 | GstCaps *caps = gst_static_caps_get(static_caps: &templ->static_caps); |
| 54 | for (uint i = 0; i < gst_caps_get_size(caps); ++i) { |
| 55 | GstStructure *structure = gst_caps_get_structure(caps, index: i); |
| 56 | types.insert(value: QString::fromUtf8(str: gst_structure_get_name(structure))); |
| 57 | } |
| 58 | gst_caps_unref(caps); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | return types; |
| 63 | } |
| 64 | |
| 65 | QGstCodecsInfo::QGstCodecsInfo(QGstCodecsInfo::ElementType elementType) |
| 66 | { |
| 67 | updateCodecs(elementType); |
| 68 | for (auto &codec : supportedCodecs()) { |
| 69 | GstElementFactory *factory = gst_element_factory_find(name: codecElement(codec).constData()); |
| 70 | if (factory) { |
| 71 | GstPadDirection direction = elementType == Muxer ? GST_PAD_SINK : GST_PAD_SRC; |
| 72 | m_streamTypes.insert(akey: codec, avalue: streamTypes(factory, direction)); |
| 73 | gst_object_unref(GST_OBJECT(factory)); |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | QStringList QGstCodecsInfo::supportedCodecs() const |
| 79 | { |
| 80 | return m_codecs; |
| 81 | } |
| 82 | |
| 83 | QString QGstCodecsInfo::codecDescription(const QString &codec) const |
| 84 | { |
| 85 | return m_codecInfo.value(akey: codec).description; |
| 86 | } |
| 87 | |
| 88 | QByteArray QGstCodecsInfo::codecElement(const QString &codec) const |
| 89 | |
| 90 | { |
| 91 | return m_codecInfo.value(akey: codec).elementName; |
| 92 | } |
| 93 | |
| 94 | QStringList QGstCodecsInfo::codecOptions(const QString &codec) const |
| 95 | { |
| 96 | QStringList options; |
| 97 | |
| 98 | QByteArray elementName = m_codecInfo.value(akey: codec).elementName; |
| 99 | if (elementName.isEmpty()) |
| 100 | return options; |
| 101 | |
| 102 | GstElement *element = gst_element_factory_make(factoryname: elementName, name: nullptr); |
| 103 | if (element) { |
| 104 | guint numProperties; |
| 105 | GParamSpec **properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(element), |
| 106 | n_properties: &numProperties); |
| 107 | for (guint j = 0; j < numProperties; ++j) { |
| 108 | GParamSpec *property = properties[j]; |
| 109 | // ignore some properties |
| 110 | if (strcmp(s1: property->name, s2: "name" ) == 0 || strcmp(s1: property->name, s2: "parent" ) == 0) |
| 111 | continue; |
| 112 | |
| 113 | options.append(t: QLatin1String(property->name)); |
| 114 | } |
| 115 | g_free(mem: properties); |
| 116 | gst_object_unref(object: element); |
| 117 | } |
| 118 | |
| 119 | return options; |
| 120 | } |
| 121 | |
| 122 | void QGstCodecsInfo::updateCodecs(ElementType elementType) |
| 123 | { |
| 124 | m_codecs.clear(); |
| 125 | m_codecInfo.clear(); |
| 126 | |
| 127 | GList *elements = elementFactories(elementType); |
| 128 | |
| 129 | QSet<QByteArray> fakeEncoderMimeTypes; |
| 130 | fakeEncoderMimeTypes << "unknown/unknown" |
| 131 | << "audio/x-raw-int" << "audio/x-raw-float" |
| 132 | << "video/x-raw-yuv" << "video/x-raw-rgb" ; |
| 133 | |
| 134 | QSet<QByteArray> fieldsToAdd; |
| 135 | fieldsToAdd << "mpegversion" << "layer" << "layout" << "raversion" |
| 136 | << "wmaversion" << "wmvversion" << "variant" << "systemstream" ; |
| 137 | |
| 138 | GList *element = elements; |
| 139 | while (element) { |
| 140 | GstElementFactory *factory = (GstElementFactory *)element->data; |
| 141 | element = element->next; |
| 142 | |
| 143 | const GList *padTemplates = gst_element_factory_get_static_pad_templates(factory); |
| 144 | while (padTemplates) { |
| 145 | GstStaticPadTemplate *padTemplate = (GstStaticPadTemplate *)padTemplates->data; |
| 146 | padTemplates = padTemplates->next; |
| 147 | |
| 148 | if (padTemplate->direction == GST_PAD_SRC) { |
| 149 | GstCaps *caps = gst_static_caps_get(static_caps: &padTemplate->static_caps); |
| 150 | for (uint i=0; i<gst_caps_get_size(caps); i++) { |
| 151 | const GstStructure *structure = gst_caps_get_structure(caps, index: i); |
| 152 | |
| 153 | //skip "fake" encoders |
| 154 | if (fakeEncoderMimeTypes.contains(value: gst_structure_get_name(structure))) |
| 155 | continue; |
| 156 | |
| 157 | GstStructure *newStructure = qt_gst_structure_new_empty(name: gst_structure_get_name(structure)); |
| 158 | |
| 159 | //add structure fields to distinguish between formats with similar mime types, |
| 160 | //like audio/mpeg |
| 161 | for (int j=0; j<gst_structure_n_fields(structure); j++) { |
| 162 | const gchar* fieldName = gst_structure_nth_field_name(structure, index: j); |
| 163 | if (fieldsToAdd.contains(value: fieldName)) { |
| 164 | const GValue *value = gst_structure_get_value(structure, fieldname: fieldName); |
| 165 | GType valueType = G_VALUE_TYPE(value); |
| 166 | |
| 167 | //don't add values of range type, |
| 168 | //gst_pb_utils_get_codec_description complains about not fixed caps |
| 169 | |
| 170 | if (valueType != GST_TYPE_INT_RANGE && valueType != GST_TYPE_DOUBLE_RANGE && |
| 171 | valueType != GST_TYPE_FRACTION_RANGE && valueType != GST_TYPE_LIST && |
| 172 | valueType != GST_TYPE_ARRAY) |
| 173 | gst_structure_set_value(structure: newStructure, fieldname: fieldName, value); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | GstCaps *newCaps = gst_caps_new_full(struct1: newStructure, nullptr); |
| 178 | |
| 179 | gchar *capsString = gst_caps_to_string(caps: newCaps); |
| 180 | QString codec = QLatin1String(capsString); |
| 181 | if (capsString) |
| 182 | g_free(mem: capsString); |
| 183 | GstRank rank = GstRank(gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory))); |
| 184 | |
| 185 | // If two elements provide the same codec, use the highest ranked one |
| 186 | QMap<QString, CodecInfo>::const_iterator it = m_codecInfo.constFind(akey: codec); |
| 187 | if (it == m_codecInfo.constEnd() || it->rank < rank) { |
| 188 | if (it == m_codecInfo.constEnd()) |
| 189 | m_codecs.append(t: codec); |
| 190 | |
| 191 | CodecInfo info; |
| 192 | info.elementName = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory)); |
| 193 | |
| 194 | gchar *description = gst_pb_utils_get_codec_description(caps: newCaps); |
| 195 | info.description = QString::fromUtf8(str: description); |
| 196 | if (description) |
| 197 | g_free(mem: description); |
| 198 | |
| 199 | info.rank = rank; |
| 200 | |
| 201 | m_codecInfo.insert(akey: codec, avalue: info); |
| 202 | } |
| 203 | |
| 204 | gst_caps_unref(caps: newCaps); |
| 205 | } |
| 206 | gst_caps_unref(caps); |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | gst_plugin_feature_list_free(list: elements); |
| 212 | } |
| 213 | |
| 214 | #if !GST_CHECK_VERSION(0, 10, 31) |
| 215 | static gboolean element_filter(GstPluginFeature *feature, gpointer user_data) |
| 216 | { |
| 217 | if (Q_UNLIKELY(!GST_IS_ELEMENT_FACTORY(feature))) |
| 218 | return FALSE; |
| 219 | |
| 220 | const QGstCodecsInfo::ElementType type = *reinterpret_cast<QGstCodecsInfo::ElementType *>(user_data); |
| 221 | |
| 222 | const gchar *klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature)); |
| 223 | if (type == QGstCodecsInfo::AudioEncoder && !(strstr(klass, "Encoder" ) && strstr(klass, "Audio" ))) |
| 224 | return FALSE; |
| 225 | if (type == QGstCodecsInfo::VideoEncoder && !(strstr(klass, "Encoder" ) && strstr(klass, "Video" ))) |
| 226 | return FALSE; |
| 227 | if (type == QGstCodecsInfo::Muxer && !strstr(klass, "Muxer" )) |
| 228 | return FALSE; |
| 229 | |
| 230 | guint rank = gst_plugin_feature_get_rank(feature); |
| 231 | if (rank < GST_RANK_MARGINAL) |
| 232 | return FALSE; |
| 233 | |
| 234 | return TRUE; |
| 235 | } |
| 236 | |
| 237 | static gint compare_plugin_func(const void *item1, const void *item2) |
| 238 | { |
| 239 | GstPluginFeature *f1 = reinterpret_cast<GstPluginFeature *>(const_cast<void *>(item1)); |
| 240 | GstPluginFeature *f2 = reinterpret_cast<GstPluginFeature *>(const_cast<void *>(item2)); |
| 241 | |
| 242 | gint diff = gst_plugin_feature_get_rank(f2) - gst_plugin_feature_get_rank(f1); |
| 243 | if (diff != 0) |
| 244 | return diff; |
| 245 | |
| 246 | return strcmp(gst_plugin_feature_get_name(f1), gst_plugin_feature_get_name (f2)); |
| 247 | } |
| 248 | #endif |
| 249 | |
| 250 | GList *QGstCodecsInfo::elementFactories(ElementType elementType) const |
| 251 | { |
| 252 | #if GST_CHECK_VERSION(0,10,31) |
| 253 | GstElementFactoryListType gstElementType = 0; |
| 254 | switch (elementType) { |
| 255 | case AudioEncoder: |
| 256 | gstElementType = GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER; |
| 257 | break; |
| 258 | case VideoEncoder: |
| 259 | gstElementType = GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER; |
| 260 | break; |
| 261 | case Muxer: |
| 262 | gstElementType = GST_ELEMENT_FACTORY_TYPE_MUXER; |
| 263 | break; |
| 264 | } |
| 265 | |
| 266 | GList *list = gst_element_factory_list_get_elements(type: gstElementType, minrank: GST_RANK_MARGINAL); |
| 267 | if (elementType == AudioEncoder) { |
| 268 | // Manually add "audioconvert" to the list |
| 269 | // to allow linking with various containers. |
| 270 | auto factory = gst_element_factory_find(name: "audioconvert" ); |
| 271 | if (factory) |
| 272 | list = g_list_prepend(list, data: factory); |
| 273 | } |
| 274 | |
| 275 | return list; |
| 276 | #else |
| 277 | GList *result = gst_registry_feature_filter(gst_registry_get_default(), |
| 278 | element_filter, |
| 279 | FALSE, &elementType); |
| 280 | result = g_list_sort(result, compare_plugin_func); |
| 281 | return result; |
| 282 | #endif |
| 283 | } |
| 284 | |
| 285 | QSet<QString> QGstCodecsInfo::supportedStreamTypes(const QString &codec) const |
| 286 | { |
| 287 | return m_streamTypes.value(akey: codec); |
| 288 | } |
| 289 | |
| 290 | QStringList QGstCodecsInfo::supportedCodecs(const QSet<QString> &types) const |
| 291 | { |
| 292 | QStringList result; |
| 293 | for (auto &candidate : supportedCodecs()) { |
| 294 | auto candidateTypes = supportedStreamTypes(codec: candidate); |
| 295 | if (candidateTypes.intersects(other: types)) |
| 296 | result << candidate; |
| 297 | } |
| 298 | |
| 299 | return result; |
| 300 | } |
| 301 | |