Skip to content

Add multivariant output support to Membrane.Transcoder#21

Merged
khamilowicz merged 8 commits into
masterfrom
multivariant-output
May 29, 2026
Merged

Add multivariant output support to Membrane.Transcoder#21
khamilowicz merged 8 commits into
masterfrom
multivariant-output

Conversation

@khamilowicz

@khamilowicz khamilowicz commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • On-request output padsMembrane.Transcoder now exposes availability: :on_request output pads, each accepting independent output_stream_format, transcoding_policy, and native_acceleration options via via_out. Single-output backward-compatible usage is unchanged.
  • Fan-out via Tee — multi-output mode inserts a Membrane.Tee.Parallel between the connector and per-output transcoding chains, created atomically with all outputs in one spec to avoid the race where the Tee could receive data before any output is connected.
  • Exampleexamples/multivariant_output.exs demonstrates a single H264 input transcoded simultaneously to H264, H265, and VP8 outputs.
  • Tests — 5 new integration tests cover multivariant video and audio scenarios. Content correctness is verified by asserting each multivariant output is byte-identical to the equivalent single-output pipeline run.

Usage

# Multiple outputs
child(:transcoder, Membrane.Transcoder),
get_child(:transcoder)
|> via_out(Pad.ref(:output, 0), options: [output_stream_format: H264])
|> child(:hd_sink, Membrane.File.Sink),
get_child(:transcoder)
|> via_out(Pad.ref(:output, 1), options: [output_stream_format: VP8])
|> child(:vp8_sink, Membrane.File.Sink)

Test plan

  • mix test --exclude vk — 54 tests, 0 failures
  • Single-output backward-compatible usage unchanged (existing test suite covers this)
  • Multivariant output produces byte-identical results to equivalent single-output pipelines
  • Run elixir examples/multivariant_output.exs to verify the example produces three non-empty output files

🤖 Generated with Claude Code

@khamilowicz khamilowicz requested a review from varsill May 21, 2026 12:25
@khamilowicz khamilowicz force-pushed the multivariant-output branch 3 times, most recently from 406c796 to 0eb8420 Compare May 22, 2026 08:33
Comment thread PLAN.prd Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to have this commited

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to update existing examples as well

Comment thread lib/transcoder/audio.ex Outdated
end

defp child_name(nil, base), do: base
defp child_name(suffix, base), do: :"#{suffix}_#{base}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am slightly worried about creating atoms dynamically since they are not garbage collected. How about making child's name a String?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChildSpec allows only atoms or tuples as names, I went with tuple {suffix, base}.

Comment thread lib/transcoder/video.ex Outdated

defp maybe_plug_encoder_and_parser(builder, %VP8{}) do
defp maybe_plug_encoder_and_parser(builder, %VP8{}, suffix) do
cpu_quota = :erlang.system_info(:cpu_quota)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I know it's unrelates but it brought my attention :D)
What :cpu_quota would return in contenerized environments (e.g. with docker)?
The problem is that if :cpu_quota doesn't reflect some cgroup limits, then we might end up in a situation that we will try to use all the cores available in the machine, and we will be given only fraction of it, which ends up in thrashing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used :logical_processors_online to handle this case.

The output pad is now availability: :on_request, allowing a single
transcoder bin to produce multiple independent output streams each
with their own output_stream_format, transcoding_policy, and
native_acceleration options.

Single-output backward-compatible usage is unchanged — implicit
via_out links inherit bin-level options and use the same internal
child names, so existing tests and code require no modification.

Multi-output pipelines use a Membrane.Tee.Parallel (new dep) placed
between the connector and per-output transcoding chains within a
single atomic spec, avoiding the race where the Tee could receive
data before any output is connected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@khamilowicz khamilowicz force-pushed the multivariant-output branch 2 times, most recently from 5a6305d to 3b00a93 Compare May 26, 2026 08:55
@khamilowicz khamilowicz requested a review from varsill May 26, 2026 09:06
@khamilowicz khamilowicz force-pushed the multivariant-output branch from 3b00a93 to b29e223 Compare May 26, 2026 09:38
- use strings as children names
- update examples
@khamilowicz khamilowicz force-pushed the multivariant-output branch from b29e223 to be2620a Compare May 26, 2026 09:41
Comment thread test/integration_test.exs Outdated
@khamilowicz khamilowicz requested a review from varsill May 26, 2026 14:29

@varsill varsill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's the last round of review :D
After addressing my comments, I think it's the right time to bump minor version of the plugin so that we can release it immediately afterwards

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can mention this new example in README.md

Comment thread test/integration_test.exs Outdated
end

@tag :tmp_dir
test "multivariant output: two video outputs with different resolutions", %{tmp_dir: tmp_dir} do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are more like two different codecs, not two different resolutions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the same time, I think it would be good to have a separate test that produces two video outputs with different resolution

Comment thread lib/transcoder.ex Outdated
Comment on lines +412 to +414
defp pad_id_to_suffix(id) when is_integer(id), do: "output_#{id}"
defp pad_id_to_suffix(id) when is_atom(id), do: "output_#{id}"
defp pad_id_to_suffix(id), do: "output_#{:erlang.phash2(id)}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I like this function - it will generate output_nil if id == nil but output_<some hash> if id == "some_hash".
Do we really need to use this hash here?

@varsill varsill moved this to In Review in Smackore May 28, 2026

@varsill varsill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I just left one comment for clarification.
Except for that - as discussed, let's open a separate PR with a feature of providing a desired output resolution.

Comment thread lib/transcoder.ex Outdated
suffix = pad_id_to_suffix(pad_id)
funnel_name = :"funnel_#{suffix}"
suffix = {pad_id, :output}
funnel_name = {pad_id, :output, :funnel}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why funnel_name cannot be created just as any other element's name i.e. "element type" + suffix?

@khamilowicz khamilowicz requested a review from varsill May 29, 2026 11:03

@varsill varsill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇

@khamilowicz khamilowicz merged commit 23b0550 into master May 29, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in Smackore May 29, 2026
@khamilowicz khamilowicz deleted the multivariant-output branch May 29, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants