Get Started with Membrane

Pads and linking

You learned how to link elements in the Pipeline chapter and how to define pads in the Element chapter. Now, let's have a deeper dive into pads and their capabilities.

Dynamic Pads

A dynamic pad is a type of pad that acts as a template - each time some other pad is linked to a dynamic pad, a new instance of it is created.

Dynamic pads don't have to be linked when the element is started. This needs to be handled by the element, but in return, it gives new possibilities when the number of pads can change on the fly.

Another use case for dynamic pads is when the number of pads is not known at the compile time. For example, an audio mixer may have any number of inputs.

Creating an element with dynamic pads

To make a pad dynamic, you need to set its availability to :on_request in def_input_pad or def_output_pad.

Now, each time some element is linked to this pad, a new instance of the pad is created and handle_pad_added callback is invoked. Instances of a dynamic pad can be referenced as Pad.ref(pad_name, pad_id). When a dynamic pad is unlinked, the handle_pad_removed callback is called.

Gotchas

As usual, with great power comes great responsibility. When implementing an element with dynamic pads, you need to consider what should happen when they are added or removed. Usually, you need to implement handle_pad_added and handle_pad_removed callbacks, and the logic of an element may generally become more complicated.

Linking dynamic pads

Let's see how to link dynamic pads. We'll use membrane_file_plugin, a plugin that allows reading and writing to files, and membrane_tee_plugin which allows forwarding the stream from a single input to multiple outputs. Running the pipeline below with Membrane.Pipeline.start_link(MyPipeline) should copy the "source" file to "target1", "target2" and "target3" files. Don't forget to create the "source" file before.

Mix.install([ :membrane_file_plugin, :membrane_tee_plugin ]) defmodule MyPipeline do use Membrane.Pipeline alias Membrane.{File, Tee} @impl true def handle_init(_ctx, _options) spec = [ child(%File.Source{location: "source"}) |> child(:tee, Tee.Parallel), get_child(:tee) |> child(%File.Sink{location: "target1"}), get_child(:tee) |> child(%File.Sink{location: "target2"}), get_child(:tee) |> child(%File.Sink{location: "target3"}) ] end end

The Membrane.Tee.Parallel element has a single static input and a single dynamic output pad. Because the output is dynamic, each time we link it, a new pad instance is created with a unique reference. In the example above, pad references were generated automatically. It's possible to specify them directly with via_in or via_out and Membrane.Pad.ref:

spec = [ child(%File.Source{location: "source"}) |> child(:tee, Tee.Parallel), get_child(:tee) |> via_out(Pad.ref(:output, 1)) |> child(%File.Sink{location: "target1"}), get_child(:tee) |> via_out(Pad.ref(:output, 2)) |> child(%File.Sink{location: "target2"}), get_child(:tee) |> via_out(Pad.ref(:output, 3)) |> child(%File.Sink{location: "target3"}) ]

In this case, it won't make a difference, but elements can rely on pad references and use them to identify the stream that should be sent or received through the given pad.

Pad options

Just like elements, pads can have options. They have to be defined with the options key in def_input_pad and def_output_pad, using the same syntax as the element options. For example, Membrane.AudioMixer does it to make its input pad accept the offset option:

def_input_pad :input, availability: :on_request, options: [ offset: [ spec: Time.non_neg(), default: 0, description: "Offset of the input audio at the pad." ] ], # ...

Pad options can be set via a keyword list within the second argument of via_in or via_out. Here's how to provide the offset option to the mixer:

spec = [ # ... child(Membrane.MP3.MAD.Decoder) |> via_in(:input, options: [offset: Membrane.Time.seconds(2)]) |> child(Membrane.AudioMixer) # ... ]

Pad options' values can be accessed in the context of the handle_pad_added callback:

@impl true def handle_pad_added(pad, context, state) do %{offset: offset} = context.pad_options # ... end
Next chapter
Flow control
Next Chapter