April 2013

GStreamer: Getting a list of plug-ins and elements

I’ve been tinkering with GStreamer lately, specifically QtGStreamer, since Qt is my preferred UI toolkit.

One thing I wanted to be able to do, is to programatically generate a list of all plug-ins and elements accessible to the application. My end goal was to allow a user to select audio devices for input/output.

Now, I could just try the suck-it-and-see approach, attempting to guess the names of elements. This could work, but suppose someone wanted to use an element other than the ones blessed enough to be included in your list?

Most of the audio source and sink elements have similar parameters, and the parameters can be discovered at run-time anyway. The bulk of them seem to accept a “device” parameter, which can be probed to generate a list of possible devices.

This gives us an elegant way of letting the user specify what they want. Known elements can be configured with specialised UI forms, but anything else, there’s a way to at least present the options to the user and allow them to configure it.

/*!
 * @file gstinfo.h
 */
#ifndef _GSTINFO_H
#define _GSTINFO_H

#include <QList>
#include <QString>

/*!
 * Get a list of all GStreamer plug-ins installed
 * @param       list    A list that will be populated with the names
 *                      of installed plug-ins.
 */
void GstGetPlugins(QList<QString>& list);

/*!
 * Get a list of all elements provided by the given GStreamer plug-in.
 * @param       plugin  The plug-in to query
 * @param       list    A list that will be populated with the names
 *                      of elements provided by this plug-in.
 */
void GstGetElements(const QString& plugin, QList<QString>& list);

#endif

/*!
 * @file gstinfo.cpp
 */
#include "gstinfo.h"
#include <gst/controller/gstcontroller.h>

/*!
 * Get a list of all GStreamer plug-ins installed
 * @param       list    A list that will be populated with the names
 *                      of installed plug-ins.
 */
void GstGetPlugins(QList<QString>& list) {
        /*
         * This code is partially based on code observed in gst-inspect.c
         * from GStreamer release 0.10.36.
         *
         * Original copyright:
         * GStreamer
         * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
         *               2000 Wim Taymans <wtay@chello.be>
         *               2004 Thomas Vander Stichele <thomas@apestaart.org>
         */
        GList* plugins; /* The head of the plug-in list */
        GList* pnode;   /* The currently viewed node */

        /* Empty the list out here */
        list.clear();

        plugins = pnode = gst_default_registry_get_plugin_list();
        while(pnode) {
                /* plugin: the plug-in info object pointed to by pnode */
                GstPlugin* plugin = (GstPlugin*)pnode->data;
                list << QString(plugin->desc.name);
                pnode = g_list_next(pnode);
        }

        /* Clean-up */
        gst_plugin_list_free (plugins);
}

/*!
 * Get a list of all elements provided by the given GStreamer plug-in.
 * @param       plugin  The plug-in to query
 * @param       list    A list that will be populated with the names
 *                      of elements provided by this plug-in.
 */
void GstGetElements(const QString& plugin, QList<QString>& list) {
        /*
         * This code is partially based on code observed in gst-inspect.c
         * from GStreamer release 0.10.36.
         *
         * Original copyright:
         * GStreamer
         * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
         *               2000 Wim Taymans <wtay@chello.be>
         *               2004 Thomas Vander Stichele <thomas@apestaart.org>
         */
        GList* features;        /* The list of plug-in features */
        GList* fnode;           /* The currently viewed node */

        /* Empty the list out here */
        list.clear();

        features = fnode = gst_registry_get_feature_list_by_plugin(
                        gst_registry_get_default(),
                        plugin.toUtf8().data());
        while(fnode) {
                if (fnode->data) {
                        /* Currently pointed-to feature */
                        GstPluginFeature* feature
                                = GST_PLUGIN_FEATURE(fnode->data);

                        if (GST_IS_ELEMENT_FACTORY (feature)) {
                                GstElementFactory* factory
                                        = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature));
                                list << QString(GST_PLUGIN_FEATURE_NAME(factory));
                        }
                }
                fnode = g_list_next(fnode);
        }
        gst_plugin_feature_list_free(features);
}

How does one use this?

	QList<QString> plugins;
	QList<QString>::iterator p_it;

	GstGetPlugins(plugins);
	for (p_it = plugins.begin(); p_it != plugins.end(); p_it++) {
		QList<QString> elements;
		QList<QString>::iterator e_it;
		GstGetElements(*p_it, elements);
		for (e_it = elements.begin(); e_it != elements.end(); e_it++) {
			std::cout	<< "Plug-in "
					<< p_it->toStdString()
					<< " Element "
					<< e_it->toStdString()
					<< std::endl;
		}
	}

OpenERP function fields, and the store= parameter

This is another one of those brain-RAM-to-blog-NVRAM dumps for my own future reference as much as anyone else’s benefit.  One thing I could never quite get my head around was the store= parameter in OpenERP.

OpenERP explains it like this:

store Parameter

It will calculate the field and store the result in the table. The field will be recalculated when certain fields are changed on other objects. It uses the following syntax:

store = {
    'object_name': (
            function_name,
            ['field_name1', 'field_name2'],
            priority)
}

It will call function function_name when any changes are written to fields in the list [‘field1’,’field2’] on object ‘object_name’. The function should have the following signature:

def function_name(self, cr, uid, ids, context=None):

Where ids will be the ids of records in the other object’s table that have changed values in the watched fields. The function should return a list of ids of records in its own table that should have the field recalculated. That list will be sent as a parameter for the main function of the field.

Now note the parameter self. Quite often, these are defined as methods of the object that owns the given function field. self, in the Python vernacular, is similar to the this keyword in C++; it is a reference to the object that owns the method. And here, the use of the name self is misleading.

I had occasion to derive a few timesheet objects so that I could rename a few fields. I didn’t want to change the implementation, just the name. And I wanted to do it in one place, not go do a search and replace on each and every view for the timesheet. The model seemed the most appropriate place for it. Unfortunately, the only way you change a field name in this way, is to copy and paste the definition.

So I tried adding this to my derived model (note, we were able to leave _progress_rate alone, since I had in fact overridden that method in this same object to fix a bug in the original, otherwise I’d use the same lambda trick here):

        'planned_hours': fields.function(
            _progress_rate,
            multi="progress", string='Total Planned Time',
            help=   "Sum of hours planned by the project manager for all "  \
                    "tasks related to this project and its child projects",
            store = {
                'project.project': (
                    lambda self, cr, uid, ids, context=None :               \
                        self._get_project_and_parents(                      \
                            cr, uid, ids, context),
                    ['tasks', 'parent_id', 'child_ids'], 10),
                'project.task': (
                    lambda self, cr, uid, ids, context=None :               \
                        self._get_projects_from_tasks(                      \
                            cr, uid, ids, context),
                    ['planned_hours', 'remaining_hours',
                    'work_ids', 'state'], 20),
            }),

The lambda functions are just a quick way of picking up the functions that would later be inherited by the base class (handled by the osv.osv object).

Imagine my surprise when I get told that there is no attribute called _get_projects_from_tasks. … Hang on, I’m sure that’s what it’s called! I check again, yes, I spelt it correctly. I look closer at the backtrace:

AttributeError: 'project.task' object has no attribute '_get_projects_from_tasks'

I’ve underlined the significant bit I had missed earlier. Despite the fact that this _get_project_from_tasks is in fact, defined as a method in project.project, the argument that’s passed in as self is not a project.project instance, but a project.task.

self is not in fact, self, but another object entirely.

So from now on, I shall no longer call this parameter self, as this will trip regular Python programmers up — it should be called what it is. My field definition now looks like this:

        'planned_hours': fields.function(
            _progress_rate,
            multi="progress", string='Total Planned Time',
            help=   "Sum of hours planned by the project manager for all "  \
                    "tasks related to this project and its child projects",
            store={
                'project.project': (
                    lambda project_obj, cr, uid, ids, context=None: \
                        project_obj._get_project_and_parents(cr, uid, ids, context),
                    ['tasks', 'parent_id', 'child_ids'],
                    10
                ),
                'project.task': (
                    lambda task_obj, cr, uid, ids, context=None:    \
                        task_obj.pool.get('project.project'         \
                            )._get_projects_from_tasks(cr, uid,     \
                                ids, context),
                    ['planned_hours', 'remaining_hours', 'work_ids', 'state'],
                    20
                ),
            }),

Hopefully that should be clear next time I, or anyone else, comes across it.