Using the BlueALSA D‐Bus API - arkq/bluez-alsa GitHub Wiki

Using the BlueALSA D-Bus API

Introduction

It is possible to create clients of the BlueALSA service using the D-Bus API directly rather than using the ALSA API. The BlueALSA API is documented as three manual pages:

To use the D-Bus API you can program to the libdbus low-level public API or you can use one of the higher-level language bindings.

For examples of using libdbus, for see the clients bluealsactl and bluealsa-aplay included in the bluez-alsa project.

In this article we demonstrate the use of the GDBus API wich is part of the GIO library in GLIB. In particular we use the utility gdbus-codegen to generate BlueALSA interface C code from the BlueALSA API XML document.

When using the current source code, that file is installed into the standard D-Bus service interface introspection XML directory (so is typically /usr/share/dbus-1/interfaces/org.bluealsa.xml). For BlueALSA release 4.3.1 and earlier it is not installed, so copy the file src/bluealsa-iface.xml from the bluez-alsa source code and name it org.bluealsa.xml.

Note that glib and gdbus-codegen are subject to the LGPL-2.1-or-later license and therefore you would need to ensure that your project complies with the terms of that license before using this method.

gdbus-codegen

Please read the gdbus-codegen manual page for detailed guidance on the use of that utility.

We begin by creating the C code to implement the BlueALSA interface as defined in the BlueALSA API XML file. This example assumes that the XML file has been copied to the local working directory and named org.bluealsa.xml.

To create the .h file, use:

	gdbus-codegen --header \
	--output org.bluealsa.c \
	--interface-prefix org.bluealsa \
	--c-generate-object-manager \
	--c-generate-autocleanup all \
	--c-namespace Bluealsa \
	--glib-min-required 2.64 \
	--annotate "org.bluealsa.PCM1:Volume" org.gtk.GDBus.C.ForceGVariant yes \
	--annotate "org.bluealsa.PCM1:CodecConfiguration" org.gtk.GDBus.C.ForceGVariant yes \
	org.bluealsa.xml

To create the .c file use the same options except to replace --header with --body and --output org.bluealsa.h with --output org.bluealsa.c

The --glib-min-required option is required because earlier versions of glib had a bug in gdbus-codegen which prevented Unix file descriptors from being passed successfully, making it impossible to Open() a PCM.

The --annotate options are required for the Volume and CodecConfiguration properties, because otherwise gdbus-codegen generates accessor functions that return just a pointer to the start of a NUL-terminated array of bytes. As both these properties may contain NUL bytes within their data, it is not possible to reliably determine the length of the array. So we use these annotation options to have the accessor functions return gVariant types, giving both the array start and its length.

The generated .c file contains documentation for each public type, function, and macro as comments within the code. Note that the generated code contains functions for both client and server implementations. Only the client side code is useful here. Refer to the gdbus-codegen manual for further advice on this.

Examples

We can now create our client application by using the functions defined in this generated interface code. We give a few simple example applications to illustrate the basic principles, and a Makefile for compiling the examples and interface code.

Obtaining the list of connected PCMs

Here is a simple example, using the generated ObjectManager code to print a list of connected PCM object paths (similar to bluealsactl list-pcms).

/*
 * bluealsa-list.c
 *
 * Print a list of connected BlueALSA PCM object paths
 */

#include <glib.h>
#include "org.bluealsa.h"

int main() {
	GDBusObjectManager *manager;
	GError *error = NULL;
	GList *bluealsa_objects = NULL;

	manager = bluealsa_object_manager_client_new_for_bus_sync(
						G_BUS_TYPE_SYSTEM,
						G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
						"org.bluealsa",
						"/org/bluealsa",
						NULL,
						&error);

	if (error) {
		g_printerr("Failed to create object manager client (%s)\n", error->message);
		exit(EXIT_FAILURE);
	}

	bluealsa_objects = g_dbus_object_manager_get_objects(manager);

	for (GList *item = g_list_first(bluealsa_objects); item; item = item->next) {
		GDBusObject *object = item->data;
		if (bluealsa_object_peek_pcm1(BLUEALSA_OBJECT(object)))
			g_print("%s\n", g_dbus_object_get_object_path(object));
		g_object_unref(object);
	}

	g_list_free(bluealsa_objects);
	g_object_unref(manager);

	return EXIT_SUCCESS;
}

Accessing a specific PCM

This next example demonstrates that it is possible to to use a PCM without needing the ObjectManager, provided that the object path of the PCM is known (similar to bluealsactl info).

/*
 * bluealsa-info.c
 *
 * Print the Properties of a connected BlueALSA PCM
 */

#include <glib.h>
#include "org.bluealsa.h"

/* Macros to extract loudness and mute component from encoded Volume property */
#define BA_LOUDNESS(x) (x & 0x7F)
#define BA_MUTED(x)    (x & 0x80)

/* Convert encoded Format property to name of equivalent ALSA PCM format */
static const gchar *alsa_format_name(guint16 format) {
	switch (format) {
	case 0x0108:
		return "U8";
	case 0x8210:
		return "S16_LE";
	case 0x8318:
		return "S24_3LE";
	case 0x8418:
		return "S24_LE";
	case 0x8420:
		return "S32_LE";
	default:
		return "UNKNOWN";
	}
}

int main(int argc, char *argv[]) {

	BluealsaPCM1 *pcm = NULL;
	const gchar *object_path;
	const gchar *device;
	GVariant *v;
	GError *error = NULL;

	if (argc < 2) {
			g_printerr("Usage: %s PCM_PATH\n", argv[0]);
			exit(EXIT_FAILURE);
	}
	object_path = argv[1];

	if (!g_variant_is_object_path(object_path)) {
		g_printerr("Invalid object path [%s]\n", object_path);
		exit(EXIT_FAILURE);
	}

	pcm = bluealsa_pcm1_proxy_new_for_bus_sync(
						G_BUS_TYPE_SYSTEM,
						G_DBUS_PROXY_FLAGS_NONE,
						"org.bluealsa",
						object_path,
						NULL,
						&error);
	if (!pcm) {
		if (error)
			g_printerr("Failed to create D-Bus proxy PCM (%s)\n", error->message);
		exit(EXIT_FAILURE);
	}

	g_print("object path: %s\n", object_path);
	device = bluealsa_pcm1_get_device(pcm);
	if (!device) {
		g_printerr("Object path does not represent a connected PCM\n");
		exit(EXIT_FAILURE);
	}
	g_print("device: %s\n", device);
	g_print("transport: %s\n", bluealsa_pcm1_get_transport(pcm));
	g_print("mode: %s\n", bluealsa_pcm1_get_mode(pcm));
	g_print("sequence number: %u\n", bluealsa_pcm1_get_sequence(pcm));
	g_print("codec: %s\n", bluealsa_pcm1_get_codec(pcm));

	/* The CodecConfiguration property is returned as a GVariant containing an
	 * array of bytes. Here we print the value as a single hexadecimal string
	 * (as used by the Bluealsa utility a2dpconf). */
	v = bluealsa_pcm1_get_codec_configuration(pcm);
	if (v) {
		gsize n;
		const guint8 *config = g_variant_get_fixed_array(v, &n, sizeof(guint8));
		g_print("codec configuration: ");
		for (gsize i = 0; i < n; i++)
			g_print("%02hhx", config[i]);
		g_print("\n");
	}
	g_print("audio format: %s\n", alsa_format_name(bluealsa_pcm1_get_format(pcm)));
	g_print("audio channels: %u\n", bluealsa_pcm1_get_channels(pcm));
	const gchar *const *channel_ptr = bluealsa_pcm1_get_channel_map(pcm);
	g_print("audio channel map: [ ");
	while (channel_ptr && *channel_ptr)
		g_print("%s ", *channel_ptr++);
	g_print("]\n");
	g_print("audio rate: %u\n", bluealsa_pcm1_get_rate(pcm));

	/* The Volume property is returned as a GVariant containing an array of
	 * bytes representing encoded loudness and mute values, one value for each
	 * channel. */
	v = bluealsa_pcm1_get_volume(pcm);
	if (v) {
		gsize len;
		const guint8 *volume = g_variant_get_fixed_array(v, &len, sizeof(guint8));
		for (gsize i = 0; i < len; i++)
			g_print("volume channel %zu: %hhu%s\n", i,
							BA_LOUDNESS(volume[i]),
							BA_MUTED(volume[i]) ? " (muted)" : "");
	}

	g_print("soft volume: %s\n", bluealsa_pcm1_get_soft_volume(pcm) ? "yes" : "no");
	g_print("running: %s\n", bluealsa_pcm1_get_running(pcm) ? "yes" : "no");
	g_print("delay: %u\n", bluealsa_pcm1_get_delay(pcm));
	g_print("client delay: %d\n", bluealsa_pcm1_get_client_delay(pcm));

	/* Obtain the set of supported codecs by invoking the GetCodecs method. */
	if (bluealsa_pcm1_call_get_codecs_sync(
					pcm,
					G_DBUS_CALL_FLAGS_NONE,
					-1,
					&v,
					NULL,
					&error)) {
		GVariantIter iter;
		GVariant *child;
		gchar *key;
		g_print("supported codecs: [");
		g_variant_iter_init(&iter, v);
		while (g_variant_iter_next(&iter, "{sa{sv}}", &key, &child))
			g_print(" %s", key);
		g_print(" ]\n");
	}

	if (error)
		g_error_free(error);

	g_object_unref(pcm);

	return EXIT_SUCCESS;
}

An event-driven example

The ObjectManager can also be used to respond to signals from BlueALSA PCMs, BlueALSA RFCOMMs and the BlueALSA manager interface. In this simple example, PCM and RFCOMM connection and disconnection events are reported, together with property changed signals. (similar to bluealsactl monitor)

/*
 * bluealsa-monitor.c
 *
 * Print BlueALSA PCM and RFCOMM connection, disconnection and property change
 * events.
 */

#include <glib.h>
#include "org.bluealsa.h"

/* Handler called when an object managed by the BlueALSA object manager is
 * created. */
static void on_object_added(
						GDBusObjectManager* self,
						GDBusObject* object,
						gpointer user_data) {
	BluealsaPCM1 *pcm;
	BluealsaRFCOMM1 *rfcomm;
	gchar *s;
	GVariant *v;

	/* The object may be either a PCM or a RFCOMM, so we distinguish
	 * them by first trying to obtain the PCM1 interface using
	 * bluealsa_object_get_pcm1(), and if that fails then try to get the
	 * RFCOMM1 interface using bluealsa_object_get_rfcomm1() */
	if ((pcm = bluealsa_object_get_pcm1(BLUEALSA_OBJECT(object)))) {
		g_print("PCM added:\n");
		g_print("	object path: %s\n", g_dbus_object_get_object_path(object));
		g_print("	device: %s\n", bluealsa_pcm1_get_device(pcm));
		g_print("	transport: %s\n", bluealsa_pcm1_get_transport(pcm));
		g_print("	mode: %s\n", bluealsa_pcm1_get_mode(pcm));
		g_print("	sequence number: %u\n", bluealsa_pcm1_get_sequence(pcm));
		g_print("	codec: %s\n", bluealsa_pcm1_get_codec(pcm));
		v = bluealsa_pcm1_get_codec_configuration(pcm);
		if (v) {
			gsize n;
			const guint8 *config = g_variant_get_fixed_array(v, &n, sizeof(guint8));
			g_print("	codec configuration: ");
			for (gsize i = 0; i < n; i++)
				g_print("%02hhx", config[i]);
			g_print("\n");
		}
		g_print("	audio format: %#4x\n", bluealsa_pcm1_get_format(pcm));
		g_print("	audio channels: %u\n", bluealsa_pcm1_get_channels(pcm));
		const gchar *const *channel_ptr = bluealsa_pcm1_get_channel_map(pcm);
		g_print("	audio channel map: [ ");
		while (channel_ptr && *channel_ptr)
			g_print("%s ", *channel_ptr++);
		g_print("]\n");
		g_print("	audio rate: %u\n", bluealsa_pcm1_get_rate(pcm));
		v = bluealsa_pcm1_get_volume(pcm);
		if (v) {
			s = g_variant_print(v, FALSE);
			g_print("	volume: %s\n", s);
			g_free(s);
		}
		g_print("	soft volume: %s\n", bluealsa_pcm1_get_soft_volume(pcm) ? "yes" : "no");
		g_print("	running: %s\n", bluealsa_pcm1_get_running(pcm) ? "yes" : "no");
		g_print("	delay: %u\n", bluealsa_pcm1_get_delay(pcm));
		g_print("	client delay: %d\n", bluealsa_pcm1_get_client_delay(pcm));

		/* The pcm object remains valid until the profile connection is closed.
		 * So it is safe to save the pcm pointer for use outside this function,
		 * until we are notified that it has been removed by the object_removed
		 * signal. When we are finished with the pcm we free it by
		 * dereferencing it. In this example we do not use the pcm outside of
		 * this function, so we dereference it here. */
		g_object_unref(pcm);
		return;
	}
	if ((rfcomm = bluealsa_object_get_rfcomm1(BLUEALSA_OBJECT(object)))) {
		g_print("RFCOMM added:\n");
		g_print("	object path: %s\n", g_dbus_object_get_object_path(object));
		g_print("	transport: %s\n", bluealsa_rfcomm1_get_transport(rfcomm));
		g_print("	battery charge: %hhd\n", bluealsa_rfcomm1_get_battery(rfcomm));
		/* Note: the "Features" property is always empty when the object is
		 * first added, so we do not report it here. */

		/*  Just the same as with a pcm object, we dereference the rfcomm
		 * object when we are finished with it. */
		g_object_unref(rfcomm);
		return;
	}
}

/* Handler called when an object managed by the BlueALSA object manager is
 * removed. */
static void on_object_removed(
						GDBusObjectManager* self,
						GDBusObject* object,
						gpointer user_data) {
	BluealsaPCM1 *pcm;
	BluealsaRFCOMM1 *rfcomm;

	if ((pcm = bluealsa_object_peek_pcm1(BLUEALSA_OBJECT(object)))) {
		g_print("PCM removed:\n");
		g_print("	object path: %s\n", g_dbus_object_get_object_path(object));
	}
	else if ((rfcomm = bluealsa_object_peek_rfcomm1(BLUEALSA_OBJECT(object)))) {
		g_print("RFCOMM removed:\n");
		g_print("	object path: %s\n", g_dbus_object_get_object_path(object));
	}

	/* Note: if the pcm or rfcomm object was previously saved then it must
	 * be dereferenced in order to free it. In this example that is not
	 * necessary.
	 */
}

/* Handler called when a Property of an object managed by the BlueALSA object
 * manager changes its value. */
static void on_interface_proxy_properties_changed(
						GDBusObjectManagerClient *manager,
						GDBusObjectProxy         *object_proxy,
						GDBusProxy               *interface_proxy,
						GVariant                 *changed_properties,
						const gchar *const       *invalidated_properties,
						gpointer                  user_data) {
	GVariantIter iter;
	gchar *key;
	GVariant *value;
	gchar *s;

	/* Notes:
	 * 1. The property value has already been changed in the associated PCM1 or
	 *    RFCOMM1 object before this handler is called.
	 *
	 * 2. If we need to know which object interface this property relates
	 *    to, we can do something like:
	 *
	 * if (BLUEALSA_IS_PCM1(interface_proxy)) {
	 *     BluealsaPCM1 *pcm = BLUEALSA_PCM1(interface_proxy);
	 *     ... actions for PCM1 interface
	 * }
	 * else if (BLUEALSA_IS_RFCOMM1(interface_proxy)) {
	 *     BluealsaRFCOMM1 *rfcomm = BLUEALSA_RFCOMM1(interface_proxy);
	 *     ... actions for RFCOMM1 interface
	 * }
	 */

	g_print("Properties Changed on %s:\n", g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy)));
	g_variant_iter_init(&iter, changed_properties);
	while (g_variant_iter_next(&iter, "{&sv}", &key, &value)){
		s = g_variant_print(value, FALSE);
		g_print("	%s: %s\n", key, s);
		g_free(s);
		g_variant_unref(value);
	}
}

int main() {
	GDBusObjectManager *manager;
	GError *error = NULL;

	manager = bluealsa_object_manager_client_new_for_bus_sync(
						G_BUS_TYPE_SYSTEM,
						G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
						"org.bluealsa",
						"/org/bluealsa",
						NULL,
						&error);

	if (error) {
		g_printerr("Failed to create object manager (%s)\n", error->message);
		exit(EXIT_FAILURE);
	}

	g_signal_connect(manager,
						"object-added",
						G_CALLBACK(on_object_added),
						NULL);

	g_signal_connect(manager,
						"object-removed",
						G_CALLBACK(on_object_removed),
						NULL);

	g_signal_connect(manager,
						"interface_proxy_properties_changed",
						G_CALLBACK(on_interface_proxy_properties_changed),
						NULL);

	GMainLoop *main_loop;
	main_loop = g_main_loop_new(NULL, TRUE);
	g_main_loop_run(main_loop);

	g_object_unref(manager);

	return EXIT_SUCCESS;
}

Sending and receiving PCM audio streams

This example shows how to use the PCM Open() method to read and write audio streams. It transfers the audio between the BlueALSA PCM and standard input or output (similar to bluealsactl open).

/*
 * bluealsa-open.c
 *
 * Transfer raw PCM audio data via stdin or stdout
 */

#include <glib.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include "org.bluealsa.h"

int main(int argc, char *argv[]) {

	BluealsaPCM1 *pcm = NULL;
	const gchar *object_path;
	gint fd_pcm = -1;
	gint fd_ctrl = -1;
	gboolean needs_drain = FALSE;
	gboolean drained = FALSE;
	gssize count;
	gchar ctrl_response[3] = {0};
    GUnixFDList  *fd_list = NULL;
	GInputStream *input_stream = NULL;
	GOutputStream *output_stream = NULL;
	GInputStream *input_ctrl = NULL;
	GOutputStream *output_ctrl = NULL;
	GError *error = NULL;

	if (argc < 2) {
			g_printerr("Usage: %s PCM_PATH\n", argv[0]);
			exit(EXIT_FAILURE);
	}
	object_path = argv[1];

	if (!g_variant_is_object_path(object_path)) {
		g_printerr("Invalid object path [%s]\n", object_path);
		goto fail;
	}

	pcm = bluealsa_pcm1_proxy_new_for_bus_sync(
						G_BUS_TYPE_SYSTEM,
						G_DBUS_PROXY_FLAGS_NONE,
						"org.bluealsa",
						object_path,
						NULL,
						&error);
	if (!pcm) {
		if (error)
			g_printerr("Failed to create D-Bus proxy PCM (%s)\n", error->message);
		goto fail;
	}

	if (!(bluealsa_pcm1_call_open_sync(
						pcm,
						G_DBUS_CALL_FLAGS_NONE,
						-1,
						NULL,
						NULL,
						NULL,
						&fd_list,
						NULL,
						&error))) {
		if (error)
			g_printerr("Unable to open PCM (%s)\n", error->message);
		goto fail;
	}

	fd_pcm = g_unix_fd_list_get(fd_list, 0, &error);
	fd_ctrl = g_unix_fd_list_get(fd_list, 1, &error);
	g_object_unref(fd_list);

	if (strcmp(bluealsa_pcm1_get_mode(pcm), "source") == 0) {
		input_stream = g_unix_input_stream_new(fd_pcm, TRUE);
		output_stream = g_unix_output_stream_new(STDOUT_FILENO, FALSE);
	}
	else {
		input_stream = g_unix_input_stream_new(STDIN_FILENO, FALSE);
		output_stream = g_unix_output_stream_new(fd_pcm, TRUE);
		needs_drain = TRUE;
	}
	input_ctrl = g_unix_input_stream_new(fd_ctrl, TRUE);
	output_ctrl = g_unix_output_stream_new(fd_ctrl, TRUE);

	count = 0;
	do {
		count = g_output_stream_splice(
					output_stream,
					input_stream,
					G_OUTPUT_STREAM_SPLICE_NONE,
					NULL,
					&error);
	}
	while (count > 0);

	if (count == 0 && needs_drain) {
		if (g_output_stream_write(output_ctrl, "Drain", 5, NULL, &error) == 5) {
			if (g_input_stream_read(input_ctrl, ctrl_response, 2, NULL, &error) == 2 &&
						strcmp(ctrl_response, "OK") == 0)
				drained = TRUE;
		}
		if (!drained)
			g_printerr("Drain request failed\n");
	}

fail:
	if (error)
		g_error_free(error);
	if (pcm)
		g_object_unref(pcm);
	if (input_stream)
		g_object_unref(input_stream);
	if (output_stream)
		g_object_unref(output_stream);
	if (input_ctrl)
		g_object_unref(input_ctrl);
	if (output_ctrl)
		g_object_unref(output_ctrl);

	return EXIT_FAILURE;
}

Asynchronous method calls.

All of the above examples use the synchronous "*_sync" functions. gdbus-codegen also creates aynchronous versions for all the methods and also for the *proxy_new_* functions. This example demonstrates how to invoke the PCM1 GetCodecs method asynchronously.

/*
 * bluealsa-async.c
 *
 * Print the supported codecs of a connected BlueALSA PCM, using an
 * asynchrounous method call.
 */


#include <glib.h>
#include "org.bluealsa.h"

static GMainLoop *main_loop = NULL;

static void print_channels(GVariant *channels) {
	const guint8 *val;
	gsize count;
	gsize i;

	val = g_variant_get_fixed_array(channels, &count, sizeof(*val));
	g_print("    channels:");
	for (i = 0; i < count; i++)
		g_print(" %hhu", val[i]);
	g_print("\n");
}

static void print_rates(GVariant *rates) {
	const guint32 *val;
	gsize count;
	gsize i;

	val = g_variant_get_fixed_array(rates, &count, sizeof(*val));
	g_print("    rates:");
	for (i = 0; i < count; i++)
		g_print(" %u", val[i]);
	g_print("\n");
}

static void iterate_caps(GVariantIter *capabilities) {
	GVariant *value;
	gchar *key;

	while (g_variant_iter_loop(capabilities, "{sv}", &key, &value)) {
		if (g_strcmp0(key, "Channels") == 0)
			print_channels(value);
		else if (g_strcmp0(key, "Rates") == 0)
			print_rates(value);
	}
}

/**
 * This function is the call-back invoked by D-Bus when the response to the
 * GetCodecs request arrives. */
static void get_codecs_result(GObject* source_object, GAsyncResult* res, gpointer data) {
	BluealsaPCM1 *pcm = BLUEALSA_PCM1(source_object);
    GVariant *out_codecs; /* type="a{sa{sv}}" */
	GVariantIter iter;
	GVariantIter *caps;
    GError *error = NULL;
	gchar *codec;

	/* Call the appropriate *_finish() function to retrieve the result data
	 * from the result message. */
	if (bluealsa_pcm1_call_get_codecs_finish(pcm, &out_codecs, res, &error)) {
		g_variant_iter_init(&iter, out_codecs);
		while (g_variant_iter_loop(&iter, "{sa{sv}}", &codec, &caps)) {
			g_print("%s\n", codec);
			iterate_caps(caps);
		}
	}
	else {
		g_printerr("GetCodecs failed: %s\n", error->message);
		g_error_free(error);
	}

	/* In this simple example we quit the main loop after we have processed
	 * the response message. */
	g_main_loop_quit(main_loop);
}

int main(int argc, char *argv[]) {

	BluealsaPCM1 *pcm = NULL;
	const gchar *object_path;
	GError *error = NULL;
	gpointer user_data = NULL;

	if (argc < 2) {
			g_print("Usage: %s PCM_PATH\n", argv[0]);
			exit(EXIT_FAILURE);
	}
	object_path = argv[1];

	if (!g_variant_is_object_path(object_path)) {
		g_print("Invalid object path [%s]\n", object_path);
		return(EXIT_FAILURE);
	}

	pcm = bluealsa_pcm1_proxy_new_for_bus_sync(
						G_BUS_TYPE_SYSTEM,
						G_DBUS_PROXY_FLAGS_NONE,
						"org.bluealsa",
						object_path,
						NULL,
						&error);
	if (!pcm) {
		if (error)
			g_print("Failed to create D-Bus proxy PCM (%s)\n", error->message);
		return(EXIT_FAILURE);
	}

	/* Call the GetCodecs method asynchronously */
	bluealsa_pcm1_call_get_codecs(
						pcm,
						G_DBUS_CALL_FLAGS_NONE,
						-1,
						NULL,
						get_codecs_result,
						user_data);

	/* Run the glib main loop to collect the method response */
	main_loop = g_main_loop_new(NULL, TRUE);
	g_main_loop_run(main_loop);

	g_object_unref(pcm);

	return EXIT_SUCCESS;

}

Makefile

All of the above examples, and the org.bluealsa.[ch] code, can be compiled using the following simple Makefile, assuming that the BlueALSA interface XML file is called org.bluealsa.xml and the example source files are called bluealsa-list.c, bluealsa-info.c, bluealsa-monitor.c and bluealsa-open.c. All the mentioned source files must be located in the same directory as the Makefile. It may be necessary to modify the variables GLIB_CFLAGS and GLIB_LIBS for your own build system.

IFACE_XML := org.bluealsa.xml

GLIB_CFLAGS := -I/usr/include/gio-unix-2.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/sysprof-6 -pthread

GLIB_LIBS := -lgio-2.0 -lgobject-2.0 -lglib-2.0

CODEGEN_ARGS := \
	--interface-prefix org.bluealsa \
	--c-generate-object-manager \
	--c-generate-autocleanup all \
	--c-namespace Bluealsa \
	--glib-min-required 2.64 \
	--annotate "org.bluealsa.PCM1:CodecConfiguration" org.gtk.GDBus.C.ForceGVariant yes \
	--annotate "org.bluealsa.PCM1:Volume" org.gtk.GDBus.C.ForceGVariant yes

PROGRAMS := bluealsa-list bluealsa-info bluealsa-monitor bluealsa-open bluealsa-async

all: $(PROGRAMS)

bluealsa-list: bluealsa-list.o org.bluealsa.o
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(GLIB_LIBS)

bluealsa-info: bluealsa-info.o org.bluealsa.o
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(GLIB_LIBS)

bluealsa-monitor: bluealsa-monitor.o org.bluealsa.o
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(GLIB_LIBS)

bluealsa-open: bluealsa-open.o org.bluealsa.o
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(GLIB_LIBS)

bluealsa-async: bluealsa-async.o org.bluealsa.o
	$(CC) $(CFLAGS) -o $@ $^ $(GLIB_LIBS)

org.bluealsa.c: $(IFACE_XML)
	gdbus-codegen --body \
		$(CODEGEN_ARGS) \
		--output $@ $<

org.bluealsa.h: $(IFACE_XML)
	gdbus-codegen --header \
		$(CODEGEN_ARGS) \
		--output $@ $<

org.bluealsa.o: org.bluealsa.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

bluealsa-list.o: bluealsa-list.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

bluealsa-info.o: bluealsa-info.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

bluealsa-monitor.o: bluealsa-monitor.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

bluealsa-open.o: bluealsa-open.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

bluealsa-async.o: bluealsa-async.c org.bluealsa.h
	$(CC) $(CFLAGS) $(GLIB_CFLAGS) -c -o $@ $<

clean:
	$(RM) $(PROGRAMS) org.bluealsa.[ch] *.o
⚠️ **GitHub.com Fallback** ⚠️