From 1095be1672ce9d927ea023df85d57aaea01c602b Mon Sep 17 00:00:00 2001 From: "feliks.pobiedzinski@swmansion.com" Date: Tue, 4 Feb 2025 14:16:01 +0100 Subject: [PATCH] Use Transcoder from membrane_transcoder_plugin --- lib/boombox/elixir_stream.ex | 4 +- lib/boombox/hls.ex | 4 +- lib/boombox/mp4.ex | 4 +- lib/boombox/rtp.ex | 2 +- lib/boombox/transcoder.ex | 94 --------------- lib/boombox/transcoder/audio.ex | 90 -------------- lib/boombox/transcoder/forwarding_filter.ex | 117 ------------------ lib/boombox/transcoder/video.ex | 124 -------------------- lib/boombox/webrtc.ex | 4 +- mix.exs | 8 +- mix.lock | 1 + 11 files changed, 11 insertions(+), 441 deletions(-) delete mode 100644 lib/boombox/transcoder.ex delete mode 100644 lib/boombox/transcoder/audio.ex delete mode 100644 lib/boombox/transcoder/forwarding_filter.ex delete mode 100644 lib/boombox/transcoder/video.ex diff --git a/lib/boombox/elixir_stream.ex b/lib/boombox/elixir_stream.ex index 750a083..e6b4bc0 100644 --- a/lib/boombox/elixir_stream.ex +++ b/lib/boombox/elixir_stream.ex @@ -55,7 +55,7 @@ defmodule Boombox.ElixirStream do Enum.map(track_builders, fn {:audio, builder} -> builder - |> child(:mp4_audio_transcoder, %Boombox.Transcoder{ + |> child(:mp4_audio_transcoder, %Membrane.Transcoder{ output_stream_format: Membrane.RawAudio }) |> maybe_plug_resampler(options) @@ -64,7 +64,7 @@ defmodule Boombox.ElixirStream do {:video, builder} -> builder - |> child(:elixir_stream_video_transcoder, %Boombox.Transcoder{ + |> child(:elixir_stream_video_transcoder, %Membrane.Transcoder{ output_stream_format: Membrane.RawVideo }) |> child(:elixir_stream_rgb_converter, %Membrane.FFmpeg.SWScale.Converter{ diff --git a/lib/boombox/hls.ex b/lib/boombox/hls.ex index e2fb8fb..7a84ba5 100644 --- a/lib/boombox/hls.ex +++ b/lib/boombox/hls.ex @@ -43,7 +43,7 @@ defmodule Boombox.HLS do Enum.map(track_builders, fn {:audio, builder} -> builder - |> child(:hls_audio_transcoder, %Boombox.Transcoder{ + |> child(:hls_audio_transcoder, %Membrane.Transcoder{ output_stream_format: Membrane.AAC }) |> via_in(Pad.ref(:input, :audio), @@ -53,7 +53,7 @@ defmodule Boombox.HLS do {:video, builder} -> builder - |> child(:hls_video_transcoder, %Boombox.Transcoder{ + |> child(:hls_video_transcoder, %Membrane.Transcoder{ output_stream_format: %H264{alignment: :au, stream_structure: :avc3} }) |> via_in(Pad.ref(:input, :video), diff --git a/lib/boombox/mp4.ex b/lib/boombox/mp4.ex index 9348c8a..aa4c1c2 100644 --- a/lib/boombox/mp4.ex +++ b/lib/boombox/mp4.ex @@ -65,7 +65,7 @@ defmodule Boombox.MP4 do Enum.map(track_builders, fn {:audio, builder} -> builder - |> child(:mp4_audio_transcoder, %Boombox.Transcoder{ + |> child(:mp4_audio_transcoder, %Membrane.Transcoder{ output_stream_format: Membrane.AAC }) |> child(:mp4_out_aac_parser, %Membrane.AAC.Parser{ @@ -77,7 +77,7 @@ defmodule Boombox.MP4 do {:video, builder} -> builder - |> child(:mp4_video_transcoder, %Boombox.Transcoder{ + |> child(:mp4_video_transcoder, %Membrane.Transcoder{ output_stream_format: fn %H264{stream_structure: :annexb} = h264 -> %H264{h264 | stream_structure: :avc3, alignment: :au} diff --git a/lib/boombox/rtp.ex b/lib/boombox/rtp.ex index ab1c94b..2827e19 100644 --- a/lib/boombox/rtp.ex +++ b/lib/boombox/rtp.ex @@ -163,7 +163,7 @@ defmodule Boombox.RTP do end builder - |> child({:rtp_transcoder, media_type}, %Boombox.Transcoder{ + |> child({:rtp_transcoder, media_type}, %Membrane.Transcoder{ output_stream_format: output_stream_format }) |> child({:rtp_out_parser, media_type}, parser) diff --git a/lib/boombox/transcoder.ex b/lib/boombox/transcoder.ex deleted file mode 100644 index f739011..0000000 --- a/lib/boombox/transcoder.ex +++ /dev/null @@ -1,94 +0,0 @@ -defmodule Boombox.Transcoder do - @moduledoc false - use Membrane.Bin - - require __MODULE__.Audio - require __MODULE__.Video - - alias __MODULE__.{Audio, ForwardingFilter, Video} - alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8} - - @type stream_format :: - H264.t() - | H265.t() - | VP8.t() - | RawVideo.t() - | AAC.t() - | Opus.t() - | RemoteStream.t() - | RawAudio.t() - - @type stream_format_module :: H264 | H265 | VP8 | RawVideo | AAC | Opus | RawAudio - - @type stream_format_resolver :: (stream_format() -> stream_format() | stream_format_module()) - - def_input_pad :input, - accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format) - - def_output_pad :output, - accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format) - - def_options output_stream_format: [ - spec: stream_format() | stream_format_module() | stream_format_resolver() - ] - - @impl true - def handle_init(_ctx, opts) do - spec = [ - bin_input() - |> child(:forwarding_filter, ForwardingFilter), - child(:output_funnel, Funnel) - |> bin_output() - ] - - state = - opts - |> Map.from_struct() - |> Map.put(:input_stream_format, nil) - - {[spec: spec], state} - end - - @impl true - def handle_child_notification({:stream_format, format}, :forwarding_filter, _ctx, state) do - state = - %{state | input_stream_format: format} - |> resolve_output_stream_format() - - spec = - get_child(:forwarding_filter) - |> plug_transcoding(format, state.output_stream_format) - |> get_child(:output_funnel) - - {[spec: spec], state} - end - - @impl true - def handle_child_notification(_notification, _element, _ctx, state) do - {[], state} - end - - defp resolve_output_stream_format(state) do - case state.output_stream_format do - format when is_struct(format) -> - state - - module when is_atom(module) -> - %{state | output_stream_format: struct(module)} - - resolver when is_function(resolver) -> - %{state | output_stream_format: resolver.(state.input_stream_format)} - |> resolve_output_stream_format() - end - end - - defp plug_transcoding(builder, input_format, output_format) - when Audio.is_audio_format(input_format) do - builder |> Audio.plug_audio_transcoding(input_format, output_format) - end - - defp plug_transcoding(builder, input_format, output_format) - when Video.is_video_format(input_format) do - builder |> Video.plug_video_transcoding(input_format, output_format) - end -end diff --git a/lib/boombox/transcoder/audio.ex b/lib/boombox/transcoder/audio.ex deleted file mode 100644 index 9d1e21c..0000000 --- a/lib/boombox/transcoder/audio.ex +++ /dev/null @@ -1,90 +0,0 @@ -defmodule Boombox.Transcoder.Audio do - @moduledoc false - - import Membrane.ChildrenSpec - require Membrane.Logger - alias Membrane.{AAC, ChildrenSpec, Opus, RawAudio, RemoteStream} - - @opus_sample_rate 48_000 - - @type audio_stream_format :: AAC.t() | Opus.t() | RawAudio.t() - - defguard is_audio_format(format) - when is_struct(format) and - (format.__struct__ in [AAC, Opus, RawAudio] or - (format.__struct__ == RemoteStream and format.content_format == Opus and - format.type == :packetized)) - - @spec plug_audio_transcoding( - ChildrenSpec.builder(), - audio_stream_format() | RemoteStream.t(), - audio_stream_format() - ) :: ChildrenSpec.builder() - def plug_audio_transcoding(builder, input_format, output_format) - when is_audio_format(input_format) and is_audio_format(output_format) do - do_plug_audio_transcoding(builder, input_format, output_format) - end - - defp do_plug_audio_transcoding(builder, %format_module{}, %format_module{}) do - Membrane.Logger.debug(""" - This bin will only forward buffers, as the input stream format is the same as the output stream format. - """) - - builder - end - - defp do_plug_audio_transcoding(builder, %RemoteStream{content_format: Opus}, %Opus{}) do - builder |> child(:opus_parser, Opus.Parser) - end - - defp do_plug_audio_transcoding(builder, input_format, output_format) do - builder - |> maybe_plug_decoder(input_format) - |> maybe_plug_resampler(input_format, output_format) - |> maybe_plug_encoder(output_format) - end - - defp maybe_plug_decoder(builder, %Opus{}) do - builder |> child(:opus_decoder, Opus.Decoder) - end - - defp maybe_plug_decoder(builder, %RemoteStream{content_format: Opus, type: :packetized}) do - builder |> child(:opus_decoder, Opus.Decoder) - end - - defp maybe_plug_decoder(builder, %AAC{}) do - builder |> child(:aac_decoder, AAC.FDK.Decoder) - end - - defp maybe_plug_decoder(builder, %RawAudio{}) do - builder - end - - defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %Opus{}) - when sample_rate != @opus_sample_rate do - builder - |> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{ - output_stream_format: %RawAudio{ - sample_format: :s16le, - sample_rate: @opus_sample_rate, - channels: input_format.channels - } - }) - end - - defp maybe_plug_resampler(builder, _input_format, _output_format) do - builder - end - - defp maybe_plug_encoder(builder, %Opus{}) do - builder |> child(:opus_encoder, Opus.Encoder) - end - - defp maybe_plug_encoder(builder, %AAC{}) do - builder |> child(:aac_encoder, AAC.FDK.Encoder) - end - - defp maybe_plug_encoder(builder, %RawAudio{}) do - builder - end -end diff --git a/lib/boombox/transcoder/forwarding_filter.ex b/lib/boombox/transcoder/forwarding_filter.ex deleted file mode 100644 index a4449c5..0000000 --- a/lib/boombox/transcoder/forwarding_filter.ex +++ /dev/null @@ -1,117 +0,0 @@ -defmodule Boombox.Transcoder.ForwardingFilter do - @moduledoc false - use Membrane.Filter - - alias Membrane.TimestampQueue - - def_input_pad :input, - accepted_format: _any, - availability: :on_request - - def_output_pad :output, - accepted_format: _any, - availability: :on_request - - defguardp is_input_linked(state) when state.input_pad_ref != nil - defguardp is_output_linked(state) when state.output_pad_ref != nil - - @impl true - def handle_init(_ctx, _opts) do - state = %{queue: TimestampQueue.new(), output_pad_ref: nil, input_pad_ref: nil} - {[], state} - end - - @impl true - def handle_playing(ctx, state), do: maybe_flush_queue(ctx, state) - - @impl true - def handle_pad_added(Pad.ref(direction, _id) = pad_ref, ctx, state) do - same_direction_pads_number = - ctx.pads - |> Enum.count(fn {_pad_ref, pad_data} -> pad_data.direction == direction end) - - if same_direction_pads_number > 1 do - raise """ - #{inspect(__MODULE__)} can have only one #{inspect(direction)} pad, but it has \ - #{same_direction_pads_number} - """ - end - - state = - case direction do - :input -> %{state | input_pad_ref: pad_ref} - :output -> %{state | output_pad_ref: pad_ref} - end - - maybe_flush_queue(ctx, state) - end - - @impl true - def handle_stream_format(_input_pad_ref, stream_format, _ctx, state) - when is_output_linked(state) do - {[ - stream_format: {state.output_pad_ref, stream_format}, - notify_parent: {:stream_format, stream_format} - ], state} - end - - @impl true - def handle_stream_format(input_pad_ref, stream_format, _ctx, state) do - queue = TimestampQueue.push_stream_format(state.queue, input_pad_ref, stream_format) - {[notify_parent: {:stream_format, stream_format}], %{state | queue: queue}} - end - - @impl true - def handle_buffer(_input_pad_ref, buffer, _ctx, state) when is_output_linked(state) do - {[buffer: {state.output_pad_ref, buffer}], state} - end - - @impl true - def handle_buffer(input_pad_ref, buffer, _ctx, state) do - {_suggested_actions, queue} = TimestampQueue.push_buffer(state.queue, input_pad_ref, buffer) - {[], %{state | queue: queue}} - end - - @impl true - def handle_event(Pad.ref(:input, _id), event, _ctx, state) when is_output_linked(state) do - {[forward: event], state} - end - - @impl true - def handle_event(Pad.ref(:output, _id), event, _ctx, state) when is_input_linked(state) do - {[forward: event], state} - end - - @impl true - def handle_event(pad_ref, event, _ctx, state) do - queue = TimestampQueue.push_event(state.queue, pad_ref, event) - {[], %{state | queue: queue}} - end - - @impl true - def handle_end_of_stream(_input_pad_ref, _ctx, state) when is_output_linked(state) do - {[end_of_stream: state.output_pad_ref], state} - end - - @impl true - def handle_end_of_stream(input_pad_ref, _ctx, state) do - queue = TimestampQueue.push_end_of_stream(state.queue, input_pad_ref) - {[], %{state | queue: queue}} - end - - defp maybe_flush_queue(ctx, state) - when ctx.playback == :playing and is_input_linked(state) and is_output_linked(state) do - {_suggested_actions, items, queue} = TimestampQueue.flush_and_close(state.queue) - - actions = - Enum.map(items, fn - {Pad.ref(:input, _id), {item_type, item}} -> {item_type, {state.output_pad_ref, item}} - {Pad.ref(:input, _id), :end_of_stream} -> {:end_of_stream, state.output_pad_ref} - {Pad.ref(:output, _id), {:event, item}} -> {:event, {state.input_pad_ref, item}} - end) - - {actions, %{state | queue: queue}} - end - - defp maybe_flush_queue(_ctx, state), do: {[], state} -end diff --git a/lib/boombox/transcoder/video.ex b/lib/boombox/transcoder/video.ex deleted file mode 100644 index cd90e18..0000000 --- a/lib/boombox/transcoder/video.ex +++ /dev/null @@ -1,124 +0,0 @@ -defmodule Boombox.Transcoder.Video do - @moduledoc false - - import Membrane.ChildrenSpec - require Membrane.Logger - alias Membrane.RemoteStream - alias Membrane.{ChildrenSpec, H264, H265, RawVideo, VP8} - - @type video_stream_format :: VP8.t() | H264.t() | H265.t() | RawVideo.t() - - defguard is_video_format(format) - when is_struct(format) and - (format.__struct__ in [VP8, H264, H265, RawVideo] or - (format.__struct__ == RemoteStream and format.content_format == VP8 and - format.type == :packetized)) - - @spec plug_video_transcoding( - ChildrenSpec.builder(), - video_stream_format(), - video_stream_format() - ) :: ChildrenSpec.builder() - def plug_video_transcoding(builder, input_format, output_format) - when is_video_format(input_format) and is_video_format(output_format) do - do_plug_video_transcoding(builder, input_format, output_format) - end - - defp do_plug_video_transcoding(builder, %h26x{}, %h26x{} = output_format) - when h26x in [H264, H265] do - parser = - h26x - |> Module.concat(Parser) - |> struct!( - output_stream_structure: stream_structure_type(output_format), - output_alignment: output_format.alignment - ) - - builder |> child(:h264_parser, parser) - end - - defp do_plug_video_transcoding(builder, %format_module{}, %format_module{}) do - Membrane.Logger.debug(""" - This bin will only forward buffers, as the input stream format is the same type as the output stream format. - """) - - builder - end - - defp do_plug_video_transcoding(builder, input_format, output_format) do - builder - |> maybe_plug_parser_and_decoder(input_format) - |> maybe_plug_encoder_and_parser(output_format) - end - - defp maybe_plug_parser_and_decoder(builder, %H264{}) do - builder - |> child(:h264_input_parser, %H264.Parser{ - output_stream_structure: :annexb, - output_alignment: :au - }) - |> child(:h264_decoder, %H264.FFmpeg.Decoder{}) - end - - defp maybe_plug_parser_and_decoder(builder, %H265{}) do - builder - |> child(:h265_input_parser, %H265.Parser{ - output_stream_structure: :annexb, - output_alignment: :au - }) - |> child(:h265_decoder, %H265.FFmpeg.Decoder{}) - end - - defp maybe_plug_parser_and_decoder(builder, %VP8{}) do - # todo: maybe specify framerate in decoder options - builder |> child(:vp8_decoder, %VP8.Decoder{}) - end - - defp maybe_plug_parser_and_decoder(builder, %RemoteStream{ - content_format: VP8, - type: :packetized - }) do - builder |> child(:vp8_decoder, %VP8.Decoder{}) - end - - defp maybe_plug_parser_and_decoder(builder, %RawVideo{}) do - builder - end - - defp maybe_plug_encoder_and_parser(builder, %H264{} = h264) do - # todo: specify different preset in eg. mp4 - builder - |> child(:h264_encoder, %H264.FFmpeg.Encoder{preset: :ultrafast}) - |> child(:h264_output_parser, %H264.Parser{ - output_stream_structure: stream_structure_type(h264), - output_alignment: h264.alignment - }) - end - - defp maybe_plug_encoder_and_parser(builder, %H265{} = h265) do - # todo: specify different preset in eg. mp4 - builder - |> child(:h265_encoder, %H265.FFmpeg.Encoder{preset: :ultrafast}) - |> child(:h265_output_parser, %H265.Parser{ - output_stream_structure: stream_structure_type(h265), - output_alignment: h265.alignment - }) - end - - defp maybe_plug_encoder_and_parser(builder, %VP8{}) do - # todo: check if no option is required - builder |> child(:vp8_encoder, %VP8.Encoder{}) - end - - defp maybe_plug_encoder_and_parser(builder, %RawVideo{}) do - builder - end - - defp stream_structure_type(%h26x{stream_structure: stream_structure}) - when h26x in [H264, H265] do - case stream_structure do - type when type in [:annexb, :avc1, :avc3, :hvc1, :hev1] -> type - {type, _dcr} when type in [:avc1, :avc3, :hvc1, :hev1] -> type - end - end -end diff --git a/lib/boombox/webrtc.ex b/lib/boombox/webrtc.ex index 88d1596..bc7fb5c 100644 --- a/lib/boombox/webrtc.ex +++ b/lib/boombox/webrtc.ex @@ -149,7 +149,7 @@ defmodule Boombox.WebRTC do Enum.map(track_builders, fn {:audio, builder} -> builder - |> child(:mp4_audio_transcoder, %Boombox.Transcoder{ + |> child(:mp4_audio_transcoder, %Membrane.Transcoder{ output_stream_format: Membrane.Opus }) |> child(:webrtc_out_audio_realtimer, Membrane.Realtimer) @@ -163,7 +163,7 @@ defmodule Boombox.WebRTC do builder |> child(:webrtc_out_video_realtimer, Membrane.Realtimer) - |> child(:webrtc_video_transcoder, %Boombox.Transcoder{ + |> child(:webrtc_video_transcoder, %Membrane.Transcoder{ output_stream_format: fn %H264{} = h264 when h264_negotiated? -> %H264{h264 | alignment: :nalu, stream_structure: :annexb} diff --git a/mix.exs b/mix.exs index b222d4c..de8e133 100644 --- a/mix.exs +++ b/mix.exs @@ -47,13 +47,7 @@ defmodule Boombox.Mixfile do [ {:membrane_core, "~> 1.1"}, {:membrane_webrtc_plugin, "~> 0.23.2"}, - {:membrane_opus_plugin, "~> 0.20.3"}, - {:membrane_aac_plugin, "~> 0.19.0"}, - {:membrane_aac_fdk_plugin, "~> 0.18.0"}, - {:membrane_vpx_plugin, "~> 0.3.0"}, - {:membrane_h26x_plugin, "~> 0.10.0"}, - {:membrane_h264_ffmpeg_plugin, "~> 0.32.5"}, - {:membrane_h265_ffmpeg_plugin, "~> 0.4.2"}, + {:membrane_transcoder_plugin, "~> 0.1.2"}, {:membrane_mp4_plugin, "~> 0.35.2"}, {:membrane_realtimer_plugin, "~> 0.9.0"}, {:membrane_http_adaptive_stream_plugin, "~> 0.18.5"}, diff --git a/mix.lock b/mix.lock index abc02ec..2373c9f 100644 --- a/mix.lock +++ b/mix.lock @@ -84,6 +84,7 @@ "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, "membrane_telemetry_metrics": {:hex, :membrane_telemetry_metrics, "0.1.1", "57917e72012f9ebe124eab54f29ca74c9d9eb3ae2207f55c95618ee51280eb4f", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "69392966e6bd51937244758c2b3d835c5ff47d8953d25431a9d37059737afc11"}, "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, + "membrane_transcoder_plugin": {:hex, :membrane_transcoder_plugin, "0.1.2", "5d5546971289109423c1d02c4a45bfce7133293d76f5850824663a4613e5a728", [:mix], [{:membrane_aac_fdk_plugin, "~> 0.18.0", [hex: :membrane_aac_fdk_plugin, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_aac_plugin, "~> 0.19.0", [hex: :membrane_aac_plugin, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_ffmpeg_swresample_plugin, "~> 0.20.0", [hex: :membrane_ffmpeg_swresample_plugin, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.1", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_ffmpeg_plugin, "~> 0.32.0", [hex: :membrane_h264_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_ffmpeg_plugin, "~> 0.4.2", [hex: :membrane_h265_ffmpeg_plugin, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_h26x_plugin, "~> 0.10.0", [hex: :membrane_h26x_plugin, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_opus_plugin, "~> 0.20.3", [hex: :membrane_opus_plugin, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.2", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}, {:membrane_vpx_plugin, "~> 0.3.0", [hex: :membrane_vpx_plugin, repo: "hexpm", optional: false]}], "hexpm", "50297b922fdd39520125120782bf679f26be8965b31565b470fb51b6ef908873"}, "membrane_udp_plugin": {:hex, :membrane_udp_plugin, "0.14.0", "d533ee5f6fcdd0551ad690045cdb6c1a76307a155d9255cc4a4606f85774bc37", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3.0", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "902d1a7aa228ec377482d53a605b100e20e0b6e59196f94f94147bb62b23c47e"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"},