Embedding .oga files into an executable to play them via GStreamer
Latest update:
We begin with a digression.
Gtk4 has an abstract interface MediaStream for media playback. Why
not use it? I found its realisation (GtkMediaFile) strangly quirky:
after receiving notify::ended
signal & calling g_object_unref()
on
GtkMediaStream
object, it still retains a connection to the
pipewire-pulse daemon.
You can easily test this behavior with tests/testsounds.c
from
the gtk repo. Compile the example:
cc -o testsounds testsounds.c `pkg-config --cflags --libs gtk4`
& run it while keeping pavucontrol open on the 'Playback' tab. If you
press any of the buttons in the testsounds app 5 times, you will
notice 5 new entries in pavucontrol, even after the playback has
finished:
These entries only disappear when you kill the Gtk4 app!
If all you need is to play a short sound to attract a user's attention, you
can use GStreamer directly without additional wrappers.
Why embeed?
There are no 'standard' sounds in gtk4. There is humble
gdk_display_beep()
, but it relies on a PC speaker under X11 that
some users disable, for beeps can be too annoying when typing in
a terminal emulator.
Therefore, it's preferable to play real sound files, like the
collection of .oga files (with Vorbis streams inside) from
$ rpm -qf /usr/share/sounds/freedesktop/stereo
sound-theme-freedesktop-0.8-19.fc38.noarch
which is fine & dandy if a user has at least the default
freedesktop.org sound theme installed, but a Linux desktop is not
Windows, there is no guarantee for anything.
We could bring an .oga file with our executable, or, if the sound file
is small, embed it directly into the compiled app.
How to embed
The C23 revision provides #embed
preprocessor directive for
char bell[] = {
#embed "bell.oga"
};
party tricks, but the current GCC 13 doesn't support it, & even if it
did, the users of various slowpoke distros (like the one starting with
'D') woudn't be able to enjoy such a modern compiler for the next N
years anyway.
This leaves us with base64 strings, as if we were embeding something in
an HTML page to decode it with JavaScript. E.g., if we have a function
void play_ogg(char *base64);
in file.c, then our Makefile can contain
file.o: CFLAGS += -DSOUND_BELL=\"`base64 -w0 bell.oga`\"
file.o: bell.oga
that adds SOUND_BELL
macro to the CFLAGS
env var only for
'file.o' target. GNU Make calls this stunt 'target-specific variable
values' & I recomend it wholeheartedly.
GStreamer
In file.c, the simplest function to play an .oga file may look like
#include <gst/gst.h>
void play_ogg(char *base64) {
size_t len = strlen(base64);
char ps[len+50];
snprintf(ps, sizeof(ps), "playbin uri=data:audio/ogg;base64,%s", base64);
GstElement *pipeline = gst_parse_launch(ps, NULL);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
GstBus *bus = gst_element_get_bus(pipeline);
GstMessage *msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
if (msg != NULL) gst_message_unref(msg);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
}
You call it as
play_ogg(SOUND_BELL);
and the preprocessor replaces it with play_ogg("T2dnUwACAA…")
during a compilation. Don't forget to initialise GStreamer somewhere
in the beginning of main()
:
gst_init(NULL, NULL);
GtkApplication *app = gtk_application_new(…);
Tags: ойті
Authors: ag