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 | |