Vala bindings and DeepStream

I find writing GObject C painful and error prone, and Python has it’s own limitations (speed, GIL). My understanding is that Vala/Genie bindings would be fairly simple to generate if GObject introspection annotations are already made on the source. I see that some documentation already exists on the supplied headers, eg:

...
/**
 * generates a unique user metadata type from the given string describing
 * user specific metadata.
 *
 * @param[in] meta_descriptor A pointer to string describing metadata.
 *            The format of the string should be specified as below
 *            ORG_NAME.COMPONENT_NAME.METADATA_DESCRIPTION.
 *            e.g. (NVIDIA.NVINFER.TENSOR_METADATA)
 */
NvDsMetaType nvds_get_user_meta_type(gchar *meta_descriptor);

But I am not sure if this is valid Gtk Doc format. When I try to use g-ir-scanner to generate a .gir to generate a .vapi, i get “ERROR: Malformed include …”. I assume that means it couldn’t parse the documentation (I say assume since not much comes up on a google).

C is not my most used language. Can anybody who is more familiar with GObject introspection than I advise on the best way to convert these annotations to Gtk Doc format (or better yet, a .gir) so I don’t have to manually generate .vapi bindings? It’s not urgent or anything (I won’t be able to play around with this until the end of March, at least), but it’s on my wish list of things that would be nice since Vala/Genie plays so well with gstreamer. I’ve done some sample apps with Vala and nvidia’s components and they work fine, but i need to parse NvDsMeta stuff and that can’t be done without either bindings, or writing, for example, my buffer callback in a separate file which does have bindings. I’m crossing my fingers for a GObject expert here.

Hi,

Sorry that we don’t have to much experience on Vala and doesn’t have a sample for it.

Here is our sample with GObject, you can check if this can give you some information:
https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/blob/master/notebooks/deepstream_test_4.ipynb

Thanks.

Thanks, AastaLLL, but my existing, working, thing is in Python, which is great. What I want to do is port it to Genie (a Vala syntax). 90% of that is very simple (see example below) and more or less identical to the Python. Below is mostly working code:

class Mce: Object
	// stuff with an underscrore is declared private automatically
	pipeline:Gst.Pipeline
	_loop:GLib.MainLoop
	// pipeline elements
	_sources:list of Gst.Element
	_pie:Gst.Element?
	_sink:Gst.Element
	_source_counter:int = 0

	construct(loop:GLib.MainLoop, pie_config:string?, sources:array of string)
		// parameters with a ? can be null
		self.pipeline = new Gst.Pipeline(null)
		// connect the bus callback
		bus:Gst.Bus = self.pipeline.get_bus()
		bus.add_watch(GLib.Priority.DEFAULT, self._on_message)

		self._loop = loop

		// create pipeline elements

		// first, the primary inference engine (pie)
#if TEGRA
		// todo: put stream muxer here
		self._pie = Gst.ElementFactory.make("nvinfer", "pie")
		if pie_config is not null
			self._pie.set_property("config-file-path", pie_config)
		else
			self._pie.set_property("config-file-path", DEFAULT_CONFIG_FILE)
#else
		self._pie = null
#endif
		// create sources and connect them to the pie
		self._sources = new list of Gst.Element
		self._init_sink()
		self.add_sources(sources)

	// create end of pipeline
	def _init_sink()
#if TEGRA
		self._sink = Gst.ElementFactory.make("nvoverlaysink", "sink")
#else
		self._sink = Gst.ElementFactory.make("autovideosink", "sink")
#endif
		if self._sink == null
			error("failed to create sink")
		self.pipeline.add(self._sink)

	def _on_message(bus: Gst.Bus, message: Gst.Message) : bool
		// note: in Genie there is no fallthrough in a case block, so no need to break;
		case message.type
			when Gst.MessageType.EOS
				GLib.message("Got EOS")
				self.quit()
			when Gst.MessageType.ERROR
				err:GLib.Error
				debug:string
				message.parse_error(out err, out debug)
				if err.code != 3  // window closed
					error(@"$(err.code):$(err.message):$(debug)")
				self.quit()
			when Gst.MessageType.WARNING
				err:GLib.Error
				debug:string
				message.parse_warning(out err, out debug)
				warning(@"$(err.code):$(err.message):$(debug)")
#if DEBUG
			default
				debug(@"BUS_MSG:$(message.src.name):$(message.type.get_name())")
#endif
		return true

	def add_source(source: string)
		bin:Gst.Bin = new Gst.Bin(source)
		// create source elements
		filesource:Gst.Element = Gst.ElementFactory.make("filesrc", @"filesrc_$(self._source_counter)")
		filesource.set("location", source)
		parser:Gst.Element = Gst.ElementFactory.make("h264parse", @"parser_$(self._source_counter)")
		decoder:Gst.Element = Gst.ElementFactory.make("vaapih264dec", @"decoder_$(self._source_counter)")
		bin.add_many(filesource, parser, decoder)
		if not filesource.link_many(parser, decoder)
			warning(@"could not link elements of $source")
			return
		self._sources.add(bin)
		self.pipeline.add(bin)
		if not decoder.link(self._sink)
			warning(@"could not link source to sink for $source")
			return
		self._source_counter += 1
		// TODO: link to the nvstreammux, by requesting a pad, etc, handle eos

	def add_sources(sources: array of string)
		for source in sources
			self.add_source(source)

	def play() : bool
		//  if self._source_bins.is_empty()
		//  	GLib.error("cannot start pipeline with no source bins")
		if self._source_counter == 0
			warning("cannot start playing becuase no sources added.")
			return false
		self.pipeline.set_state(Gst.State.PLAYING)
		if not self._loop.is_running()
			self._loop.run()
		return true

	def quit()
		self.pipeline.set_state(Gst.State.NULL)
		self._loop.quit()

When passed through the vala compiler, that results in:

/* mce.c generated by valac 0.47.2, the Vala compiler
 * generated from mce.gs, do not modify */

/* mce.gs
 *
 * Copyright 2020 Michael de Gans
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the sale,
 * use or other dealings in this Software without prior written
 * authorization.
 */

#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <gee.h>

#define TYPE_CLASS_ID (class_id_get_type ())
#define CLASS_ID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_CLASS_ID, ClassID))
#define CLASS_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_CLASS_ID, ClassIDClass))
#define IS_CLASS_ID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_CLASS_ID))
#define IS_CLASS_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_CLASS_ID))
#define CLASS_ID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_CLASS_ID, ClassIDClass))

typedef struct _ClassID ClassID;
typedef struct _ClassIDClass ClassIDClass;
typedef struct _ClassIDPrivate ClassIDPrivate;
enum  {
	CLASS_ID_0_PROPERTY,
	CLASS_ID_NUM_PROPERTIES
};
static GParamSpec* class_id_properties[CLASS_ID_NUM_PROPERTIES];

#define TYPE_MCE (mce_get_type ())
#define MCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_MCE, Mce))
#define MCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_MCE, MceClass))
#define IS_MCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_MCE))
#define IS_MCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_MCE))
#define MCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_MCE, MceClass))

typedef struct _Mce Mce;
typedef struct _MceClass MceClass;
typedef struct _McePrivate McePrivate;
enum  {
	MCE_0_PROPERTY,
	MCE_NUM_PROPERTIES
};
static GParamSpec* mce_properties[MCE_NUM_PROPERTIES];
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
#define _g_main_loop_unref0(var) ((var == NULL) ? NULL : (var = (g_main_loop_unref (var), NULL)))
#define _g_error_free0(var) ((var == NULL) ? NULL : (var = (g_error_free (var), NULL)))
#define _g_free0(var) (var = (g_free (var), NULL))

struct _ClassID {
	GObject parent_instance;
	ClassIDPrivate * priv;
};

struct _ClassIDClass {
	GObjectClass parent_class;
};

struct _Mce {
	GObject parent_instance;
	McePrivate * priv;
	GstPipeline* pipeline;
};

struct _MceClass {
	GObjectClass parent_class;
};

struct _McePrivate {
	GMainLoop* _loop;
	GeeArrayList* _sources;
	GstElement* _pie;
	GstElement* _sink;
	gint _source_counter;
};

static gpointer class_id_parent_class = NULL;
static gint Mce_private_offset;
static gpointer mce_parent_class = NULL;

#define DEFAULT_CONFIG_FILE "~/.mce/pie.conf"
GType class_id_get_type (void) G_GNUC_CONST;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClassID, g_object_unref)
#define CLASS_ID_VEHICLE 0
#define CLASS_ID_BICYCLE 1
#define CLASS_ID_PERSON 2
#define CLASS_ID_ROADSIGN 3
ClassID* class_id_new (void);
ClassID* class_id_construct (GType object_type);
static GType class_id_get_type_once (void);
GType mce_get_type (void) G_GNUC_CONST;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Mce, g_object_unref)
Mce* mce_new (GMainLoop* loop,
              const gchar* pie_config,
              gchar** sources,
              gint sources_length1);
Mce* mce_construct (GType object_type,
                    GMainLoop* loop,
                    const gchar* pie_config,
                    gchar** sources,
                    gint sources_length1);
static gboolean _mce_on_message (Mce* self,
                          GstBus* bus,
                          GstMessage* message);
static gboolean __mce_on_message_gst_bus_func (GstBus* bus,
                                        GstMessage* message,
                                        gpointer self);
static void _mce_init_sink (Mce* self);
void mce_add_sources (Mce* self,
                      gchar** sources,
                      gint sources_length1);
void mce_quit (Mce* self);
void mce_add_source (Mce* self,
                     const gchar* source);
gboolean mce_play (Mce* self);
static void mce_finalize (GObject * obj);
static GType mce_get_type_once (void);

ClassID*
class_id_construct (GType object_type)
{
	ClassID * self = NULL;
#line 33 "../src/mce.gs"
	self = (ClassID*) g_object_new (object_type, NULL);
#line 33 "../src/mce.gs"
	return self;
#line 154 "mce.c"
}

ClassID*
class_id_new (void)
{
#line 33 "../src/mce.gs"
	return class_id_construct (TYPE_CLASS_ID);
#line 162 "mce.c"
}

static void
class_id_class_init (ClassIDClass * klass,
                     gpointer klass_data)
{
#line 33 "../src/mce.gs"
	class_id_parent_class = g_type_class_peek_parent (klass);
#line 171 "mce.c"
}

static void
class_id_instance_init (ClassID * self,
                        gpointer klass)
{
}

static GType
class_id_get_type_once (void)
{
	static const GTypeInfo g_define_type_info = { sizeof (ClassIDClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) class_id_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (ClassID), 0, (GInstanceInitFunc) class_id_instance_init, NULL };
	GType class_id_type_id;
	class_id_type_id = g_type_register_static (G_TYPE_OBJECT, "ClassID", &g_define_type_info, 0);
	return class_id_type_id;
}

GType
class_id_get_type (void)
{
	static volatile gsize class_id_type_id__volatile = 0;
	if (g_once_init_enter (&class_id_type_id__volatile)) {
		GType class_id_type_id;
		class_id_type_id = class_id_get_type_once ();
		g_once_init_leave (&class_id_type_id__volatile, class_id_type_id);
	}
	return class_id_type_id__volatile;
}

static inline gpointer
mce_get_instance_private (Mce* self)
{
	return G_STRUCT_MEMBER_P (self, Mce_private_offset);
}

static gboolean
__mce_on_message_gst_bus_func (GstBus* bus,
                               GstMessage* message,
                               gpointer self)
{
	gboolean result;
	result = _mce_on_message ((Mce*) self, bus, message);
#line 54 "../src/mce.gs"
	return result;
#line 216 "mce.c"
}

static gpointer
_g_main_loop_ref0 (gpointer self)
{
#line 56 "../src/mce.gs"
	return self ? g_main_loop_ref (self) : NULL;
#line 224 "mce.c"
}

Mce*
mce_construct (GType object_type,
               GMainLoop* loop,
               const gchar* pie_config,
               gchar** sources,
               gint sources_length1)
{
	Mce * self = NULL;
	GstPipeline* _tmp0_;
	GstBus* bus = NULL;
	GstPipeline* _tmp1_;
	GstBus* _tmp2_;
	GMainLoop* _tmp3_;
	GeeArrayList* _tmp4_;
#line 50 "../src/mce.gs"
	g_return_val_if_fail (loop != NULL, NULL);
#line 50 "../src/mce.gs"
	self = (Mce*) g_object_new (object_type, NULL);
#line 51 "../src/mce.gs"
	_tmp0_ = (GstPipeline*) gst_pipeline_new (NULL);
#line 51 "../src/mce.gs"
	g_object_ref_sink (_tmp0_);
#line 51 "../src/mce.gs"
	_g_object_unref0 (self->pipeline);
#line 51 "../src/mce.gs"
	self->pipeline = _tmp0_;
#line 53 "../src/mce.gs"
	_tmp1_ = self->pipeline;
#line 53 "../src/mce.gs"
	_tmp2_ = gst_pipeline_get_bus (_tmp1_);
#line 53 "../src/mce.gs"
	bus = _tmp2_;
#line 54 "../src/mce.gs"
	gst_bus_add_watch_full (bus, G_PRIORITY_DEFAULT, __mce_on_message_gst_bus_func, g_object_ref (self), g_object_unref);
#line 56 "../src/mce.gs"
	_tmp3_ = _g_main_loop_ref0 (loop);
#line 56 "../src/mce.gs"
	_g_main_loop_unref0 (self->priv->_loop);
#line 56 "../src/mce.gs"
	self->priv->_loop = _tmp3_;
#line 68 "../src/mce.gs"
	_g_object_unref0 (self->priv->_pie);
#line 68 "../src/mce.gs"
	self->priv->_pie = NULL;
#line 71 "../src/mce.gs"
	_tmp4_ = gee_array_list_new (gst_element_get_type (), (GBoxedCopyFunc) g_object_ref, (GDestroyNotify) g_object_unref, NULL, NULL, NULL);
#line 71 "../src/mce.gs"
	_g_object_unref0 (self->priv->_sources);
#line 71 "../src/mce.gs"
	self->priv->_sources = _tmp4_;
#line 72 "../src/mce.gs"
	_mce_init_sink (self);
#line 73 "../src/mce.gs"
	mce_add_sources (self, sources, (gint) sources_length1);
#line 50 "../src/mce.gs"
	_g_object_unref0 (bus);
#line 50 "../src/mce.gs"
	return self;
#line 285 "mce.c"
}

Mce*
mce_new (GMainLoop* loop,
         const gchar* pie_config,
         gchar** sources,
         gint sources_length1)
{
#line 50 "../src/mce.gs"
	return mce_construct (TYPE_MCE, loop, pie_config, sources, sources_length1);
#line 296 "mce.c"
}

static void
_mce_init_sink (Mce* self)
{
	GstElement* _tmp0_;
	GstElement* _tmp1_;
	GstPipeline* _tmp2_;
	GstElement* _tmp3_;
#line 76 "../src/mce.gs"
	g_return_if_fail (self != NULL);
#line 80 "../src/mce.gs"
	_tmp0_ = gst_element_factory_make ("autovideosink", "sink");
#line 80 "../src/mce.gs"
	if (_tmp0_ != NULL) {
#line 80 "../src/mce.gs"
		g_object_ref_sink (_tmp0_);
#line 314 "mce.c"
	}
#line 80 "../src/mce.gs"
	_g_object_unref0 (self->priv->_sink);
#line 80 "../src/mce.gs"
	self->priv->_sink = _tmp0_;
#line 82 "../src/mce.gs"
	_tmp1_ = self->priv->_sink;
#line 82 "../src/mce.gs"
	if (_tmp1_ == NULL) {
#line 83 "../src/mce.gs"
		g_error ("mce.gs:83: failed to create sink");
#line 326 "mce.c"
	}
#line 84 "../src/mce.gs"
	_tmp2_ = self->pipeline;
#line 84 "../src/mce.gs"
	_tmp3_ = self->priv->_sink;
#line 84 "../src/mce.gs"
	gst_bin_add ((GstBin*) _tmp2_, _tmp3_);
#line 334 "mce.c"
}

static const gchar*
string_to_string (const gchar* self)
{
	const gchar* result = NULL;
#line 1570 "glib-2.0.vapi"
	g_return_val_if_fail (self != NULL, NULL);
#line 1571 "glib-2.0.vapi"
	result = self;
#line 1571 "glib-2.0.vapi"
	return result;
#line 347 "mce.c"
}

static gboolean
_mce_on_message (Mce* self,
                 GstBus* bus,
                 GstMessage* message)
{
	GstMessageType _tmp0_;
	gboolean result = FALSE;
#line 86 "../src/mce.gs"
	g_return_val_if_fail (self != NULL, FALSE);
#line 86 "../src/mce.gs"
	g_return_val_if_fail (bus != NULL, FALSE);
#line 86 "../src/mce.gs"
	g_return_val_if_fail (message != NULL, FALSE);
#line 88 "../src/mce.gs"
	_tmp0_ = message->type;
#line 88 "../src/mce.gs"
	switch (_tmp0_) {
#line 88 "../src/mce.gs"
		case GST_MESSAGE_EOS:
#line 369 "mce.c"
		{
			{
#line 90 "../src/mce.gs"
				g_message ("mce.gs:90: Got EOS");
#line 91 "../src/mce.gs"
				mce_quit (self);
#line 376 "mce.c"
			}
#line 88 "../src/mce.gs"
			break;
#line 380 "mce.c"
		}
#line 88 "../src/mce.gs"
		case GST_MESSAGE_ERROR:
#line 384 "mce.c"
		{
			{
				GError* err = NULL;
				gchar* debug = NULL;
				GError* _tmp1_ = NULL;
				gchar* _tmp2_ = NULL;
				GError* _tmp3_;
#line 95 "../src/mce.gs"
				gst_message_parse_error (message, &_tmp1_, &_tmp2_);
#line 95 "../src/mce.gs"
				_g_error_free0 (err);
#line 95 "../src/mce.gs"
				err = _tmp1_;
#line 95 "../src/mce.gs"
				_g_free0 (debug);
#line 95 "../src/mce.gs"
				debug = _tmp2_;
#line 96 "../src/mce.gs"
				_tmp3_ = err;
#line 96 "../src/mce.gs"
				if (_tmp3_->code != 3) {
#line 406 "mce.c"
					GError* _tmp4_;
					gchar* _tmp5_;
					gchar* _tmp6_;
					GError* _tmp7_;
					const gchar* _tmp8_;
					const gchar* _tmp9_;
					const gchar* _tmp10_;
					const gchar* _tmp11_;
					gchar* _tmp12_;
					gchar* _tmp13_;
#line 97 "../src/mce.gs"
					_tmp4_ = err;
#line 97 "../src/mce.gs"
					_tmp5_ = g_strdup_printf ("%i", _tmp4_->code);
#line 97 "../src/mce.gs"
					_tmp6_ = _tmp5_;
#line 97 "../src/mce.gs"
					_tmp7_ = err;
#line 97 "../src/mce.gs"
					_tmp8_ = _tmp7_->message;
#line 97 "../src/mce.gs"
					_tmp9_ = string_to_string (_tmp8_);
#line 97 "../src/mce.gs"
					_tmp10_ = debug;
#line 97 "../src/mce.gs"
					_tmp11_ = string_to_string (_tmp10_);
#line 97 "../src/mce.gs"
					_tmp12_ = g_strconcat (_tmp6_, ":", _tmp9_, ":", _tmp11_, NULL);
#line 97 "../src/mce.gs"
					_tmp13_ = _tmp12_;
#line 97 "../src/mce.gs"
					g_error ("mce.gs:97: %s", _tmp13_);
#line 97 "../src/mce.gs"
					_g_free0 (_tmp13_);
#line 97 "../src/mce.gs"
					_g_free0 (_tmp6_);
#line 443 "mce.c"
				}
#line 98 "../src/mce.gs"
				mce_quit (self);
#line 93 "../src/mce.gs"
				_g_free0 (debug);
#line 93 "../src/mce.gs"
				_g_error_free0 (err);
#line 451 "mce.c"
			}
#line 88 "../src/mce.gs"
			break;
#line 455 "mce.c"
		}
#line 88 "../src/mce.gs"
		case GST_MESSAGE_WARNING:
#line 459 "mce.c"
		{
			{
				GError* err = NULL;
				gchar* debug = NULL;
				GError* _tmp14_ = NULL;
				gchar* _tmp15_ = NULL;
				GError* _tmp16_;
				gchar* _tmp17_;
				gchar* _tmp18_;
				GError* _tmp19_;
				const gchar* _tmp20_;
				const gchar* _tmp21_;
				const gchar* _tmp22_;
				const gchar* _tmp23_;
				gchar* _tmp24_;
				gchar* _tmp25_;
#line 102 "../src/mce.gs"
				gst_message_parse_warning (message, &_tmp14_, &_tmp15_);
#line 102 "../src/mce.gs"
				_g_error_free0 (err);
#line 102 "../src/mce.gs"
				err = _tmp14_;
#line 102 "../src/mce.gs"
				_g_free0 (debug);
#line 102 "../src/mce.gs"
				debug = _tmp15_;
#line 103 "../src/mce.gs"
				_tmp16_ = err;
#line 103 "../src/mce.gs"
				_tmp17_ = g_strdup_printf ("%i", _tmp16_->code);
#line 103 "../src/mce.gs"
				_tmp18_ = _tmp17_;
#line 103 "../src/mce.gs"
				_tmp19_ = err;
#line 103 "../src/mce.gs"
				_tmp20_ = _tmp19_->message;
#line 103 "../src/mce.gs"
				_tmp21_ = string_to_string (_tmp20_);
#line 103 "../src/mce.gs"
				_tmp22_ = debug;
#line 103 "../src/mce.gs"
				_tmp23_ = string_to_string (_tmp22_);
#line 103 "../src/mce.gs"
				_tmp24_ = g_strconcat (_tmp18_, ":", _tmp21_, ":", _tmp23_, NULL);
#line 103 "../src/mce.gs"
				_tmp25_ = _tmp24_;
#line 103 "../src/mce.gs"
				g_warning ("mce.gs:103: %s", _tmp25_);
#line 103 "../src/mce.gs"
				_g_free0 (_tmp25_);
#line 103 "../src/mce.gs"
				_g_free0 (_tmp18_);
#line 100 "../src/mce.gs"
				_g_free0 (debug);
#line 100 "../src/mce.gs"
				_g_error_free0 (err);
#line 516 "mce.c"
			}
#line 88 "../src/mce.gs"
			break;
#line 520 "mce.c"
		}
		default:
		{
			{
				GstObject* _tmp26_;
				gchar* _tmp27_;
				gchar* _tmp28_;
				gchar* _tmp29_;
				const gchar* _tmp30_;
				GstMessageType _tmp31_;
				const gchar* _tmp32_;
				const gchar* _tmp33_;
				gchar* _tmp34_;
				gchar* _tmp35_;
#line 106 "../src/mce.gs"
				_tmp26_ = message->src;
#line 106 "../src/mce.gs"
				_tmp27_ = gst_object_get_name (_tmp26_);
#line 106 "../src/mce.gs"
				_tmp28_ = _tmp27_;
#line 106 "../src/mce.gs"
				_tmp29_ = _tmp28_;
#line 106 "../src/mce.gs"
				_tmp30_ = string_to_string (_tmp29_);
#line 106 "../src/mce.gs"
				_tmp31_ = message->type;
#line 106 "../src/mce.gs"
				_tmp32_ = gst_message_type_get_name (_tmp31_);
#line 106 "../src/mce.gs"
				_tmp33_ = string_to_string (_tmp32_);
#line 106 "../src/mce.gs"
				_tmp34_ = g_strconcat ("BUS_MSG:", _tmp30_, ":", _tmp33_, NULL);
#line 106 "../src/mce.gs"
				_tmp35_ = _tmp34_;
#line 106 "../src/mce.gs"
				g_debug ("mce.gs:106: %s", _tmp35_);
#line 106 "../src/mce.gs"
				_g_free0 (_tmp35_);
#line 106 "../src/mce.gs"
				_g_free0 (_tmp29_);
#line 561 "mce.c"
			}
#line 88 "../src/mce.gs"
			break;
#line 565 "mce.c"
		}
	}
#line 108 "../src/mce.gs"
	result = TRUE;
#line 108 "../src/mce.gs"
	return result;
#line 572 "mce.c"
}

void
mce_add_source (Mce* self,
                const gchar* source)
{
	GstBin* bin = NULL;
	GstBin* _tmp0_;
	GstElement* filesource = NULL;
	gchar* _tmp1_;
	gchar* _tmp2_;
	gchar* _tmp3_;
	gchar* _tmp4_;
	GstElement* _tmp5_;
	GstElement* _tmp6_;
	GstElement* _tmp7_;
	GstElement* parser = NULL;
	gchar* _tmp8_;
	gchar* _tmp9_;
	gchar* _tmp10_;
	gchar* _tmp11_;
	GstElement* _tmp12_;
	GstElement* _tmp13_;
	GstElement* decoder = NULL;
	gchar* _tmp14_;
	gchar* _tmp15_;
	gchar* _tmp16_;
	gchar* _tmp17_;
	GstElement* _tmp18_;
	GstElement* _tmp19_;
	GstBin* _tmp20_;
	GstElement* _tmp21_;
	GstElement* _tmp22_;
	GstElement* _tmp23_;
	GstElement* _tmp24_;
	GstElement* _tmp25_;
	GstElement* _tmp26_;
	GeeArrayList* _tmp30_;
	GstBin* _tmp31_;
	GstPipeline* _tmp32_;
	GstBin* _tmp33_;
	GstElement* _tmp34_;
	GstElement* _tmp35_;
#line 110 "../src/mce.gs"
	g_return_if_fail (self != NULL);
#line 110 "../src/mce.gs"
	g_return_if_fail (source != NULL);
#line 111 "../src/mce.gs"
	_tmp0_ = (GstBin*) gst_bin_new (source);
#line 111 "../src/mce.gs"
	g_object_ref_sink (_tmp0_);
#line 111 "../src/mce.gs"
	bin = _tmp0_;
#line 113 "../src/mce.gs"
	_tmp1_ = g_strdup_printf ("%i", self->priv->_source_counter);
#line 113 "../src/mce.gs"
	_tmp2_ = _tmp1_;
#line 113 "../src/mce.gs"
	_tmp3_ = g_strconcat ("filesrc_", _tmp2_, NULL);
#line 113 "../src/mce.gs"
	_tmp4_ = _tmp3_;
#line 113 "../src/mce.gs"
	_tmp5_ = gst_element_factory_make ("filesrc", _tmp4_);
#line 113 "../src/mce.gs"
	if (_tmp5_ != NULL) {
#line 113 "../src/mce.gs"
		g_object_ref_sink (_tmp5_);
#line 640 "mce.c"
	}
#line 113 "../src/mce.gs"
	_tmp6_ = _tmp5_;
#line 113 "../src/mce.gs"
	_g_free0 (_tmp4_);
#line 113 "../src/mce.gs"
	_g_free0 (_tmp2_);
#line 113 "../src/mce.gs"
	filesource = _tmp6_;
#line 114 "../src/mce.gs"
	_tmp7_ = filesource;
#line 114 "../src/mce.gs"
	g_object_set ((GObject*) _tmp7_, "location", source, NULL);
#line 115 "../src/mce.gs"
	_tmp8_ = g_strdup_printf ("%i", self->priv->_source_counter);
#line 115 "../src/mce.gs"
	_tmp9_ = _tmp8_;
#line 115 "../src/mce.gs"
	_tmp10_ = g_strconcat ("parser_", _tmp9_, NULL);
#line 115 "../src/mce.gs"
	_tmp11_ = _tmp10_;
#line 115 "../src/mce.gs"
	_tmp12_ = gst_element_factory_make ("h264parse", _tmp11_);
#line 115 "../src/mce.gs"
	if (_tmp12_ != NULL) {
#line 115 "../src/mce.gs"
		g_object_ref_sink (_tmp12_);
#line 668 "mce.c"
	}
#line 115 "../src/mce.gs"
	_tmp13_ = _tmp12_;
#line 115 "../src/mce.gs"
	_g_free0 (_tmp11_);
#line 115 "../src/mce.gs"
	_g_free0 (_tmp9_);
#line 115 "../src/mce.gs"
	parser = _tmp13_;
#line 116 "../src/mce.gs"
	_tmp14_ = g_strdup_printf ("%i", self->priv->_source_counter);
#line 116 "../src/mce.gs"
	_tmp15_ = _tmp14_;
#line 116 "../src/mce.gs"
	_tmp16_ = g_strconcat ("decoder_", _tmp15_, NULL);
#line 116 "../src/mce.gs"
	_tmp17_ = _tmp16_;
#line 116 "../src/mce.gs"
	_tmp18_ = gst_element_factory_make ("vaapih264dec", _tmp17_);
#line 116 "../src/mce.gs"
	if (_tmp18_ != NULL) {
#line 116 "../src/mce.gs"
		g_object_ref_sink (_tmp18_);
#line 692 "mce.c"
	}
#line 116 "../src/mce.gs"
	_tmp19_ = _tmp18_;
#line 116 "../src/mce.gs"
	_g_free0 (_tmp17_);
#line 116 "../src/mce.gs"
	_g_free0 (_tmp15_);
#line 116 "../src/mce.gs"
	decoder = _tmp19_;
#line 117 "../src/mce.gs"
	_tmp20_ = bin;
#line 117 "../src/mce.gs"
	_tmp21_ = filesource;
#line 117 "../src/mce.gs"
	_tmp22_ = parser;
#line 117 "../src/mce.gs"
	_tmp23_ = decoder;
#line 117 "../src/mce.gs"
	gst_bin_add_many (_tmp20_, _tmp21_, _tmp22_, _tmp23_, NULL);
#line 118 "../src/mce.gs"
	_tmp24_ = filesource;
#line 118 "../src/mce.gs"
	_tmp25_ = parser;
#line 118 "../src/mce.gs"
	_tmp26_ = decoder;
#line 118 "../src/mce.gs"
	if (!gst_element_link_many (_tmp24_, _tmp25_, _tmp26_, NULL)) {
#line 720 "mce.c"
		const gchar* _tmp27_;
		gchar* _tmp28_;
		gchar* _tmp29_;
#line 119 "../src/mce.gs"
		_tmp27_ = string_to_string (source);
#line 119 "../src/mce.gs"
		_tmp28_ = g_strconcat ("could not link elements of ", _tmp27_, NULL);
#line 119 "../src/mce.gs"
		_tmp29_ = _tmp28_;
#line 119 "../src/mce.gs"
		g_warning ("mce.gs:119: %s", _tmp29_);
#line 119 "../src/mce.gs"
		_g_free0 (_tmp29_);
#line 120 "../src/mce.gs"
		_g_object_unref0 (decoder);
#line 120 "../src/mce.gs"
		_g_object_unref0 (parser);
#line 120 "../src/mce.gs"
		_g_object_unref0 (filesource);
#line 120 "../src/mce.gs"
		_g_object_unref0 (bin);
#line 120 "../src/mce.gs"
		return;
#line 744 "mce.c"
	}
#line 121 "../src/mce.gs"
	_tmp30_ = self->priv->_sources;
#line 121 "../src/mce.gs"
	_tmp31_ = bin;
#line 121 "../src/mce.gs"
	gee_abstract_collection_add ((GeeAbstractCollection*) _tmp30_, (GstElement*) _tmp31_);
#line 122 "../src/mce.gs"
	_tmp32_ = self->pipeline;
#line 122 "../src/mce.gs"
	_tmp33_ = bin;
#line 122 "../src/mce.gs"
	gst_bin_add ((GstBin*) _tmp32_, (GstElement*) _tmp33_);
#line 123 "../src/mce.gs"
	_tmp34_ = decoder;
#line 123 "../src/mce.gs"
	_tmp35_ = self->priv->_sink;
#line 123 "../src/mce.gs"
	if (!gst_element_link (_tmp34_, _tmp35_)) {
#line 764 "mce.c"
		const gchar* _tmp36_;
		gchar* _tmp37_;
		gchar* _tmp38_;
#line 124 "../src/mce.gs"
		_tmp36_ = string_to_string (source);
#line 124 "../src/mce.gs"
		_tmp37_ = g_strconcat ("could not link source to sink for ", _tmp36_, NULL);
#line 124 "../src/mce.gs"
		_tmp38_ = _tmp37_;
#line 124 "../src/mce.gs"
		g_warning ("mce.gs:124: %s", _tmp38_);
#line 124 "../src/mce.gs"
		_g_free0 (_tmp38_);
#line 125 "../src/mce.gs"
		_g_object_unref0 (decoder);
#line 125 "../src/mce.gs"
		_g_object_unref0 (parser);
#line 125 "../src/mce.gs"
		_g_object_unref0 (filesource);
#line 125 "../src/mce.gs"
		_g_object_unref0 (bin);
#line 125 "../src/mce.gs"
		return;
#line 788 "mce.c"
	}
#line 126 "../src/mce.gs"
	self->priv->_source_counter = self->priv->_source_counter + 1;
#line 110 "../src/mce.gs"
	_g_object_unref0 (decoder);
#line 110 "../src/mce.gs"
	_g_object_unref0 (parser);
#line 110 "../src/mce.gs"
	_g_object_unref0 (filesource);
#line 110 "../src/mce.gs"
	_g_object_unref0 (bin);
#line 800 "mce.c"
}

void
mce_add_sources (Mce* self,
                 gchar** sources,
                 gint sources_length1)
{
#line 129 "../src/mce.gs"
	g_return_if_fail (self != NULL);
#line 810 "mce.c"
	{
		gchar** source_collection = NULL;
		gint source_collection_length1 = 0;
		gint _source_collection_size_ = 0;
		gint source_it = 0;
#line 130 "../src/mce.gs"
		source_collection = sources;
#line 130 "../src/mce.gs"
		source_collection_length1 = sources_length1;
#line 130 "../src/mce.gs"
		for (source_it = 0; source_it < source_collection_length1; source_it = source_it + 1) {
#line 822 "mce.c"
			gchar* _tmp0_;
			gchar* source = NULL;
#line 130 "../src/mce.gs"
			_tmp0_ = g_strdup (source_collection[source_it]);
#line 130 "../src/mce.gs"
			source = _tmp0_;
#line 829 "mce.c"
			{
				const gchar* _tmp1_;
#line 131 "../src/mce.gs"
				_tmp1_ = source;
#line 131 "../src/mce.gs"
				mce_add_source (self, _tmp1_);
#line 130 "../src/mce.gs"
				_g_free0 (source);
#line 838 "mce.c"
			}
		}
	}
}

gboolean
mce_play (Mce* self)
{
	GstPipeline* _tmp0_;
	GMainLoop* _tmp1_;
	gboolean result = FALSE;
#line 133 "../src/mce.gs"
	g_return_val_if_fail (self != NULL, FALSE);
#line 136 "../src/mce.gs"
	if (self->priv->_source_counter == 0) {
#line 137 "../src/mce.gs"
		g_warning ("mce.gs:137: cannot start playing becuase no sources added.");
#line 138 "../src/mce.gs"
		result = FALSE;
#line 138 "../src/mce.gs"
		return result;
#line 860 "mce.c"
	}
#line 139 "../src/mce.gs"
	_tmp0_ = self->pipeline;
#line 139 "../src/mce.gs"
	gst_element_set_state ((GstElement*) _tmp0_, GST_STATE_PLAYING);
#line 140 "../src/mce.gs"
	_tmp1_ = self->priv->_loop;
#line 140 "../src/mce.gs"
	if (!g_main_loop_is_running (_tmp1_)) {
#line 870 "mce.c"
		GMainLoop* _tmp2_;
#line 141 "../src/mce.gs"
		_tmp2_ = self->priv->_loop;
#line 141 "../src/mce.gs"
		g_main_loop_run (_tmp2_);
#line 876 "mce.c"
	}
#line 142 "../src/mce.gs"
	result = TRUE;
#line 142 "../src/mce.gs"
	return result;
#line 882 "mce.c"
}

void
mce_quit (Mce* self)
{
	GstPipeline* _tmp0_;
	GMainLoop* _tmp1_;
#line 144 "../src/mce.gs"
	g_return_if_fail (self != NULL);
#line 145 "../src/mce.gs"
	_tmp0_ = self->pipeline;
#line 145 "../src/mce.gs"
	gst_element_set_state ((GstElement*) _tmp0_, GST_STATE_NULL);
#line 146 "../src/mce.gs"
	_tmp1_ = self->priv->_loop;
#line 146 "../src/mce.gs"
	g_main_loop_quit (_tmp1_);
#line 900 "mce.c"
}

static void
mce_class_init (MceClass * klass,
                gpointer klass_data)
{
#line 40 "../src/mce.gs"
	mce_parent_class = g_type_class_peek_parent (klass);
#line 40 "../src/mce.gs"
	g_type_class_adjust_private_offset (klass, &Mce_private_offset);
#line 40 "../src/mce.gs"
	G_OBJECT_CLASS (klass)->finalize = mce_finalize;
#line 913 "mce.c"
}

static void
mce_instance_init (Mce * self,
                   gpointer klass)
{
#line 40 "../src/mce.gs"
	self->priv = mce_get_instance_private (self);
#line 47 "../src/mce.gs"
	self->priv->_source_counter = 0;
#line 924 "mce.c"
}

static void
mce_finalize (GObject * obj)
{
	Mce * self;
#line 40 "../src/mce.gs"
	self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_MCE, Mce);
#line 41 "../src/mce.gs"
	_g_object_unref0 (self->pipeline);
#line 42 "../src/mce.gs"
	_g_main_loop_unref0 (self->priv->_loop);
#line 44 "../src/mce.gs"
	_g_object_unref0 (self->priv->_sources);
#line 45 "../src/mce.gs"
	_g_object_unref0 (self->priv->_pie);
#line 46 "../src/mce.gs"
	_g_object_unref0 (self->priv->_sink);
#line 40 "../src/mce.gs"
	G_OBJECT_CLASS (mce_parent_class)->finalize (obj);
#line 945 "mce.c"
}

static GType
mce_get_type_once (void)
{
	static const GTypeInfo g_define_type_info = { sizeof (MceClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) mce_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (Mce), 0, (GInstanceInitFunc) mce_instance_init, NULL };
	GType mce_type_id;
	mce_type_id = g_type_register_static (G_TYPE_OBJECT, "Mce", &g_define_type_info, 0);
	Mce_private_offset = g_type_add_instance_private (mce_type_id, sizeof (McePrivate));
	return mce_type_id;
}

GType
mce_get_type (void)
{
	static volatile gsize mce_type_id__volatile = 0;
	if (g_once_init_enter (&mce_type_id__volatile)) {
		GType mce_type_id;
		mce_type_id = mce_get_type_once ();
		g_once_init_leave (&mce_type_id__volatile, mce_type_id);
	}
	return mce_type_id__volatile;
}

These are the interesting bits:

...
#line 73 "../src/mce.gs"
	mce_add_sources (self, sources, (gint) sources_length1);
#line 50 "../src/mce.gs"
<b>	_g_object_unref0 (bus);</b> (was added automatically)
#line 50 "../src/mce.gs"
	return self;
... (all this too):
		_g_free0 (_tmp38_);
#line 125 "../src/mce.gs"
		_g_object_unref0 (decoder);
#line 125 "../src/mce.gs"
		_g_object_unref0 (parser);
#line 125 "../src/mce.gs"
		_g_object_unref0 (filesource);
#line 125 "../src/mce.gs"
		_g_object_unref0 (bin);

See. Memory management is handled automatically. When something leaves the context and reference count drops to zero, boom. You don’t forget, the app doesn’t leak memory, it’s just as legible as Python, as fast as C (it is), it has fstring style string formatting, and you get all the object oriented goodness.

Sorry to sound like an evangelist, but the language is perfect for anything GObject based and the gstreamer bindings are automatically generated. Issue is the Nvidia documentation does not seem to be in the Gtk Doc format so the bindings cannot be generated automatically (or I may be doing something wrong).

I can write my pad probe buffer callback to handle the metadata stuff in pure C manually, however if anybody does know of a way to parse and convert the documentation in the header files, I might be able to avoid a lot of work and write some prettier, more reliable code with Vala bindings. Basically I need a C expert who has handled a lot of GObject stuff, which I am not.

So, for anybody who is interested, I ended up getting things working. Doing the callbacks in pure C and just including that in my Genie code as “extern” worked fine. No .vapi necessary. The callback is the only thing that deals with nvds_meta and friends, so I avoided writing any .vapi bindings at all. My meson.build (if you use Meson for Vala/Genie) looks like this:

cc = meson.get_compiler('c')

nvalhalla_sources = files(
  'cb_buffer.c',
  'main.gs',
  'nvalhalla.gs',
  'bins.gs',
)
includes = include_directories('../includes')

ds_includes = include_directories('/opt/nvidia/deepstream/deepstream-4.0/sources/includes')
ds_libdir = '/opt/nvidia/deepstream/deepstream-4.0/lib'

nvalhalla_deps = [
  dependency('glib-2.0', version: '>=2.50'),
  dependency('gobject-2.0', version: '>=2.0'),
  dependency('gstreamer-1.0', version: '>=1.0'),
  dependency('gee-0.8'),
  # this is needed for the SignalHandler:
  meson.get_compiler('vala').find_library('posix'),
  # this is needed for Math
  cc.find_library('m', required : false),
  cc.find_library('nvds_meta', dirs: ds_libdir),
  cc.find_library('nvdsgst_meta', dirs: ds_libdir),
]

executable('nvalhalla', nvalhalla_sources,
  vala_args: [
    '--header=nvalhalla.h',
    '--vapi=nvalhalla.vapi'
  ],
  dependencies: nvalhalla_deps,
  install: true,
  include_directories: [includes, ds_includes],
)

If anybody wants some more details, I’m planning on publishing this as soon as I add some Gst.Bins.

Lastly, if a .pc could be provided with DeepStream it would be much appreciated so my main .build file can be prettier.

Glad to know issue resolved, thanks for your sharing!