Pipelines is the way to create streaming applications with Membrane. Pipeline allows you to spawn
Elements and establish data flow between them. Pipelines can also communicate with elements or terminate them. Elements within a pipeline are often referred to as its
children and the pipeline is their
Connecting elements together is called
To create a pipeline, you need to implement the Membrane.Pipeline behavior. It boils down to implementing callbacks and returning actions from them. For a simple pipeline, it's sufficient to implement the handle_init callback, which is called upon the pipeline startup, and return the spec action, which spawns and links elements. Let's see it in an example:
The code above is one of the simplest examples of Membrane usage. It plays an MP3 file through your computer's default audio playback device with the help of the PortAudio audio I/O library. Let's digest this code and put it to work playing some sound.
Membrane is written in Elixir. It's an awesome programming language of the functional paradigm with great fault tolerance and process management, which made it the best choice for Membrane. If you're not familiar with it, you can use this cheatsheet for a quick look-up. We encourage you to also take a deep look into Elixir and learn how to use it to take full advantage of all its awesomeness. We believe you'll fall in love with Elixir too!
To run the snippet, follow the steps below:
- On Mac OS:
brew install libmad portaudio pkg-config
- On Debian:
apt install libmad0-dev portaudio19-dev
- On Mac OS:
Option 2: If you don't want to use Livebook, you can install Elixir, type
iexto run the interactive shell and paste the snippet there.
Sample pipeline explained
Let's figure out step-by-step what happens in the sample pipeline.
Firstly, we install the needed dependencies. They are plugins, that contain elements that we will use in the pipeline.
Instead of creating a script and using
Mix.install, you can also create a Mix project and add these dependencies to
After installing the dependencies, we can create a module for our pipeline:
Membrane.Pipeline behaviour means we are treating our module as a Membrane Pipeline, so we will have access to functions defined in the
Membrane.Pipeline module, and we can implement some of its callbacks. Let's implement the
handle_init/2 callback, which is a function that is invoked to initialize a pipeline during start-up:
If the concept of callbacks and behaviours is new to you, you can read more about it here, or see examples of behaviours in the Elixir standard library, for example the GenServer and Supervisor behaviours.
handle_init to specify all its elements as children and set up links between them to define the order in which data will flow through the pipeline:
The child function allows us to spawn particular elements. By piping one child to another with the
|> operator, we can specify the order of the elements in the pipeline. Thus, the code above links
Membrane.PortAudio.Sink. Here's what they are:
- Hackney source - an element based on the Hackney HTTP library, that downloads a file via HTTP chunk by chunk, and sends these chunks through its
outputpad. We pass two options to it: a URL where the MP3 is stored and a flag to make it follow HTTP redirects.
- MP3 decoder - an element based on libmad, that accepts MP3 audio on the
inputpad and sends the decoded audio through the
- PortAudio sink - an element that accepts decoded audio on its
inputpad and uses the PortAudio library to play in on the speaker.
In our spec, we don't mention the names of the pads, because
output are the defaults. However, we could explicitly specify them:
The value returned from
is a tuple containing the list of actions and the state.
- Actions are the way to interact with Membrane. Apart from
spec, you can for example return
terminate: reasonthat will stop the elements and terminate the pipeline. Most actions, including
spec, can be returned from multiple callbacks, allowing, for example, to spawn elements on demand. Check the Membrane.Pipeline behavior for the available callbacks and Membrane.Pipeline.Action for the available actions.
- State is an arbitrary data that will be passed to subsequent callbacks as the last argument. It's usually a map. As we have no use for the state in this case, we just set it to an empty map.
When we have created our pipeline module, we can call Membrane.Pipeline.start_link to run it:
We pass to it the pipeline module and options, which in our case is the
mp3_url. The options are passed directly to the
Congratulations! You've just built and run your first Membrane application. Let's now have a deeper look at the elements.