|
| 1 | +# Manual demands |
| 2 | + |
| 3 | +Elements that use `:manual` flow control are responsible for explicitly |
| 4 | +requesting data from their input pads. This guide explains how that mechanism |
| 5 | +works and how to choose the right demand unit for your use case. |
| 6 | + |
| 7 | +## How manual flow control works |
| 8 | + |
| 9 | +When an input pad has `flow_control: :manual`, the element controls exactly |
| 10 | +how much data it wants to receive on that pad at any given time. Each such |
| 11 | +pad has an internal input queue that manages buffering and flow. |
| 12 | + |
| 13 | +The general flow for a filter with a manual input and output looks like this: |
| 14 | + |
| 15 | +1. **At startup**, the input queue issues an initial demand upstream to |
| 16 | + pre-fill itself to its target size. This happens automatically before the |
| 17 | + element returns its first `:demand` action, so that data is already |
| 18 | + available when the element first asks for it. |
| 19 | + |
| 20 | +2. **When downstream demands data** (via `handle_demand/5`), the element |
| 21 | + requests some amount from its input queue using the |
| 22 | + [`:demand`](`t:Membrane.Element.Action.demand/0`) action. |
| 23 | + |
| 24 | +3. **The queue delivers those buffers** synchronously from what it has |
| 25 | + buffered, invoking |
| 26 | + [`handle_buffer/4`](`c:Membrane.Element.WithInputPads.handle_buffer/4`). |
| 27 | + If the queue doesn't have enough, it delivers what it has and requests more |
| 28 | + from upstream. |
| 29 | + |
| 30 | +4. **As the queue empties**, it automatically issues new demands upstream to |
| 31 | + stay close to its target size. |
| 32 | + |
| 33 | +## Demand is overwritten, not accumulated |
| 34 | + |
| 35 | +A critical property of `handle_demand/5`: the `demand_size` argument is the |
| 36 | +**total current demand** from downstream, not a delta. If the downstream |
| 37 | +element requested 5 buffers and then requested 16 more before the first |
| 38 | +request was fulfilled, `handle_demand` will be called once with `16` — not |
| 39 | +twice with `5` and `16`, and not once with `21`. |
| 40 | + |
| 41 | +This means the element does not need to track previously received demands. |
| 42 | +It simply produces as many buffers as `handle_demand` says. |
| 43 | + |
| 44 | +## Demand units |
| 45 | + |
| 46 | +The `demand_unit` option in the pad spec controls how "how much data" is |
| 47 | +measured. It affects both the value passed to `handle_demand/5` on the |
| 48 | +upstream side and the size argument of the `:demand` action. |
| 49 | + |
| 50 | +### `:buffers` and `:bytes` |
| 51 | + |
| 52 | +These are the classic demand units. |
| 53 | + |
| 54 | +With `:buffers`, the demand is a count of buffers. With `:bytes`, the demand |
| 55 | +is a number of payload bytes. When demanding by bytes, the input queue will |
| 56 | +split buffers at exact byte boundaries if needed — the resulting fragments |
| 57 | +share the same metadata and timestamps as the original buffer. |
| 58 | + |
| 59 | +```elixir |
| 60 | +def_input_pad :input, |
| 61 | + flow_control: :manual, |
| 62 | + demand_unit: :buffers, |
| 63 | + accepted_format: _any |
| 64 | + |
| 65 | +@impl true |
| 66 | +def handle_demand(:output, demand_size, :buffers, _ctx, state) do |
| 67 | + buffers = produce_buffers(demand_size, state) |
| 68 | + {[buffer: {:output, buffers}], state} |
| 69 | +end |
| 70 | +``` |
| 71 | + |
| 72 | +#### Deadlocks |
| 73 | + |
| 74 | +The most common pitfall with `:buffers`/`:bytes` demands is a deadlock: the |
| 75 | +downstream element is waiting for buffers that will never arrive because the |
| 76 | +upstream element stopped producing. |
| 77 | + |
| 78 | +This happens when a filter produces fewer buffers than demanded. For example, |
| 79 | +if downstream demanded 10 and the filter only sent 8, the downstream will wait |
| 80 | +forever for the remaining 2 — unless something triggers another `handle_demand` |
| 81 | +call. |
| 82 | + |
| 83 | +There are two ways to avoid this: |
| 84 | + |
| 85 | +1. **Track the shortfall explicitly** — if the filter knows it sent fewer |
| 86 | + buffers than demanded, it can issue an explicit `:demand` action on its |
| 87 | + input to trigger another round of processing. |
| 88 | + |
| 89 | +2. **Use the `:redemand` action** — returning |
| 90 | + [`:redemand`](`t:Membrane.Element.Action.redemand/0`) causes Membrane to |
| 91 | + re-invoke `handle_demand` with the remaining unfulfilled demand. This is a |
| 92 | + safe way to signal "I may not be done yet, check again." If there is no |
| 93 | + outstanding demand on the output, `:redemand` has no effect. |
| 94 | + |
| 95 | +> #### Deadlocks are intermittent {: .warning} |
| 96 | +> |
| 97 | +> Deadlocks caused by under-producing are especially tricky because they only |
| 98 | +> manifest sometimes: if downstream happens to issue a new demand while the |
| 99 | +> filter is still processing, the deadlock is avoided. This makes them easy to |
| 100 | +> miss in testing. |
| 101 | +
|
| 102 | +#### Sending slightly more than demanded |
| 103 | + |
| 104 | +It is acceptable to send slightly more buffers than demanded. The extra buffers |
| 105 | +will be buffered in the downstream queue. This can be useful to avoid waiting |
| 106 | +for one more demand round-trip when a buffer is already ready. |
| 107 | + |
| 108 | +However, the overshoot should always be a small, **bounded constant** — never |
| 109 | +proportional to the stream or unbounded. If the filter always sends `n+k` |
| 110 | +buffers when asked for `n`, the downstream queue will grow by `k` on every |
| 111 | +cycle and eventually overflow. |
| 112 | + |
| 113 | +### Timestamp demand units |
| 114 | + |
| 115 | +Timestamp demand units let an element request data in terms of _time duration_ |
| 116 | +rather than buffer count or byte size. This is useful when the element needs to |
| 117 | +process a fixed time window of media regardless of how many buffers that window |
| 118 | +contains — for example, always consuming exactly 40 ms of audio. |
| 119 | + |
| 120 | +Timestamp demand units are only applicable to **input pads**. Output pads do |
| 121 | +not support them. If an input pad uses a timestamp demand unit and the upstream |
| 122 | +element's linked output pad does not specify a `demand_unit`, that element will |
| 123 | +receive `handle_demand/5` with the demand expressed in `:buffers`. |
| 124 | + |
| 125 | +The available variants are: |
| 126 | + |
| 127 | +- `:timestamp` or `{:timestamp, :dts_or_pts}` — uses DTS if present on a |
| 128 | + buffer, falls back to PTS. |
| 129 | +- `{:timestamp, :pts}` — uses the PTS field of each buffer. |
| 130 | +- `{:timestamp, :dts}` — uses the DTS field of each buffer. |
| 131 | + |
| 132 | +When using a timestamp unit, the demand size is a `t:Membrane.Time.t/0` |
| 133 | +value (nanoseconds). The queue delivers buffers until the elapsed timestamp |
| 134 | +span — measured as `last_consumed_buffer.ts - first_consumed_buffer.ts` — |
| 135 | +reaches the demanded duration. |
| 136 | + |
| 137 | +**Key difference from `:buffers`/`:bytes`**: timestamp demand does _not_ |
| 138 | +decrement as buffers are consumed. The demanded duration is a fixed threshold, |
| 139 | +not a shrinking counter. Once the demanded window has been satisfied, the |
| 140 | +element must issue a new, _larger_ demand to make progress. Issuing the same |
| 141 | +demand again after the window has elapsed will produce a warning and deliver no |
| 142 | +buffers. |
| 143 | + |
| 144 | +A typical pattern for time-based pacing: |
| 145 | + |
| 146 | +```elixir |
| 147 | +def_input_pad :input, |
| 148 | + flow_control: :manual, |
| 149 | + demand_unit: {:timestamp, :dts}, |
| 150 | + accepted_format: _any |
| 151 | + |
| 152 | +@impl true |
| 153 | +def handle_init(_ctx, opts) do |
| 154 | + {[], %{window_end: Membrane.Time.milliseconds(40)}} |
| 155 | +end |
| 156 | + |
| 157 | +@impl true |
| 158 | +def handle_demand(:output, _demand_size, :buffers, _ctx, state) do |
| 159 | + # Advance the window by 40 ms each time the downstream asks for more data |
| 160 | + {[demand: {:input, state.window_end}], |
| 161 | + %{state | window_end: state.window_end + Membrane.Time.milliseconds(40)}} |
| 162 | +end |
| 163 | +``` |
| 164 | + |
| 165 | +In this example, successive demands are `40ms`, `80ms`, `120ms`, … All are |
| 166 | +measured from the timestamp of the very first buffer ever consumed on the pad, |
| 167 | +so each demand naturally covers the next 40 ms slice of the stream. |
| 168 | + |
| 169 | +> #### Buffers must carry timestamps {: .warning} |
| 170 | +> |
| 171 | +> When using a timestamp demand unit, every buffer in the stream must have |
| 172 | +> its relevant timestamp field (`:pts` or `:dts`) set to a non-`nil` value. |
| 173 | +> Missing timestamps will cause the queue to behave incorrectly. |
| 174 | +
|
| 175 | +> #### Timestamps should be monotonic {: .warning} |
| 176 | +> |
| 177 | +> The queue assumes that timestamps are non-decreasing. Non-monotonic |
| 178 | +> timestamps (e.g. those produced by B-frame reordering when using `:pts`) |
| 179 | +> will trigger a warning. Prefer `{:timestamp, :dts}` for streams where DTS |
| 180 | +> is available and monotonic. |
0 commit comments