blob: 2522ee19c38232a8c5025e75ff9e754df95df52b [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgstcodecsinfo_p.h"
#include "qgstutils_p.h"
#include <QtCore/qset.h>
#include <gst/pbutils/pbutils.h>
static QSet<QString> streamTypes(GstElementFactory *factory, GstPadDirection direction)
{
QSet<QString> types;
const GList *pads = gst_element_factory_get_static_pad_templates(factory);
for (const GList *pad = pads; pad; pad = g_list_next(pad)) {
GstStaticPadTemplate *templ = reinterpret_cast<GstStaticPadTemplate *>(pad->data);
if (templ->direction == direction) {
GstCaps *caps = gst_static_caps_get(&templ->static_caps);
for (uint i = 0; i < gst_caps_get_size(caps); ++i) {
GstStructure *structure = gst_caps_get_structure(caps, i);
types.insert(QString::fromUtf8(gst_structure_get_name(structure)));
}
gst_caps_unref(caps);
}
}
return types;
}
QGstCodecsInfo::QGstCodecsInfo(QGstCodecsInfo::ElementType elementType)
{
updateCodecs(elementType);
for (auto &codec : supportedCodecs()) {
GstElementFactory *factory = gst_element_factory_find(codecElement(codec).constData());
if (factory) {
GstPadDirection direction = elementType == Muxer ? GST_PAD_SINK : GST_PAD_SRC;
m_streamTypes.insert(codec, streamTypes(factory, direction));
gst_object_unref(GST_OBJECT(factory));
}
}
}
QStringList QGstCodecsInfo::supportedCodecs() const
{
return m_codecs;
}
QString QGstCodecsInfo::codecDescription(const QString &codec) const
{
return m_codecInfo.value(codec).description;
}
QByteArray QGstCodecsInfo::codecElement(const QString &codec) const
{
return m_codecInfo.value(codec).elementName;
}
QStringList QGstCodecsInfo::codecOptions(const QString &codec) const
{
QStringList options;
QByteArray elementName = m_codecInfo.value(codec).elementName;
if (elementName.isEmpty())
return options;
GstElement *element = gst_element_factory_make(elementName, nullptr);
if (element) {
guint numProperties;
GParamSpec **properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(element),
&numProperties);
for (guint j = 0; j < numProperties; ++j) {
GParamSpec *property = properties[j];
// ignore some properties
if (strcmp(property->name, "name") == 0 || strcmp(property->name, "parent") == 0)
continue;
options.append(QLatin1String(property->name));
}
g_free(properties);
gst_object_unref(element);
}
return options;
}
void QGstCodecsInfo::updateCodecs(ElementType elementType)
{
m_codecs.clear();
m_codecInfo.clear();
GList *elements = elementFactories(elementType);
QSet<QByteArray> fakeEncoderMimeTypes;
fakeEncoderMimeTypes << "unknown/unknown"
<< "audio/x-raw-int" << "audio/x-raw-float"
<< "video/x-raw-yuv" << "video/x-raw-rgb";
QSet<QByteArray> fieldsToAdd;
fieldsToAdd << "mpegversion" << "layer" << "layout" << "raversion"
<< "wmaversion" << "wmvversion" << "variant" << "systemstream";
GList *element = elements;
while (element) {
GstElementFactory *factory = (GstElementFactory *)element->data;
element = element->next;
const GList *padTemplates = gst_element_factory_get_static_pad_templates(factory);
while (padTemplates) {
GstStaticPadTemplate *padTemplate = (GstStaticPadTemplate *)padTemplates->data;
padTemplates = padTemplates->next;
if (padTemplate->direction == GST_PAD_SRC) {
GstCaps *caps = gst_static_caps_get(&padTemplate->static_caps);
for (uint i=0; i<gst_caps_get_size(caps); i++) {
const GstStructure *structure = gst_caps_get_structure(caps, i);
//skip "fake" encoders
if (fakeEncoderMimeTypes.contains(gst_structure_get_name(structure)))
continue;
GstStructure *newStructure = qt_gst_structure_new_empty(gst_structure_get_name(structure));
//add structure fields to distinguish between formats with similar mime types,
//like audio/mpeg
for (int j=0; j<gst_structure_n_fields(structure); j++) {
const gchar* fieldName = gst_structure_nth_field_name(structure, j);
if (fieldsToAdd.contains(fieldName)) {
const GValue *value = gst_structure_get_value(structure, fieldName);
GType valueType = G_VALUE_TYPE(value);
//don't add values of range type,
//gst_pb_utils_get_codec_description complains about not fixed caps
if (valueType != GST_TYPE_INT_RANGE && valueType != GST_TYPE_DOUBLE_RANGE &&
valueType != GST_TYPE_FRACTION_RANGE && valueType != GST_TYPE_LIST &&
valueType != GST_TYPE_ARRAY)
gst_structure_set_value(newStructure, fieldName, value);
}
}
GstCaps *newCaps = gst_caps_new_full(newStructure, nullptr);
gchar *capsString = gst_caps_to_string(newCaps);
QString codec = QLatin1String(capsString);
if (capsString)
g_free(capsString);
GstRank rank = GstRank(gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory)));
// If two elements provide the same codec, use the highest ranked one
QMap<QString, CodecInfo>::const_iterator it = m_codecInfo.constFind(codec);
if (it == m_codecInfo.constEnd() || it->rank < rank) {
if (it == m_codecInfo.constEnd())
m_codecs.append(codec);
CodecInfo info;
info.elementName = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
gchar *description = gst_pb_utils_get_codec_description(newCaps);
info.description = QString::fromUtf8(description);
if (description)
g_free(description);
info.rank = rank;
m_codecInfo.insert(codec, info);
}
gst_caps_unref(newCaps);
}
gst_caps_unref(caps);
}
}
}
gst_plugin_feature_list_free(elements);
}
#if !GST_CHECK_VERSION(0, 10, 31)
static gboolean element_filter(GstPluginFeature *feature, gpointer user_data)
{
if (Q_UNLIKELY(!GST_IS_ELEMENT_FACTORY(feature)))
return FALSE;
const QGstCodecsInfo::ElementType type = *reinterpret_cast<QGstCodecsInfo::ElementType *>(user_data);
const gchar *klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature));
if (type == QGstCodecsInfo::AudioEncoder && !(strstr(klass, "Encoder") && strstr(klass, "Audio")))
return FALSE;
if (type == QGstCodecsInfo::VideoEncoder && !(strstr(klass, "Encoder") && strstr(klass, "Video")))
return FALSE;
if (type == QGstCodecsInfo::Muxer && !strstr(klass, "Muxer"))
return FALSE;
guint rank = gst_plugin_feature_get_rank(feature);
if (rank < GST_RANK_MARGINAL)
return FALSE;
return TRUE;
}
static gint compare_plugin_func(const void *item1, const void *item2)
{
GstPluginFeature *f1 = reinterpret_cast<GstPluginFeature *>(const_cast<void *>(item1));
GstPluginFeature *f2 = reinterpret_cast<GstPluginFeature *>(const_cast<void *>(item2));
gint diff = gst_plugin_feature_get_rank(f2) - gst_plugin_feature_get_rank(f1);
if (diff != 0)
return diff;
return strcmp(gst_plugin_feature_get_name(f1), gst_plugin_feature_get_name (f2));
}
#endif
GList *QGstCodecsInfo::elementFactories(ElementType elementType) const
{
#if GST_CHECK_VERSION(0,10,31)
GstElementFactoryListType gstElementType = 0;
switch (elementType) {
case AudioEncoder:
gstElementType = GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER;
break;
case VideoEncoder:
gstElementType = GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER;
break;
case Muxer:
gstElementType = GST_ELEMENT_FACTORY_TYPE_MUXER;
break;
}
GList *list = gst_element_factory_list_get_elements(gstElementType, GST_RANK_MARGINAL);
if (elementType == AudioEncoder) {
// Manually add "audioconvert" to the list
// to allow linking with various containers.
auto factory = gst_element_factory_find("audioconvert");
if (factory)
list = g_list_prepend(list, factory);
}
return list;
#else
GList *result = gst_registry_feature_filter(gst_registry_get_default(),
element_filter,
FALSE, &elementType);
result = g_list_sort(result, compare_plugin_func);
return result;
#endif
}
QSet<QString> QGstCodecsInfo::supportedStreamTypes(const QString &codec) const
{
return m_streamTypes.value(codec);
}
QStringList QGstCodecsInfo::supportedCodecs(const QSet<QString> &types) const
{
QStringList result;
for (auto &candidate : supportedCodecs()) {
auto candidateTypes = supportedStreamTypes(candidate);
if (candidateTypes.intersects(types))
result << candidate;
}
return result;
}