Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Membrane Live dev #188

Merged
merged 64 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0f9c4cd
Add compositor and audio mixer to hls
Karolk99 Nov 4, 2022
a2772cb
Add hls stream without compositor
Karolk99 Nov 3, 2022
cb305dc
Update dependencies
Karolk99 Nov 4, 2022
d80f126
Fix hls_endpoint tests and add tests for audio and video mixer.
Karolk99 Nov 6, 2022
44ba2e8
Change requests
Karolk99 Nov 9, 2022
9a920af
fix segments length
Karolk99 Nov 10, 2022
9271d31
Fix hls endpoint test
Karolk99 Nov 10, 2022
b5630c5
Change requests
Karolk99 Nov 14, 2022
dbac997
Add more specific spec in compositor config
Karolk99 Nov 15, 2022
d632ed9
Change request
Karolk99 Nov 15, 2022
3c308bb
Migrate to new compositor version
Rados13 Nov 10, 2022
1048e96
Add membrane compositor (dev)
Karolk99 Nov 28, 2022
91835a3
hls endpoint refactor plus fixes
Karolk99 Dec 6, 2022
8ad2da1
Use new version of compositor and audio mixer
Karolk99 Dec 8, 2022
791b4f1
Deps update
Karolk99 Dec 9, 2022
e7d2f25
Restart offsets on mixers removal
Karolk99 Dec 13, 2022
b4ccb6f
Add dynamic creation of hls output stream grid
Karolk99 Dec 15, 2022
c39c737
Refactor, Add frames to thumbnails in hls stream
Karolk99 Dec 16, 2022
b5a008f
Fix credo errors
Karolk99 Dec 19, 2022
58a9269
Fix tests and credo warns
Karolk99 Dec 19, 2022
a4fc493
Change requests
Karolk99 Dec 21, 2022
549d9c7
Code refactor
Karolk99 Dec 23, 2022
a650aa7
Change requests
Karolk99 Dec 23, 2022
8f404dd
Requested changes
Karolk99 Dec 27, 2022
d3ab5e7
Code cleaning
Karolk99 Dec 27, 2022
a2b07c8
Merge pull request #209 from membraneframework/synchronize-streams-wa…
Karolk99 Dec 28, 2022
d8bf197
Update dependencies and ad audio realtimer
Karolk99 Dec 28, 2022
814e2be
Merge pull request #216 from membraneframework/update-deps-add-realitmer
Karolk99 Dec 28, 2022
40664a8
Spawn mixers before first peer
Karolk99 Jan 11, 2023
2f6d383
Add audio filler and clean code
Karolk99 Jan 6, 2023
4f93e1e
Add audio filler
Karolk99 Jan 11, 2023
4167816
Add ll-hls
Karolk99 Jan 11, 2023
d4c15bf
Change requests
Karolk99 Jan 12, 2023
2d8719a
Merge pull request #219 from jellyfish-dev/fix-synchronization
Karolk99 Jan 12, 2023
da1b5f3
Add compositor tranformations
Karolk99 Jan 16, 2023
a2946bc
Add cropping and corners rounding
Karolk99 Jan 16, 2023
b033681
Update dependencies
Karolk99 Jan 17, 2023
0184af6
Merge branch 'add-compositor-with-synchronization' into compositor-tr…
Karolk99 Jan 18, 2023
91f20ce
Fix credo warnings
Karolk99 Jan 18, 2023
f9a8dac
Change requests
Karolk99 Jan 18, 2023
2c98a84
Merge pull request #222 from jellyfish-dev/compositor-transformations
Karolk99 Jan 18, 2023
4136022
Mobile layout maker refactor
Karolk99 Jan 18, 2023
2bee302
First part of hls refactor
Karolk99 Jan 23, 2023
a8171d1
Refactor draft
Karolk99 Jan 24, 2023
bf31102
Fix tests
Karolk99 Jan 25, 2023
d98237d
Requested changes
Karolk99 Jan 26, 2023
c858d01
Merge pull request #226 from jellyfish-dev/live-dev-refactor
Karolk99 Jan 26, 2023
e446356
Merge branch 'core-v0.11' into add-compositor-with-synchronization
Karolk99 Jan 30, 2023
855c5f8
Fix tests
Karolk99 Jan 31, 2023
81c9b76
Merge pull request #231 from jellyfish-dev/live-dev-update-core
Karolk99 Feb 1, 2023
60a9a65
Add new options to hls endpoint and fix tests
Karolk99 Feb 2, 2023
5c6aff3
Fix compilation without optional dependencies
Karolk99 Feb 2, 2023
adbcfaa
Fix linter warnings
Karolk99 Feb 2, 2023
8c89877
Fix dialyzer warnings
Karolk99 Feb 2, 2023
1b4d857
Update generator_plugin
Karolk99 Feb 3, 2023
3d2cfdb
Merge pull request #234 from jellyfish-dev/live-dev-update-core
Karolk99 Feb 3, 2023
a7887b5
Change requests
Karolk99 Feb 13, 2023
64a090d
Add test for hls notification 'update_layou'
Karolk99 Feb 14, 2023
9cd47aa
Merge branch 'core-v0.11' into add-compositor-with-synchronization
Karolk99 Feb 14, 2023
59e14ea
mix format
Karolk99 Feb 14, 2023
930dfe9
Fix integration tests and look check for RawVideo dependency in mixer…
Karolk99 Feb 14, 2023
df5e4f2
Requested changes
Karolk99 Feb 16, 2023
688705f
Requested changes
Karolk99 Feb 16, 2023
dcffa6e
Merge branch 'core-v0.11' into add-compositor-with-synchronization
mickel8 Feb 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration_test/test_videoroom/lib/test_videoroom/room.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ defmodule TestVideoroom.Room do
id: room_id
]

{:ok, _supervisor, pid} = Membrane.RTC.Engine.start(rtc_engine_options, [])
{:ok, pid} = Membrane.RTC.Engine.start(rtc_engine_options, [])
Process.monitor(pid)
Engine.register(pid, self())

Expand Down
22 changes: 11 additions & 11 deletions integration_test/test_videoroom/mix.lock

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/config/audio_mixer_config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
if Code.ensure_loaded?(Membrane.RawAudio) do
defmodule Membrane.RTC.Engine.Endpoint.HLS.AudioMixerConfig do
@moduledoc """
Module representing audio mixer configuration for the HLS endpoint.
"""

alias Membrane.RawAudio

@typedoc """
* `stream_format` - defines audio mixer output stream_format.
* `background` - module generating background sound that will be played from the beginning of the stream.
"""
@type t() :: %__MODULE__{
stream_format: RawAudio.t(),
background: struct() | nil
}
defstruct stream_format: %RawAudio{
channels: 1,
sample_rate: 48_000,
sample_format: :s16le
},
background: nil
end
end
31 changes: 31 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/config/compositor_config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
if Code.ensure_loaded?(Membrane.RawVideo) do
defmodule Membrane.RTC.Engine.Endpoint.HLS.CompositorConfig do
@moduledoc """
Module representing compositor configuration for the HLS endpoint.

Compositor is the element responsible for positioning input video streams
into one video output.
"""

@typedoc """
* `stream_format` - output video specification in Membrane.RawVideo format
* `layout_module` - Module implementing `Membrane.RTC.Engine.Endpoint.HLS.VideoLayoutMaker`
behavior that should be used by the HLS endpoint.
* `background` - Struct representing module that generates video used as a background in composed video.
"""
@type t() :: %__MODULE__{
stream_format: Membrane.RawVideo.t(),
layout_module: module(),
background: struct() | nil
}
defstruct stream_format: %Membrane.RawVideo{
width: 400,
height: 800,
pixel_format: :I420,
framerate: {24, 1},
aligned: true
},
layout_module: Membrane.RTC.Engine.Endpoint.HLS.MobileLayoutMaker,
background: nil
end
end
24 changes: 24 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/config/mixer_config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
if Enum.all?([Membrane.RawAudio, Membrane.RawVideo], &Code.ensure_loaded?/1) do
defmodule Membrane.RTC.Engine.Endpoint.HLS.MixerConfig do
@moduledoc """
Module representing mixer configuration for the HLS endpoint.
"""

alias Membrane.RTC.Engine.Endpoint.HLS.{AudioMixerConfig, CompositorConfig}

@typedoc """
* `video` - video compositor configuration.
* `audio` - audio mixer configuration.
* `persist?` - if set to true, the stream will be continuous even if all tracks are removed, if then some new track is added it will belong to the same stream.
Otherwise if set to false, stream will automatically end when the last track is removed, adding then new track will start a new hls stream.
"""
@type t() :: %__MODULE__{
video: CompositorConfig.t(),
audio: AudioMixerConfig.t(),
persist?: boolean()
}
defstruct video: %CompositorConfig{},
audio: %AudioMixerConfig{},
persist?: false
end
end
46 changes: 46 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/config/sink_bin_config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
if Code.ensure_loaded?(Membrane.HTTPAdaptiveStream.Manifest) do
defmodule Membrane.RTC.Engine.Endpoint.HLS.HLSConfig do
@moduledoc """
Module representing `Membrane.HTTPAdaptiveStream.SinkBin` configuration for the HLS endpoint.
"""

alias Membrane.HTTPAdaptiveStream.{Manifest, Storage}
alias Membrane.HTTPAdaptiveStream.Sink.SegmentDuration
alias Membrane.Time

@typedoc """
To read more about config options go to module Membrane.HTTPAdaptiveStream.SinkBin and read options descriptions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
To read more about config options go to module Membrane.HTTPAdaptiveStream.SinkBin and read options descriptions.
To read more about config options go to module `Membrane.HTTPAdaptiveStream.SinkBin` and read options descriptions.

* `segment_duration` - The segment duration range of the regular segments.
* `partial_segment_duration` - The segment duration range of the partial segments. If not set then the bin won't produce any partial segments.
"""
@type t() :: %__MODULE__{
manifest_name: String.t(),
manifest_module: Manifest.t(),
storage: (Path.t() -> Storage.config_t()),
target_window_duration: pos_integer | :infinity,
persist?: boolean,
mode: :live | :vod,
hls_mode: :muxed_av | :separate_av,
header_naming_fun: (Manifest.Track.t(), counter :: non_neg_integer() -> String.t()),
segment_naming_fun: (Manifest.Track.t() -> String.t()),
segment_duration: SegmentDuration.t(),
partial_segment_duration: SegmentDuration.t() | nil
}

defstruct manifest_name: "index",
manifest_module: Membrane.HTTPAdaptiveStream.HLS,
storage: &__MODULE__.default_storage/1,
target_window_duration: Membrane.Time.seconds(40),
persist?: false,
mode: :live,
hls_mode: :separate_av,
header_naming_fun: &Manifest.Track.default_header_naming_fun/2,
segment_naming_fun: &Manifest.Track.default_segment_naming_fun/1,
segment_duration: SegmentDuration.new(Time.seconds(4), Time.seconds(5)),
partial_segment_duration: nil

@spec default_storage(String.t()) :: any
def default_storage(directory),
do: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: directory}
end
end
176 changes: 176 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/mobile_layout_maker.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
if Code.ensure_loaded?(Membrane.VideoCompositor) do
defmodule Membrane.RTC.Engine.Endpoint.HLS.MobileLayoutMaker do
@moduledoc """
Module representing function for updating video layout for the HLS stream.

1) Only main presenter
_ _ _ _
| |
| |
| |
| |
| |
- - - -
2) Main presenter and one side presenter
_ _ _ _
| |
| |
| |
- - |
| | |
- - - -

3) Main presenter and two side presenters
_ _ _ _
| |
| |
| |
- - - -
| | |
- - - -
Comment on lines +4 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

Really cool docs 👍

"""

@behaviour Membrane.RTC.Engine.Endpoint.HLS.VideoLayoutMaker

alias Membrane.VideoCompositor.RustStructs.BaseVideoPlacement
alias Membrane.VideoCompositor.VideoTransformations
alias Membrane.VideoCompositor.VideoTransformations.TextureTransformations.CornersRounding
alias Membrane.VideoCompositor.VideoTransformations.TextureTransformations.Cropping

@main_stream %{position: {0, 0}, z_value: 0.1, corner_radius: 0}
@side_stream %{z_value: 0.3, corner_radius: 20, padding: 5}

@impl true
def init(output_stream_format), do: %{tracks: %{}, output_stream_format: output_stream_format}

@impl true
def track_added(%{metadata: %{"mainPresenter" => true}} = track, stream_format, state) do
new_state = put_in(state, [:tracks, track.id], {track, stream_format})

{layout, transformations} =
get_track_layout(:main, nil, stream_format, state.output_stream_format)

updated_layout = [
{track.id, layout, transformations}
]

{updated_layout, new_state}
end

@impl true
def track_added(track, stream_format, state) do
new_state = put_in(state, [:tracks, track.id], {track, stream_format})
{update_layout(new_state), new_state}
end

@impl true
def track_updated(track, stream_format, state), do: track_added(track, stream_format, state)

@impl true
def track_removed(track, state) do
{_, new_state} = pop_in(state, [:tracks, track.id])
{update_layout(new_state), new_state}
end

defp update_layout(%{tracks: tracks, output_stream_format: output_stream_format}) do
tracks
|> Enum.filter(fn {_id, {track, _stream_format}} ->
track.type == :video and not track.metadata["mainPresenter"]
end)
|> Enum.with_index()
|> Enum.flat_map(fn {{_id, {track, stream_format}}, index} ->
{layout, transcoding} =
get_track_layout(:side, index, stream_format, output_stream_format)

[
{track.id, layout, transcoding}
]
end)
end

defp get_track_layout(:main, _index, stream_format, output_stream_format) do
placement =
get_placement(
output_stream_format,
stream_format,
@main_stream.position,
@main_stream.z_value
)

transformations =
get_transformations(output_stream_format, placement.size, @main_stream.corner_radius)

{placement, transformations}
end

defp get_track_layout(:side, index, stream_format, %{width: width, height: height}) do
# side track layout is a thumbnail that is placed on the bottom of the main stream.
output_stream_format = %{
width: round(1 / 2 * width) - @side_stream.padding * 2,
height: round(1 / 4 * height) - @side_stream.padding * 2
}

# x coordinate of a thumbnail can differ depends on which index they have, y coordinate is always the same.
position_x = round(index / 2 * width) + @side_stream.padding
position_y = height - round(1 / 4 * height) + @side_stream.padding

position = {position_x, position_y}

placement =
get_placement(output_stream_format, stream_format, position, @side_stream.z_value)

transformations =
get_transformations(output_stream_format, placement.size, @side_stream.corner_radius)

{placement, transformations}
end

defp video_proportion(%{width: width, height: height}), do: width / height

defp get_placement(output_stream_format, stream_format, position, z_value),
do: %BaseVideoPlacement{
position: position,
size: get_display_size(output_stream_format, stream_format),
z_value: z_value
}

defp get_display_size(output_stream_format, stream_format) do
if video_proportion(output_stream_format) > video_proportion(stream_format),
do:
{output_stream_format.width,
round(output_stream_format.width / stream_format.width * stream_format.height)},
else:
{round(output_stream_format.height / stream_format.height * stream_format.width),
output_stream_format.height}
end

defp get_transformations(output_stream_format, display_size, radius),
do: %VideoTransformations{
texture_transformations: [
get_cropping(output_stream_format, display_size),
get_corners_rounding(radius)
]
}

defp get_corners_rounding(radius), do: %CornersRounding{border_radius: radius}

defp get_cropping(output_stream_format, display_size),
do: %Cropping{
crop_top_left_corner: get_cropping_position(output_stream_format, display_size),
crop_size: get_cropping_size(output_stream_format, display_size),
cropped_video_position: :input_position
}

defp get_cropping_position(output_stream_format, {width, height}) do
if output_stream_format.width == width,
do: {0.0, (height - output_stream_format.height) / (2 * height)},
else: {(width - output_stream_format.width) / (2 * width), 0.0}
end

defp get_cropping_size(output_stream_format, {width, height}) do
if output_stream_format.width == width,
do: {1.0, output_stream_format.height / height},
else: {output_stream_format.width / width, 1.0}
end
end
end
59 changes: 59 additions & 0 deletions lib/membrane_rtc_engine/endpoints/hls/stream_format_updater.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Membrane.RTC.Engine.Endpoint.HLS.StreamFormatUpdater do
@moduledoc """
Element responsible for sending stream format for specific track to HLS Endpoint and waiting for Endpoint to send updated layout to compositor element.
"""

use Membrane.Filter

def_input_pad :input,
accepted_format: _any,
demand_unit: :buffers,
demand_mode: :auto

def_output_pad :output,
accepted_format: _any,
demand_mode: :auto

@impl true
def handle_init(_ctx, _opts) do
{[], %{update_queue: 0, buffers: %{}, end_of_stream: false}}
end

@impl true
def handle_stream_format(_pad, stream_format, _ctx, state) do
state = put_in(state, [:buffers, state.update_queue + 1], [])

{[forward: stream_format, notify_parent: {:update_layout, stream_format}],
%{state | update_queue: state.update_queue + 1}}
end

@impl true
def handle_end_of_stream(_pad, _ctx, %{update__queue: 0} = state),
do: {[end_of_stream: :output], state}

@impl true
def handle_end_of_stream(_pad, _ctx, state), do: {[], %{state | end_of_stream: true}}

@impl true
def handle_process(_pad, buffer, _ctx, %{update_queue: 0} = state),
do: {[buffer: {:output, buffer}], state}

@impl true
def handle_process(_pad, buffer, _ctx, state) do
state = update_in(state, [:buffers, state.update_queue], &[buffer | &1])
{[], state}
end

@impl true
def handle_parent_notification(:layout_updated, _ctx, state) do
{buffers, state} = pop_in(state, [:buffers, state.update_queue])
buffers = Enum.reverse(buffers)
actions = [buffer: {:output, buffers}] ++ maybe_notify_end_of_stream(state)
{actions, %{state | update_queue: state.update_queue - 1}}
end

defp maybe_notify_end_of_stream(%{end_of_stream: true, update_queue: 0}),
do: [end_of_stream: :output]

defp maybe_notify_end_of_stream(_state), do: []
end
Loading