IOverThoughtThis

Recovering Perfectionist

Compiling Vala Apps on macOS with GStream

I recently installed Elementary OS on my girlfriends laptop and it looks sweet! I'm on macOS and I'm jealous.

I've been thinking of moving from macOS and will probably be moving over to Elemenatry on some nice ultra-book in the future.

In the mean time I want to be preped to developing for the platform. That means writing apps in Vala.

Vala is a nice abstraction ontop of c. Comming from Ruby it lacks a lot of the "beautiful" syntax but since it is the language of Elementary OS, I think it best to learn it!

I started by trying to build a simple audio app. This meant pulling in GStreamer so that I can "easily" setup multi media pipelines to manage playing audio. I didn't find it that easy.

Documentation is everywhere and nowhere. Installation instructions are varied and vauge. The domain language is arbitrary and confusing, I really with they had borrowed from audio production language!

Oh well. I tried to get a very simple app to just play a song.

audio-player.vala
    void main (string[] args) {
      Gst.init (ref args);
      var pipeline = Gst.ElementFactory.make ("playbin", "player");
      pipeline.set ("uri", Gst.filename_to_uri("/Users/jasperlyons/Downloads/Ariyah EP/MP3s/03 - Disgraced.mp3"));
      pipeline.set_state (Gst.State.PLAYING);
      new MainLoop ().run ();
    }
  
void main (string[] args) {

Here we define our main function, it takes in an array of strings. This syntax is very reminicent of Java!

Gst.init (ref args);

Here we initialie the Gst library which lets it set up some internal stuff and deal with any Gst specific command line arguments.

The key word ref here means pass this value by reference, vala passes parameters just like c which means it varies. This is why you have key words to control the passing behavior.

var pipeline = Gst.ElementFactory.make ("playbin", "player");

Here we create a var (Gst.Element in this case) to hold our "pipeline". A pipeline is like a track in an audio editor (or video editor). It can handle the sourcing, decoding, filtering, effecting and sending of streams of data (audio, video). You can plug in sources, effects and sinks to a pipeline.

var is a Vala keyword that lets you not care about the type of value, similar in apparent semantics to Javascripts "var".

"Gst.ElementFactor.make" is a namespaced function that takes in a type and an optional name. Type is to identify which factory to use and eventually, which Element to initialize. Name is potentially for later reference... I haven't got that far yet. Check out the docs here.

"playbin" is a type of plugin I think. It handles sourcing, decoding playback and some other stuff probably. Looks like a convenience object but, like many things in GStreamer it isn't amazingly convenient.

pipeline.set ("uri", Gst.filename_to_uri("/Users/jasperlyons/Downloads/Ariyah EP/MP3s/03 - Disgraced.mp3"));

This type of element ("playbin") needs a uri (Universal Resource Locator). It can be a web resource or a local resource! Which immediately feels weird, why not let the application developer handle all of these responcibilities; Finding the file, reading the file etc.

Gst.filename_to_uri is a function that escapes all of the crazy characters in filenames, like spaces. :O.

pipeline.set_state (Gst.State.PLAYING);

Pipelines have states. This can be one of: PLAYING, PAUSED, READY, NULL, VOID_PENDING. They seem to go from sensible to uninteligable!? Luckily I have not had to deal with anything other than PLAYING yet.

These states determine what should be happening to the data flowing through the pipeline, largely. You can get a better handle on this than me here.

Anyway, now the state of the pipeline is playing! But it is not actually playing. We have to sing the magic incantation:

new MainLoop ().run ();

It always pains me when instantiating an object and calling a method on it some how effects all of the other objects I've been instantiating. It's the kind of global effects that make me cry. Why11!?!

I understand that there needs to be some kind of event loop + threading to allow the program to continue execution and react to user input while media is PLAYING but I think there could have been some decisions made when exposing the C stuff in vala that wrapped this up in a more intuative way. Oh well, I don't have the time.

Running the Code!

Yeah, the fun part! To compile this and run it we need:

$ valac -g --pkg gstreamer-1.0 audio-player.vala

valac is the compiler, -g means embed source code info in the binary, --pkg means use this package, gstreamer-1.0 is the package were using and audio-player.vala is the file the above code is situated in!

Oh wait! This will fail.

-bash: /usr/local/bin/valac: No such file or directory

Lets install vala:

brew install vala

Try again!

    valac -g --pkg gstreamer-1.0 audio-player.vala
    audio-player.vala:21.24-21.108: warning: unhandled error `GLib.Error'
    pipeline.set ("uri", Gst.filename_to_uri("/Users/jasperlyons/Downloads/Ariyah EP/MP3s/03 - Disgraced.mp3"));
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    /Users/jasperlyons/workspace/languages/vala/audio-player/audio-player.vala.c:7:10: fatal error: 'gst/gst.h' file not found
    #include 
              ^
    1 error generated.
    error: cc exited with status 256
    Compilation failed: 1 error(s), 1 warning(s)
  

We'll ignore the warning, error handling is for code I care about. How to solve that error though? Install gstreamer!

brew install gstreamer

Try again:

    $ valac -g --pkg gstreamer-1.0 audio-player.vala
    audio-player.vala:21.24-21.108: warning: unhandled error `GLib.Error'
    pipeline.set ("uri", Gst.filename_to_uri("/Users/jasperlyons/Downloads/Ariyah EP/MP3s/03 - Disgraced.mp3"));
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    Compilation succeeded - 1 warning(s)
  

Yes!

This will result in a:

    $ ls
    audio-player		audio-player.dSYM	audio-player.vala
  

which we can then run with:

$ ./audio-player

No audio and lots of failed assertions!

    $ ./audio-player

    (audio-player:68857): GLib-GObject-CRITICAL **: g_object_set: assertion 'G_IS_OBJECT (object)' failed

    (audio-player:68857): GStreamer-CRITICAL **: gst_element_get_bus: assertion 'GST_IS_ELEMENT (element)' failed

    (audio-player:68857): GStreamer-CRITICAL **: gst_bus_add_watch_full: assertion 'GST_IS_BUS (bus)' failed

    (audio-player:68857): GStreamer-CRITICAL **: gst_element_set_state: assertion 'GST_IS_ELEMENT (element)' failed
  

You might wonder, what is going on here. I did, and after ~16 hours searching and learning IRC with ask in #vala on irc.gnome.org I found the answer:

Install the plugins.

WHAT!? What. Why is there no mention of the plugins in these error messages? Surely that would make sense. The developers who wrote GStreamer should know far more about this than me. Ok.

So lets try the solution. For brevityies sake, I will provide here:

brew install gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly

Ho Ho HO the puns. How funny. Why? Help me, the user of the library, stop mocking me with your Anglo-American contexts. Stop cutting out people who have never watched the best spaghetti western ever.

Anyway, again:

$./audio-player

YES, I HAVE AUDIO! Though, you may not.

It's unlikely the file: "/Users/jasperlyons/Downloads/Ariyah EP/MP3s/03 - Disgraced.mp3" exists for you so you'll need to replace it with an audio file of your choosing!

I hope that helped someone else avoid 16hours of wasted time.

ps: No hate to the Gstreamer devs, they built this big amazing library that is far far more complex than I have taken into account here. My winnings are those of someone who just wishes we would make it easier for other devs to get involved.

peace.