Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create server #1

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
# Membrane Template Plugin

[![Hex.pm](https://img.shields.io/hexpm/v/membrane_template_plugin.svg)](https://hex.pm/packages/membrane_template_plugin)
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_template_plugin)
[![CircleCI](https://circleci.com/gh/membraneframework/membrane_template_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_template_plugin)
[![Hex.pm](https://img.shields.io/hexpm/v/membrane_simple_rtsp_server.svg)](https://hex.pm/packages/membrane_simple_rtsp_server)
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_simple_rtsp_server)
[![CircleCI](https://circleci.com/gh/membraneframework/membrane_simple_rtsp_server.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_simple_rtsp_server)

This repository contains a template for new plugins.

Check out different branches for other flavors of this template.

It's a part of the [Membrane Framework](https://membrane.stream).
A Simple RTSP server that serves a MP4 file

## Installation

The package can be installed by adding `membrane_template_plugin` to your list of dependencies in `mix.exs`:
The package can be installed by adding `membrane_simple_rtsp_server` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:membrane_template_plugin, "~> 0.1.0"}
{:membrane_simple_rtsp_server, "~> 0.1.0"}
]
end
```

## Usage

TODO
To serve a MP4 file run the following:
```elixir
Membrane.SimpleRTSPServer.start_link("path/to/file.mp4", port: 30001)
```

To receive and immediately play the stream you can use a tool like `ffplay`:
```sh
ffplay rtsp://localhost:30001
```

To receive the mp4 and store it you can use a tool like [Boombox](https://github.com/membraneframework/boombox):
```elixir
Boombox.run(input: "rtsp://localhost:30001", output: "output.mp4")
```

## Copyright and License

Expand Down
6 changes: 6 additions & 0 deletions dupa.exs
Copy link

Choose a reason for hiding this comment

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

perform deduping

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

whoops, a careless git add :P

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Membrane.SimpleRTSPServer.start_link(
"/Users/noarkhh/Membrane/membrane_mp4_plugin/test/fixtures/isom/ref_aac.mp4",
port: 30001
)

Process.sleep(:infinity)
15 changes: 10 additions & 5 deletions lib/membrane/simple_rtsp_server.ex
Copy link

Choose a reason for hiding this comment

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

cannot comment on line 7, but I'd use a keyword list instead of optional arguments

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ defmodule Membrane.SimpleRTSPServer do
Module for starting a simple RTSP Server that streams a MP4 file.
"""

@spec start_link(String.t(), :inet.port_number(), :inet.ip4_address()) :: GenServer.on_start()
def start_link(mp4_path, rtsp_port \\ 554, address \\ {127, 0, 0, 1}) do
@spec start_link(String.t(),
port: :inet.port_number(),
address: :inet.ip4_address(),
realtime: boolean()
) ::
GenServer.on_start()
def start_link(mp4_path, opts) do
Membrane.RTSP.Server.start_link(
handler: Membrane.SimpleRTSPServer.Handler,
handler_config: %{mp4_path: mp4_path},
port: rtsp_port,
address: address,
handler_config: %{mp4_path: mp4_path, realtime: Keyword.get(opts, :realtime, true)},
port: Keyword.get(opts, :port, 554),
address: Keyword.get(opts, :address, {127, 0, 0, 1}),
udp_rtp_port: 0,
udp_rtcp_port: 0
)
Expand Down
35 changes: 28 additions & 7 deletions lib/membrane/simple_rtsp_server/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ defmodule Membrane.SimpleRTSPServer.Handler do
@video_clock_rate 90_000
@audio_pt 97
@audio_clock_rate 44_100
@audio_specific_config "1210"

@impl true
def init(config) do
Expand All @@ -26,6 +25,8 @@ defmodule Membrane.SimpleRTSPServer.Handler do

@impl true
def handle_describe(_req, state) do
asc = get_audio_specific_config(state.mp4_path)

sdp = """
v=0
m=video 0 RTP/AVP #{@video_pt}
Expand All @@ -35,7 +36,7 @@ defmodule Membrane.SimpleRTSPServer.Handler do
m=audio 0 RTP/AVP #{@audio_pt}
a=control:audio
a=rtpmap:#{@audio_pt} mpeg4-generic/#{@audio_clock_rate}/2
a=fmtp:#{@audio_pt} streamtype=5; profile-level-id=5; mode=AAC-hbr; config=#{@audio_specific_config}; sizeLength=13; indexLength=3
a=fmtp:#{@audio_pt} streamtype=5; profile-level-id=5; mode=AAC-hbr; config=#{Base.encode16(asc)}; sizeLength=13; indexLength=3
"""

response =
Expand Down Expand Up @@ -75,11 +76,13 @@ defmodule Membrane.SimpleRTSPServer.Handler do
{key, config}
end)

arg = %{
socket: state.socket,
mp4_path: state.mp4_path,
media_config: media_config
}
arg =
%{
socket: state.socket,
mp4_path: state.mp4_path,
realtime: state.realtime,
media_config: media_config
}

{:ok, _sup_pid, pipeline_pid} =
Membrane.SimpleRTSPServer.Pipeline.start_link(arg)
Expand All @@ -99,4 +102,22 @@ defmodule Membrane.SimpleRTSPServer.Handler do

@impl true
def handle_closed_connection(_state), do: :ok

@spec get_audio_specific_config(String.t()) :: binary()
def get_audio_specific_config(mp4_path) do
{container, ""} = File.read!(mp4_path) |> Membrane.MP4.Container.parse!()

container[:moov].children
|> Keyword.get_values(:trak)
|> Enum.map(& &1.children[:mdia].children[:minf].children[:stbl].children[:stsd])
|> Enum.find_value(fn
%{children: [{:mp4a, mp4a_box}]} ->
mp4a_box.children[:esds].fields.elementary_stream_descriptor

_other ->
false
end)
|> Membrane.AAC.Parser.Esds.parse_esds()
|> Membrane.AAC.Parser.AudioSpecificConfig.generate_audio_specific_config()
end
end
26 changes: 13 additions & 13 deletions lib/membrane/simple_rtsp_server/pipeline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ defmodule Membrane.SimpleRTSPServer.Pipeline do
{id, %Membrane.AAC{}} ->
get_child(:mp4_demuxer)
|> via_out(Pad.ref(:output, id))
|> build_track(:audio, state.media_config)
|> build_track(:audio, state.media_config, state.realtime)

{id, %Membrane.H264{}} ->
get_child(:mp4_demuxer)
|> via_out(Pad.ref(:output, id))
|> build_track(:video, state.media_config)
|> build_track(:video, state.media_config, state.realtime)
end)

{[spec: spec], state}
Expand All @@ -46,7 +46,6 @@ defmodule Membrane.SimpleRTSPServer.Pipeline do

@impl true
def handle_element_end_of_stream({:udp_sink, :video}, :input, _ctx, state) do
Process.sleep(50)
:gen_tcp.close(state.socket)
{[terminate: :normal], state}
end
Expand All @@ -56,20 +55,19 @@ defmodule Membrane.SimpleRTSPServer.Pipeline do
{[], state}
end

defp build_track(builder, :audio, %{audio: config}) do
defp build_track(builder, :audio, %{audio: config}, realtime) do
builder
|> child(:aac_parser, %Membrane.AAC.Parser{
out_encapsulation: :none,
output_config: :audio_specific_config
})
|> child(%Membrane.Debug.Filter{handle_stream_format: &IO.inspect(&1, label: "strfmt")})
|> via_in(Pad.ref(:input, config.ssrc),
options: [payloader: %Membrane.RTP.AAC.Payloader{frames_per_packet: 1, mode: :hbr}]
)
|> build_tail(:audio, config)
|> build_tail(:audio, config, realtime)
end

defp build_track(builder, :video, %{video: config}) do
defp build_track(builder, :video, %{video: config}, realtime) do
builder
|> child(:h264_parser, %Membrane.H264.Parser{
output_alignment: :nalu,
Expand All @@ -80,17 +78,15 @@ defmodule Membrane.SimpleRTSPServer.Pipeline do
|> via_in(Pad.ref(:input, config.ssrc),
options: [payloader: Membrane.RTP.H264.Payloader]
)
|> build_tail(:video, config)
|> build_tail(:video, config, realtime)
end

defp build_track(builder, _type, _media_config) do
defp build_track(builder, _type, _media_config, _realtime) do
builder
|> child(Membrane.Debug.Sink)
end

defp build_tail(builder, type, config) do
config |> IO.inspect(label: type)

defp build_tail(builder, type, config, realtime) do
builder
|> get_child(:rtp_session_bin)
|> via_out(Pad.ref(:rtp_output, config.ssrc),
Expand All @@ -99,7 +95,11 @@ defmodule Membrane.SimpleRTSPServer.Pipeline do
clock_rate: config.clock_rate
]
)
|> child({:realtimer, type}, Membrane.Realtimer)
|> then(
&if realtime,
Copy link

Choose a reason for hiding this comment

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

do you have a use case for realtime: false? I think it would overload the socket buffer and cause packet loss

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wanted to have the test not take so long, but came to the same conclusion

Copy link

Choose a reason for hiding this comment

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

An alternative would be an option in realtimer that would speed it up a few times. And a relatively short fixture - I'm using up to 10s for such cases

do: &1 |> child({:realtimer, type}, Membrane.Realtimer),
else: &1
)
|> child({:udp_sink, type}, %Membrane.UDP.Sink{
destination_address: config.client_address,
destination_port_no: config.client_port,
Expand Down
14 changes: 11 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@ defmodule Membrane.SimpleRTSPServer.Mixfile do
defp deps do
[
{:membrane_core, "~> 1.0"},
{:membrane_rtsp, "~> 0.10.0"},
{:membrane_rtsp_plugin,
github: "membraneframework-labs/membrane_rtsp_plugin",
branch: "audio-depayloading",
override: true},
{:membrane_rtp_plugin, "~> 0.29.0"},
{:membrane_rtp_h264_plugin, "~> 0.19.0"},
{:membrane_rtp_aac_plugin, "~> 0.9.0"},
# {:membrane_rtp_aac_plugin, "~> 0.9.0"},
{:membrane_file_plugin, "~> 0.17.0"},
{:membrane_mp4_plugin, "~> 0.35.0"},
{:membrane_h26x_plugin, "~> 0.10.0"},
{:membrane_aac_plugin, "~> 0.18.0"},
{:membrane_aac_plugin,
git: "https://github.com/membraneframework/membrane_aac_plugin.git",
branch: "config-option",
override: true},
{:ex_sdp, github: "membraneframework/ex_sdp", branch: "aac-fmtp", override: true},
{:membrane_udp_plugin, "~> 0.14.0"},
{:membrane_realtimer_plugin, "~> 0.9.0"},
{:boombox, "~> 0.1.0", only: :test},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:dialyxir, ">= 0.0.0", only: :dev, runtime: false},
{:credo, ">= 0.0.0", only: :dev, runtime: false}
Expand Down
Loading