Skip to content
9 changes: 8 additions & 1 deletion lib/boombox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Boombox do
alias Membrane.RTP

@type transcoding_policy_opt :: {:transcoding_policy, :always | :if_needed | :never}
@type ignore_timestamps_opt :: {:ignore_timestamps, boolean()}
@type hls_variant_selection_policy_opt ::
{:variant_selection_policy, HTTPAdaptiveStream.Source.variant_selection_policy()}

Expand Down Expand Up @@ -74,6 +75,12 @@ defmodule Boombox do
| {:port, :inet.port_number()}
| {:target, String.t()}
| transcoding_policy_opt()
| ignore_timestamps_opt()
]

@type out_webrtc_opts :: [
transcoding_policy_opt()
| ignore_timestamps_opt()
]

@type input ::
Expand Down Expand Up @@ -102,7 +109,7 @@ defmodule Boombox do
| {:mp4 | :aac | :wav | :mp3 | :ivf | :ogg | :h264 | :h265, location :: String.t(),
[transcoding_policy_opt()]}
| {:webrtc, webrtc_signaling()}
| {:webrtc, webrtc_signaling(), [transcoding_policy_opt()]}
| {:webrtc, webrtc_signaling(), out_webrtc_opts()}
| {:whip, uri :: String.t(),
[{:token, String.t()} | {bandit_option :: atom(), term()} | transcoding_policy_opt()]}
| {:hls, location :: String.t()}
Expand Down
2 changes: 1 addition & 1 deletion lib/boombox/bin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ defmodule Boombox.Bin do
| {:mp4 | :aac | :wav | :mp3 | :ivf | :ogg | :h264 | :h265, location :: String.t(),
[Boombox.transcoding_policy_opt()]}
| {:webrtc, Boombox.webrtc_signaling()}
| {:webrtc, Boombox.webrtc_signaling(), [Boombox.transcoding_policy_opt()]}
| {:webrtc, Boombox.webrtc_signaling(), [Boombox.out_webrtc_opts()]}
| {:whip, uri :: String.t(),
[
{:token, String.t()}
Expand Down
24 changes: 15 additions & 9 deletions lib/boombox/internal_bin/rtp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ defmodule Boombox.InternalBin.RTP do
optional(:audio) => parsed_output_track_config(),
optional(:video) => parsed_output_track_config()
},
transcoding_policy: :always | :if_needed | :never
transcoding_policy: :always | :if_needed | :never,
ignore_timestamps: boolean()
Copy link
Member

Choose a reason for hiding this comment

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

trying to do a small brainstorm here: did you have any other ideas about what could be the name of this option?

Copy link
Member

@mat-hek mat-hek Aug 14, 2025

Choose a reason for hiding this comment

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

Yup, we definitely need a better name. This sounds like we somehow discard timestamps, while we just don't apply pacing. So I'd call it pace_control or realtime or similar. Also, a typedoc would be useful.

}

@type parsed_track_config :: parsed_input_track_config() | parsed_output_track_config()
Expand Down Expand Up @@ -126,17 +127,17 @@ defmodule Boombox.InternalBin.RTP do
Membrane.ChildrenSpec.t()
) :: Ready.t()
def link_output(opts, track_builders, spec_builder) do
parsed_opts = validate_and_parse_options(:output, opts)
opts = validate_and_parse_options(:output, opts)

spec = [
spec_builder,
child(:rtp_muxer, Membrane.RTP.Muxer)
|> child(:udp_rtp_sink, %Membrane.UDP.Sink{
destination_address: parsed_opts.address,
destination_port_no: parsed_opts.port
destination_address: opts.address,
destination_port_no: opts.port
}),
Enum.map(track_builders, fn {media_type, builder} ->
track_config = parsed_opts.track_configs[media_type]
track_config = opts.track_configs[media_type]

{output_stream_format, parser, payloader} =
case track_config.encoding_name do
Expand Down Expand Up @@ -166,11 +167,15 @@ defmodule Boombox.InternalBin.RTP do
builder
|> child({:rtp_transcoder, media_type}, %Membrane.Transcoder{
output_stream_format: output_stream_format,
transcoding_policy: parsed_opts.transcoding_policy
transcoding_policy: opts.transcoding_policy
})
|> child({:rtp_out_parser, media_type}, parser)
|> child({:rtp_payloader, media_type}, payloader)
|> child({:realtimer, media_type}, Membrane.Realtimer)
|> then(
&if opts.ignore_timestamps,
do: &1,
else: child(&1, {:realtimer, media_type}, Membrane.Realtimer)
)
|> via_in(:input,
options: [
encoding: track_config.encoding_name,
Expand Down Expand Up @@ -222,8 +227,9 @@ defmodule Boombox.InternalBin.RTP do
parsed_opts

:output ->
transcoding_policy = opts |> Keyword.get(:transcoding_policy, :if_needed)
parsed_opts |> Map.put(:transcoding_policy, transcoding_policy)
parsed_opts
|> Map.put(:transcoding_policy, Keyword.get(opts, :transcoding_policy, :if_needed))
|> Map.put(:ignore_timestamps, Keyword.get(opts, :ignore_timestamps, false))
end
end

Expand Down
19 changes: 14 additions & 5 deletions lib/boombox/internal_bin/webrtc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ defmodule Boombox.InternalBin.WebRTC do
end

@spec link_output(
[Boombox.transcoding_policy_opt()],
Boombox.out_webrtc_opts(),
Boombox.InternalBin.track_builders(),
Membrane.ChildrenSpec.t(),
webrtc_sink_new_tracks(),
Expand All @@ -116,7 +116,7 @@ defmodule Boombox.InternalBin.WebRTC do
end

@spec handle_output_tracks_negotiated(
[Boombox.transcoding_policy_opt()],
Boombox.out_webrtc_opts(),
Boombox.InternalBin.track_builders(),
Membrane.ChildrenSpec.t(),
webrtc_sink_new_tracks(),
Expand All @@ -133,7 +133,8 @@ defmodule Boombox.InternalBin.WebRTC do
end

defp do_link_output(opts, track_builders, spec_builder, tracks, state) do
transcoding_policy = opts |> Keyword.get(:transcoding_policy, :if_needed)
transcoding_policy = Keyword.get(opts, :transcoding_policy, :if_needed)
ignore_timestamps = Keyword.get(opts, :ignore_timestamps, false)
tracks = Map.new(tracks, &{&1.kind, &1.id})

spec = [
Expand All @@ -145,15 +146,23 @@ defmodule Boombox.InternalBin.WebRTC do
output_stream_format: Membrane.Opus,
transcoding_policy: transcoding_policy
})
|> child(:webrtc_out_audio_realtimer, Membrane.Realtimer)
|> then(
&if ignore_timestamps,
do: &1,
else: child(&1, :webrtc_out_audio_realtimer, Membrane.Realtimer)
)
|> via_in(Pad.ref(:input, tracks.audio), options: [kind: :audio])
|> get_child(:webrtc_output)

{:video, builder} ->
negotiated_codecs = state.output_webrtc_state.negotiated_video_codecs

builder
|> child(:webrtc_out_video_realtimer, Membrane.Realtimer)
|> then(
&if ignore_timestamps,
do: &1,
else: child(&1, :webrtc_out_video_realtimer, Membrane.Realtimer)
)
|> child(:webrtc_video_transcoder, %Membrane.Transcoder{
output_stream_format: fn input_format ->
resolve_output_video_stream_format(
Expand Down