Ruby FFI Module Tutorial (Example: Play MP3 with VLC)

I want to answer one simple question…

What is FFI in Ruby?

FFI stands for “Foreign Function Interface”.

It’s a way to use functions defined in other programming languages.

Ruby’s FFI module gives you access to external libraries & code that you wouldn’t have otherwise.

We often use this to work with C code.

Examples include:

You can write your own programs that use FFI with the help of the FFI module.

How?

Let’s see a few code examples!

FFI Basics: Loading & Importing Functions

You can work with FFI by including its functions into a module you create.

Like this:

require 'ffi'

module A
  extend FFI::Library
end

Then, inside the module, we are going to load a library using the ffi_lib method.

A C library, like a “DLL” in Windows, or “SO” in Linux, exports a list of “symbols”, or function definitions that a program makes available for others to use.

Let’s require “libc“, C’s standard library.

Here’s how:

module A
  extend FFI::Library
  ffi_lib 'c'
end

Now:

You can import specific functions from this library as Ruby methods.

With the attach_function method.

Here’s an example:

module A
  extend FFI::Library
  ffi_lib 'c'

  attach_function :strlen, [:string], :int
end

A.strlen("abc")
# 3

How does this work?

First, we know that there is a function in C called strlen, this function takes chars * as an argument.

chars * translates to String in Ruby.

Then…

This strlen function returns a value, which is the length of the string.

In other words, attach_function takes 3 parameters:

  1. Name of the C function
  2. An array of argument types
  3. A return type

You’ll have to read the documentation to find out which types a particular function works with.

Time for another example!

Build Your Own Media Player

You can find many interesting libraries to play with.

In Linux, most are found under the /usr/lib/ directory, starting with “lib” in their name & ending with the “so” extension.

List them like this:

ls /usr/lib/lib*.so

For this example, we’re going to use VLC’s media player library:

/usr/lib/libvlc.so

Our goal is to load an mp3 file & play it!

Let’s start with this:

require 'ffi'

module VLC
  extend FFI::Library
  ffi_lib 'vlc'

  attach_function :libvlc_get_version, [], :string
end

This allows you to test if the library is loading correctly & then print its version.

Like this:

VLC.libvlc_get_version
# "3.0.6 Vetinari"

You can give these methods a custom name by adding a 4th parameter.

Example:

attach_function :get_version, :libvlc_get_version, [], :string

Then you can do:

VLC.get_version

What do we need to play an mp3 now?

If we look at the documentation, the libvlc_media_player_play function looks like the way to go.

This function needs a media player “object”.

A media player needs a media, and a media needs a VLC instance.

Here’s the process:

VLC instance -> media -> media_player -> play

I pieced this together by reading the documentation for libVLC.

Memory Pointers in Ruby???

In C you’ll not find any objects, everything is handled with memory pointers.

A memory pointer is a memory address with data.

Let’s see a code example:

module VLC
  extend FFI::Library
  ffi_lib 'vlc'

  attach_function :get_version, :libvlc_get_version, [], :string
  attach_function :new, :libvlc_new, [:int, :int], :pointer
end

VLC.new(0, 0)
# FFI::Pointer address=0x000055c6f04ae7b0

As a result of calling VLC.new we get a pointer.

We have to pass it along to other C functions that need it.

Now:

The full example & how to use it.

module VLC
  extend FFI::Library
  ffi_lib 'vlc'

  attach_function :version, :libvlc_get_version, [], :string

  attach_function :new, :libvlc_new, [:int, :int], :pointer
  attach_function :libvlc_media_new_path, [:pointer, :string], :pointer
  attach_function :libvlc_media_player_new_from_media, [:pointer], :pointer

  attach_function :play, :libvlc_media_player_play, [:pointer], :int
  attach_function :stop, :libvlc_media_player_stop, [:pointer], :int
  attach_function :pause, :libvlc_media_player_pause, [:pointer], :int
end

That’s enough to play some music:

vlc    = VLC.new(0, 0)
media  = VLC.libvlc_media_new_path(vlc, "/home/jesus/Downloads/meditation.mp3")
player = VLC.libvlc_media_player_new_from_media(media)

VLC.play(player)

This will play the music, without opening any kind of visual interface.

You could build your own web interface on top of this.

Give it a try!

Summary

You’ve learned about the amazing power of FFI! A Ruby module that allows you to use external libraries as if they were part of the language itself.

Please share this article so more people can enjoy it 🙂

Thanks for reading!

9 comments
Pavel says a few months ago

Great article, Thanks!

    Jesus Castello says a few months ago

    Thanks for reading 🙂

musa says a few months ago

Thanks for sharing, i couldnt find libvlc.so on my lib folder btw

    Jesus Castello says a few months ago

    Thanks for reading! You need the VLC media player installed on your computer 🙂

    Denis says a few months ago

    If you installed vlc via apt-get you can use ‘libvlc.so.5’. Probably there is no more ‘libvlc.so’

Nickolay says a few months ago

Thanks for great article!

    Jesus Castello says a few months ago

    Thanks for reading! 🙂

Vlad says a few months ago

Is it possible to do it with Mac OS?

    Jesus Castello says a few months ago

    Yes, give it a try 🙂

Comments are closed