|
1 | | -As explained in the [Architecture chapter](08_RTSP_Architecture.md), the pipeline will consist of a couple of elements, that will be processing the RTP stream. |
| 1 | +As explained in the [Architecture chapter](08_RTSP_Architecture.md), the pipeline will consist of a RTSP Source and a HLS Sink. |
2 | 2 |
|
3 | | -The flow of the pipeline will consist of three steps. First, when the pipeline is initialized we will start the Connection Manager, which will set up the RTP stream via the RTSP. |
4 | | -Once that is finished, we will set up two initial elements in the pipeline - the `UDP Source` and `RTP SessionBin`, which will allow us to receive RTP packets and process them. |
5 | | -When the SessionBin detects that the RTP stream has been started, it will notify the pipeline with the `:new_rtp_stream` notification. Later on, we will add the remaining elements to the pipeline, allowing for the whole conversion process to take place. |
| 3 | +The initial pipeline will consist only of the `RTSP Source` and it'll start establishing the connection with the RTSP server. |
6 | 4 |
|
7 | | -Those steps take place, respectively, in the: `handle_init/1`, `handle_other/3` and `handle_notification/4` callbacks. While the `handle_init/1` is rather intuitive, we will describe in detail what's happening in the other callbacks. |
| 5 | +<!--The flow of the pipeline will consist of three steps. First, when the pipeline is initialized we will start the Connection Manager, which will set up the RTP stream via the RTSP.--> |
| 6 | +<!--Once that is finished, we will set up two initial elements in the pipeline - the `UDP Source` and `RTP SessionBin`, which will allow us to receive RTP packets and process them.--> |
| 7 | +<!--When the SessionBin detects that the RTP stream has been started, it will notify the pipeline with the `:new_rtp_stream` notification. Later on, we will add the remaining elements to the pipeline, allowing for the whole conversion process to take place.--> |
8 | 8 |
|
9 | | -Let us explain what's going on in the `handle_other` callback: |
| 9 | +<!--Those steps take place, respectively, in the: `handle_init/1`, `handle_other/3` and `handle_notification/4` callbacks. While the `handle_init/1` is rather intuitive, we will describe in detail what's happening in the other callbacks.--> |
| 10 | + |
| 11 | +<!--Let us explain what's going on in the `handle_other` callback:--> |
10 | 12 |
|
11 | 13 | ##### lib/pipeline.ex |
12 | 14 | ```elixir |
13 | 15 | @impl true |
14 | | -def handle_other({:rtsp_setup_complete, options}, _ctx, state) do |
15 | | - children = %{ |
16 | | - app_source: %Membrane.UDP.Source{ |
17 | | - local_port_no: state[:port], |
18 | | - recv_buffer_size: 500_000 |
19 | | - }, |
20 | | - rtp: %Membrane.RTP.SessionBin{ |
21 | | - fmt_mapping: %{96 => {:H264, 90_000}} |
22 | | - }, |
23 | | - hls: %Membrane.HTTPAdaptiveStream.Sink{ |
24 | | - manifest_module: Membrane.HTTPAdaptiveStream.HLS, |
25 | | - target_window_duration: 120 |> Membrane.Time.seconds(), |
26 | | - target_segment_duration: 4 |> Membrane.Time.seconds(), |
27 | | - storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{ |
28 | | - directory: state[:output_path] |
29 | | - } |
30 | | - } |
31 | | - } |
| 16 | +def handle_init(_context, options) do |
| 17 | +Logger.debug("Source handle_init options: #{inspect(options)}") |
| 18 | + |
| 19 | +spec = [ |
| 20 | + child(:source, %Membrane.RTSP.Source{ |
| 21 | + transport: {:udp, options.port, options.port + 5}, |
| 22 | + allowed_media_types: [:video, :audio], |
| 23 | + stream_uri: options.stream_url, |
| 24 | + on_connection_closed: :send_eos |
| 25 | + }) |
| 26 | +] |
32 | 27 |
|
33 | | - links = [ |
34 | | - link(:app_source) |
35 | | - |> via_in(Pad.ref(:rtp_input, make_ref())) |
36 | | - |> to(:rtp) |
37 | | - ] |
| 28 | +{[spec: spec], |
| 29 | + %{ |
| 30 | + output_path: options.output_path, |
| 31 | + parent_pid: options.parent_pid, |
| 32 | + tracks_left_to_link: nil, |
| 33 | + track_specs: [] |
| 34 | + }} |
| 35 | +end |
| 36 | +``` |
| 37 | + |
| 38 | +Once we receive the `{:set_up_tracks, tracks}` notification from the source we have the information what tracks have been set up during connection establishment and what we should expect. We take this information and store it, so that we link the source to the `HLS Sink Bin` correctly. |
| 39 | + |
| 40 | +```elixir |
| 41 | +@impl true |
| 42 | +def handle_child_notification({:set_up_tracks, tracks}, :source, _ctx, state) do |
| 43 | + tracks_left_to_link = |
| 44 | + [:audio, :video] |
| 45 | + |> Enum.filter(fn media_type -> Enum.any?(tracks, &(&1.type == media_type)) end) |
38 | 46 |
|
39 | | - spec = %ParentSpec{children: children, links: links} |
40 | | - { {:ok, spec: spec}, %{state | video: %{sps: options[:sps], pps: options[:pps]}} } |
| 47 | + {[], %{state | tracks_left_to_link: tracks_left_to_link}} |
41 | 48 | end |
42 | 49 | ``` |
43 | 50 |
|
| 51 | +When a `PLAY` request is eventually sent by the source, we should be prepared to receive streams that have been set up. When a new RTP stream is received and identified by the source, a message is set to the parent - the pipeline in our case - containing information necessary to handle the stream. |
44 | 52 | When we receive the `rtsp_setup_complete` message, we first define the new children for the pipeline, and links between them - the UDP Source and the RTP SessionBin. We also create the HLS Sink, however we won't be linking it just yet. With the message we receive the sps and pps inside the options, and we add them to the pipeline's state. |
45 | 53 |
|
46 | 54 | Only after we receive the `:new_rtp_stream` notification we add the rest of the elements and link them with each other: |
|
0 commit comments