diff --git a/rtp_to_hls/README.md b/rtp_to_hls/README.md index 2daf71d0..b6b543e1 100644 --- a/rtp_to_hls/README.md +++ b/rtp_to_hls/README.md @@ -12,13 +12,10 @@ To run the demo, you'll need to have [Elixir installed](https://elixir-lang.org/ elixir rtp_to_hls.exs ``` -After a while, the server will start listening for UDP connections on port 5000. - -After that, you can start sending any H264 video and AAC audio stream via RTP. Below you can see an example of how to generate sample streams with [GStreamer](https://gstreamer.freedesktop.org/). +After a while, the server will start listening for UDP connections on port 5000. Then, start the RTP stream with ```shell -gst-launch-1.0 -v audiotestsrc ! audio/x-raw,rate=44100 ! faac ! rtpmp4gpay pt=127 ! udpsink host=127.0.0.1 port=5000 \ - videotestsrc ! video/x-raw,format=I420 ! x264enc key-int-max=10 tune=zerolatency ! rtph264pay pt=96 ! udpsink host=127.0.0.1 port=5000 +elixir send.exs ``` When the server prints that playback is available, visit `http://localhost:8000/stream.html` and you should see the stream there. The stream can be also played with players other than the browser, like `vlc` or `ffplay`, for example @@ -27,6 +24,14 @@ When the server prints that playback is available, visit `http://localhost:8000/ ffplay http://localhost:8000/output/index.m3u8 ``` +The RTP stream can be sent with other tools as well, for example with [GStreamer](https://gstreamer.freedesktop.org/): + +```shell +gst-launch-1.0 -v audiotestsrc ! audio/x-raw,rate=44100 ! faac ! rtpmp4gpay pt=127 ! udpsink host=127.0.0.1 port=5000 \ + videotestsrc ! video/x-raw,format=I420 ! x264enc key-int-max=10 tune=zerolatency ! rtph264pay pt=96 ! udpsink host=127.0.0.1 port=5000 +``` + + ## Copyright and License Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane) diff --git a/rtp_to_hls/rtp_to_hls.exs b/rtp_to_hls/rtp_to_hls.exs index 6830cc13..3bc82853 100644 --- a/rtp_to_hls/rtp_to_hls.exs +++ b/rtp_to_hls/rtp_to_hls.exs @@ -3,12 +3,13 @@ Logger.configure(level: :info) Mix.install([ {:membrane_core, "~> 1.0"}, - {:membrane_udp_plugin, "~> 0.12.0"}, - {:membrane_rtp_plugin, "~> 0.24.0"}, + {:membrane_udp_plugin, "~> 0.13.0"}, + {:membrane_rtp_plugin, "~> 0.27.1"}, {:membrane_rtp_aac_plugin, "~> 0.8.0"}, {:membrane_rtp_h264_plugin, "~> 0.19.0"}, - {:membrane_http_adaptive_stream_plugin, "~> 0.18.0"}, - {:membrane_fake_plugin, "~> 0.11.0"} + {:membrane_http_adaptive_stream_plugin, "~> 0.18.4"}, + {:membrane_fake_plugin, "~> 0.11.0"}, + {:membrane_h26x_plugin, "~> 0.10.0"} ]) defmodule RtpToHls do @@ -24,7 +25,7 @@ defmodule RtpToHls do |> child(:rtp, Membrane.RTP.SessionBin), child(:hls, %Membrane.HTTPAdaptiveStream.SinkBin{ manifest_module: Membrane.HTTPAdaptiveStream.HLS, - target_window_duration: Membrane.Time.seconds(10), + target_window_duration: Membrane.Time.seconds(15), storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"} }) ] @@ -36,7 +37,9 @@ defmodule RtpToHls do def handle_child_notification({:new_rtp_stream, ssrc, 96, _ext}, :rtp, _ctx, state) do spec = get_child(:rtp) - |> via_out(Pad.ref(:output, ssrc), options: [depayloader: Membrane.RTP.H264.Depayloader]) + |> via_out(Pad.ref(:output, ssrc), + options: [depayloader: Membrane.RTP.H264.Depayloader] + ) |> via_in(Pad.ref(:input, :video), options: [encoding: :H264, segment_duration: Membrane.Time.seconds(4)] ) @@ -49,7 +52,9 @@ defmodule RtpToHls do def handle_child_notification({:new_rtp_stream, ssrc, 127, _ext}, :rtp, _ctx, state) do spec = get_child(:rtp) - |> via_out(Pad.ref(:output, ssrc), options: [depayloader: Membrane.RTP.AAC.Depayloader]) + |> via_out(Pad.ref(:output, ssrc), + options: [depayloader: Membrane.RTP.AAC.Depayloader] + ) |> via_in(Pad.ref(:input, :audio), options: [encoding: :AAC, segment_duration: Membrane.Time.seconds(4)] ) diff --git a/rtp_to_hls/send.exs b/rtp_to_hls/send.exs new file mode 100644 index 00000000..a6408ec5 --- /dev/null +++ b/rtp_to_hls/send.exs @@ -0,0 +1,100 @@ +require Logger +Logger.configure(level: :info) + +Mix.install([ + {:membrane_core, "~> 1.0"}, + {:membrane_hackney_plugin, "~> 0.11.0"}, + {:membrane_realtimer_plugin, "~> 0.9.0"}, + {:membrane_h26x_plugin, "~> 0.10.0"}, + {:membrane_aac_plugin, "~> 0.18.1"}, + {:membrane_funnel_plugin, "~> 0.9.0"}, + {:membrane_mp4_plugin, "~> 0.34.1"}, + {:membrane_udp_plugin, "~> 0.13.0"}, + {:membrane_rtp_plugin, "~> 0.27.1"}, + {:membrane_rtp_aac_plugin, "~> 0.9.0"}, + {:membrane_rtp_h264_plugin, "~> 0.19.1"} +]) + +defmodule SendRTP do + use Membrane.Pipeline + + @samples_url "https://raw.githubusercontent.com/membraneframework/static/gh-pages/samples/big-buck-bunny/" + + @mp4_url @samples_url <> "bun33s.mp4" + + @impl true + def handle_init(_ctx, _opts) do + spec = + child(:video_src, %Membrane.Hackney.Source{ + location: @mp4_url, + hackney_opts: [follow_redirect: true] + }) + |> child(:mp4, Membrane.MP4.Demuxer.ISOM) + + {[spec: spec], %{}} + end + + @impl true + def handle_child_notification({:new_tracks, tracks}, :mp4, _ctx, state) do + audio_ssrc = 1234 + video_ssrc = 1235 + {audio_id, _format} = Enum.find(tracks, fn {_id, %format{}} -> format == Membrane.AAC end) + {video_id, _format} = Enum.find(tracks, fn {_id, %format{}} -> format == Membrane.H264 end) + + spec = [ + get_child(:mp4) + |> via_out(Pad.ref(:output, video_id)) + |> child(:video_parser, %Membrane.H264.Parser{ + output_stream_structure: :annexb, + output_alignment: :nalu + }) + |> child(:video_realtimer, Membrane.Realtimer) + |> via_in(Pad.ref(:input, video_ssrc), + options: [payloader: Membrane.RTP.H264.Payloader] + ) + |> child(:rtp, Membrane.RTP.SessionBin) + |> via_out(Pad.ref(:rtp_output, video_ssrc), options: [encoding: :H264]) + |> get_child(:funnel), + get_child(:mp4) + |> via_out(Pad.ref(:output, audio_id)) + |> child(:audio_realtimer, Membrane.Realtimer) + |> child(:audio_parser, %Membrane.AAC.Parser{out_encapsulation: :none}) + |> via_in(Pad.ref(:input, audio_ssrc), + options: [payloader: %Membrane.RTP.AAC.Payloader{frames_per_packet: 1, mode: :hbr}] + ) + |> get_child(:rtp) + |> via_out(Pad.ref(:rtp_output, audio_ssrc), options: [encoding: :AAC]) + |> get_child(:funnel), + child(:funnel, Membrane.Funnel) + |> child(:udp, %Membrane.UDP.Sink{ + destination_port_no: 5000, + destination_address: {127, 0, 0, 1} + }) + ] + + {[spec: spec], state} + end + + @impl true + def handle_child_notification(_notification, _child, _ctx, state) do + {[], state} + end + + @impl true + def handle_element_end_of_stream(:udp, :input, _ctx, state) do + {[terminate: :normal], state} + end + + @impl true + def handle_element_end_of_stream(_element, _pad, _ctx, state) do + {[], state} + end +end + +{:ok, supervisor, _pipeline} = Membrane.Pipeline.start_link(SendRTP) + +monitor = Process.monitor(supervisor) + +receive do + {:DOWN, ^monitor, _kind, _pid, :normal} -> :ok +end