Skip to content
This repository has been archived by the owner on Oct 10, 2023. It is now read-only.

Commit

Permalink
Improve pads description and mention pad opts
Browse files Browse the repository at this point in the history
  • Loading branch information
bblaszkow06 committed Mar 28, 2019
1 parent a5e82b6 commit a59af13
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 31 deletions.
2 changes: 1 addition & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
## Concepts

* [Elements](concepts/elements.md)
* [Pads](concepts/pads.md)
* [Lifecycle](concepts/lifecycle.md)
* [Pads](concepts/pads.md)
* [Pipelines](concepts/pipelines.md)

## Building application
Expand Down
31 changes: 29 additions & 2 deletions concepts/elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,43 @@ audio encoder (converting raw audio to encoded format like MP3),
file source (reading data from a file and passing it to other elements) or
UDP sink (sending data from an application via UDP socket).

There are three basic types of elements: `sink`, `source`, and `filters`:
## Element types

There are three basic types of elements:

* `Source` - responsible for getting (or generating) data delivering it to other elements
* `Sink` - defines an endpoint for data flowing through an application.
* `Filter` - an element that receives data from other elements, processes it and sends it further to the next elements

## Playback states

Every element may be in one of three states:

* `:stopped` - element has been created
* `:prepared` - element should be ready for processing data. All necessary resources should be allocated and initialized.
* `:playing` - element is actually processing data

Elements define their own `options` that parametrize their work. For example, some audio decoder may have an option named `bitrate` that represents bitrate of the output data.
More detailed info can be found in [chapter about element's lifecycle](./lifecycle.md)

## Pads

Element's pads, much like contact pads on printed circuit board, are inputs and outputs of an element
and are used to connect the elements with each other.

The are covered in greater detail in [this chapter](./pads.md)

## Options

Both elements and their pads may define their own `options` that parametrize their work.
For example, some audio decoder may have an option named `bitrate` that represents bitrate of the output data.

The options for an element are passed when the element is created, while pad options are provided when
two elements are linked

## Defining an elements

Elements are Elixir modules that `use` a proper module
(either [`Membrane.Element.Base.Sink`](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Sink.html),
[`Membrane.Element.Base.Filter`](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Filter.html) or
[`Membrane.Element.Base.Source`](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Source.html)).
The have to define options and pads using provided macros (`def_input_pad`, `def_output_pad` and `def_options`) implement at least required callbacks.
65 changes: 63 additions & 2 deletions concepts/pads.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
## Pads and capabilities
# Pads and capabilities

To create the flow of data between elements in the application, they have to communicate with each other. For that purpose, the concept of `pads` and `capabilities` is used. `Pads` are basically inputs and outputs of the elements and because of that, there are two types of pads: `input` and `output`. It is worth mentioning that `Source` elements may only contain `output` pads, `Sinks` contain only `input` pads, and `Filters` can have both of them.

Every pad has some capabilities, which defines a type of data that it is expecting. This format can be, for example, raw audio with a specific sample rate or encoded audio in a given format.

Two elements that should send data between each other, should have linked pads. One pad can be linked with only one other pad of a different element. Only links between `output` and `input` pads are allowed. Furthermore, to link two pads, their capabilities have to be compatible.
In order to send data between elements their pads need to be linked. There are a couple of rules that apply to pad linking:

* One pad of an elment can only be linked with one pad from another element.
([dynamic pads](#dynamic-pads) can help with that limitation)
* Only links between `output` and `input` pads are allowed.
* Capabilities of pads have to be compatible.

## Pad properties

Each pad have some properties that can be set when defining a pad:

* Availability - either `:always` - meaning the pad is static and available from the moment an element
is spawned or `:on_request` meaning it is [dynamic](#dynamic-pads)
* Mode:
* `:push` - it's a simple mode where an element producing data `pushes` it right away on the output pad
and input pad in this mode should be always ready to process that data
* `:pull` - this mode provides a back-pressure mechanism - Source/Filter is only allowed to send data
after receiving demand from downstream element and the responsibility of Sink/Filter is to send those
demands
* Demand unit (only pull input pads) - specifies what unit will be used to send demands
(either `:bytes` or `:buffers`)
* Caps - capabilities supported by the pad
* Options - specification of options accepted by pad

## Dynamic Pads

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 instace of it is created. That allow

### Why do we need dynamic pads?

In some applications manually specifying pads isn't good enough.
Let's consider audio mixer - it is a type of element that is likely to have numerous pads
with the same definition.
Thanks to dynamic pads there's no need to copy-paste the same definition over and over again.
Plus you won't be limited by the number of pads that have been defined.

### Creating an element with dynamic pads

Creating an element with dynamic pads is not much different than
creating one with static pads. The key difference is that
we need to specify that one of the pads is dynamic, by setting pad `availability`
to `:on_request`.

Now, each time some element is linked to this pad, a new instance of the
pad is created and callback [`handle_pad_added`](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Mixin.CommonBehaviour.html#c:handle_pad_added/3)
is invoked. Instances of a pad can be referenced as `{:dynamic, :input, number}`

### Handling events

What if Event such as End of Stream is passed through a pad of filter element?
Usually if you are using pads `:input` and `:output` the default
action is to forward an event. It means that if an event comes at `:input`
pad it is set via `:output` pad and vice versa.

There is one problem though, which of dynamic pad would be considered `:input`
and which would be considered `:output`? That's why you have to implement
`handle_event/4` yourself.

## Defining a pad

Pads are defined in element's module with [def_input_pad](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Mixin.SinkBehaviour.html#def_input_pad/2) and [def_output_pad](https://hexdocs.pm/membrane_core/Membrane.Element.Base.Mixin.SinkBehaviour.html#def_output_pad/2)
4 changes: 2 additions & 2 deletions concepts/pipelines.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Pipelines
# Pipelines

A pipeline is a container that consists of many elements and links between them. Like an Element, every Pipeline also has a playback state and on its basis, it manages the state of the contained elements.

During the application execution, elements may want to signal some events. For that purpose, they send the `notification` to their supervisor, which in most cases is a pipeline. A programmer can handle those notifications by defining the appropriate method in the pipeline module.
During the application execution, elements may want to signal some events. For that purpose, they send the `Notification` to their supervisor, which in most cases is a pipeline. A programmer can handle those notifications by defining the appropriate method in the pipeline module.
24 changes: 24 additions & 0 deletions creating_app/pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ Then, we should initialize a map containing links between elements. Keys and val
}
```

If a pad has some options you can provide it in keyword at the end of the tuple:

```elixir
links = %{
# ...
{:decoder, :output} => {:converter, :input, pad: [some_option: true]},
# ...
}
```

Available pad options are documented in elements' modules.

When an input pad works in `:pull` mode you can also configure the buffer:

```elixir
links = %{
# ...
{:decoder, :output} => {:converter, :input, buffer: [preffered_size: 42_000]},
# ...
}
```

Available settings are described in the [InputBuffer docs](https://hexdocs.pm/membrane_core/Membrane.Core.InputBuffer.html#t:props_t/0).

Last but not least, we should return created terms in the correct format - `%Pipeline.Spec{}`

```elixir
Expand Down
50 changes: 26 additions & 24 deletions creating_element/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,31 @@ def_input_pad :input,
availability: :always,
mode: :pull,
demand_unit: :bytes,
caps: :any
caps: :any,
options: [
divisor: [
type: :integer,
default: 1,
description: "Number by which the counter will be divided before sending notification"
]
]

def_output_pad :output,
availability: :always,
mode: :pull,
caps: :any
```

In above definition, availability `:always` means that pad of this element is always available. The other option is `:on_request` which means that an instances of pad are created on request. For example, an audio mixer element can use it to allow linking new sources, even while in `:playing` state.

In above definition, availability `:always` means that pad of this element is always available.
`:pull` mode means that this element sends buffers to the next element only when they are demanded. When a demand is received on an output pad, the `handle_demand` callback is invoked with total demand for the pad and a unit - either `:bytes` or `:buffers`.

For input pads in `:pull` mode one more entry has to be provided - `:demand_unit`. It determines in which unit the demand is sent to the upstream element and can be set, as mentioned, to `:bytes` or `:buffers`.

The other option is `:push` mode, that means that element will send buffers whenever it wants or whenever they are available. In this case, specifying a demand unit is unnecessary.

The next element in the keyword list represents the capabilities (caps) of the pad. `:any` means that any type of buffer can be passed on this pad. If you want to restrict the types of data allowed on this pad you can define caps specifications as described in [docs](https://hexdocs.pm/membrane_core/0.3.0/Membrane.Caps.Matcher.html).

Last entry for input pad defines an option for this pad added just for demonstration purpose.
The options for pads are defined just like element's options in `def_options` macro.

## `handle_init/1`

In `handle_init` we initialize the internal state of the element. As the first argument of callback, options will be received. In our state, we will create variable for counting buffers and timer. Timer won't be initialized at this point, we will wait for `handle_prepared_to_playing/2` callback, which informs that pipeline is in the `:playing` state.
Expand Down Expand Up @@ -122,13 +129,15 @@ end
All messages sent to the element's process that were not recognized as internal membrane messages (like buffers, caps, events or notifications) are handled in `handle_other/3`.
We will receive our ticks here, so we will know that we should zero the counter. It is also a good place to send a notification to the pipeline with the statistics.

This also presents how pad options can be accessed via context.

```elixir
@impl true
def handle_other(:tick, _ctx, state) do
def handle_other(:tick, ctx, state) do
# create the term to send
notification = {
:counter,
state.counter
div(state.counter, ctx.pads.input.options.divisor)
}

# reset the timer
Expand Down Expand Up @@ -169,7 +178,14 @@ defmodule Your.Module.Element do
availability: :always,
mode: :pull,
demand_unit: :bytes,
caps: :any
caps: :any,
options: [
divisor: [
type: :integer,
default: 1,
description: "Number by which the counter will be divided before sending notification"
]
]

def_output_pad :output,
availability: :always,
Expand Down Expand Up @@ -211,11 +227,11 @@ defmodule Your.Module.Element do
end

@impl true
def handle_other(:tick, _ctx, state) do
def handle_other(:tick, ctx, state) do
# create the term to send
notification = {
:counter,
state.counter
div(state.counter, ctx.pads.input.options.divisor)
}

# reset the timer
Expand All @@ -225,17 +241,3 @@ defmodule Your.Module.Element do
end
end
```

## Test the element

Our element is now ready! The last step is to put it in some pipeline and add callback handling notifications in the pipeline. The simple example of such callback is the following:

```elixir
@impl true
def handle_notification(notification, _elem_name, state) do
IO.inspect(notification)
{:ok, state}
end
```

You can use the pipeline from the previous chapter and put this element between the sink and the decoder.

0 comments on commit a59af13

Please sign in to comment.