Skip to content

Commit b2eb80a

Browse files
mat-hekmaslowalex
andauthored
Allow raw rtp in sink (#36)
* allow to receive RemoteStream{content_type: Membrane.RTP} in Membrane.WebRTC.Sink * allow to negotiate AV1 codecs * added symmetrical changes to source, refactored code, added meaningful error messages * fix linters * allow to receive RemoteStream{content_type: Membrane.RTP} in Membrane.WebRTC.Sink * allow to negotiate AV1 codecs * added symmetrical changes to source, refactored code, added meaningful error messages * fixed code style issues * bump version --------- Co-authored-by: maslowalex <maslo.rails@gmail.com>
1 parent 3db2ce0 commit b2eb80a

File tree

7 files changed

+118
-15
lines changed

7 files changed

+118
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The package can be installed by adding `membrane_webrtc_plugin` to your list of
1515
```elixir
1616
def deps do
1717
[
18-
{:membrane_webrtc_plugin, "~> 0.26.1"}
18+
{:membrane_webrtc_plugin, "~> 0.26.2"}
1919
]
2020
end
2121
```

lib/membrane_webrtc/ex_webrtc/sink.ex

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ defmodule Membrane.WebRTC.ExWebRTCSink do
130130
:ok
131131

132132
%{kind: :video, codec: nil} ->
133-
supported_video_codecs = get_transceiver(state, pad)
133+
transceiver = get_transceiver(state, pad)
134+
supported_video_codecs = transceiver.codecs
134135

135136
if length(supported_video_codecs) > 1 do
136137
raise """
@@ -151,8 +152,8 @@ defmodule Membrane.WebRTC.ExWebRTCSink do
151152
{[], state}
152153
end
153154

154-
defp set_pc_sender_video_codec(state, pad, codec) when codec in [:vp8, :h264] do
155-
mime_type = if codec == :vp8, do: "video/VP8", else: "video/H264"
155+
defp set_pc_sender_video_codec(state, pad, codec) when codec in [:vp8, :h264, :av1] do
156+
mime_type = mime_type_from_codec(codec)
156157
transceiver = get_transceiver(state, pad)
157158

158159
if transceiver == nil do
@@ -371,4 +372,8 @@ defmodule Membrane.WebRTC.ExWebRTCSink do
371372
seq_num = rem(params.seq_num + 1, @max_rtp_seq_num + 1)
372373
put_in(state.input_tracks[pad], {id, %{params | seq_num: seq_num}})
373374
end
375+
376+
defp mime_type_from_codec(:h264), do: "video/H264"
377+
defp mime_type_from_codec(:vp8), do: "video/VP8"
378+
defp mime_type_from_codec(:av1), do: "video/AV1"
374379
end

lib/membrane_webrtc/ex_webrtc/utils.ex

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
33

44
alias ExWebRTC.RTPCodecParameters
55

6-
@type codec :: :opus | :h264 | :vp8
6+
@type codec :: :opus | :h264 | :vp8 | :av1
77
@type codec_or_codecs :: codec() | [codec()]
88

99
@spec codec_params(codec_or_codecs()) :: [RTPCodecParameters.t()]
@@ -43,6 +43,22 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
4343
]
4444
end
4545

46+
def codec_params(:av1) do
47+
[
48+
%RTPCodecParameters{
49+
payload_type: 45,
50+
mime_type: "video/AV1",
51+
clock_rate: codec_clock_rate(:av1),
52+
sdp_fmtp_line: %ExSDP.Attribute.FMTP{
53+
pt: 45,
54+
profile: 0,
55+
level_idx: 5,
56+
tier: 0
57+
}
58+
}
59+
]
60+
end
61+
4662
def codec_params(codecs) when is_list(codecs) do
4763
codecs |> Enum.flat_map(&codec_params/1)
4864
end
@@ -51,6 +67,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
5167
def codec_clock_rate(:opus), do: 48_000
5268
def codec_clock_rate(:vp8), do: 90_000
5369
def codec_clock_rate(:h264), do: 90_000
70+
def codec_clock_rate(:av1), do: 90_000
5471

5572
def codec_clock_rate(codecs) when is_list(codecs) do
5673
cond do
@@ -62,7 +79,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
6279
end
6380
end
6481

65-
@spec get_video_codecs_from_sdp(ExWebRTC.SessionDescription.t()) :: [:h264 | :vp8]
82+
@spec get_video_codecs_from_sdp(ExWebRTC.SessionDescription.t()) :: [:h264 | :vp8 | :av1]
6683
def get_video_codecs_from_sdp(%ExWebRTC.SessionDescription{sdp: sdp}) do
6784
ex_sdp = ExSDP.parse!(sdp)
6885

@@ -74,6 +91,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
7491
|> Enum.flat_map(fn
7592
%ExSDP.Attribute.RTPMapping{encoding: "H264"} -> [:h264]
7693
%ExSDP.Attribute.RTPMapping{encoding: "VP8"} -> [:vp8]
94+
%ExSDP.Attribute.RTPMapping{encoding: "AV1"} -> [:av1]
7795
_attribute -> []
7896
end)
7997
|> Enum.uniq()

lib/membrane_webrtc/sink.ex

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ defmodule Membrane.WebRTC.Sink do
6868
"""
6969
],
7070
video_codec: [
71-
spec: :vp8 | :h264 | [:vp8 | :h264],
71+
spec: :vp8 | :h264 | :av1 | [:vp8 | :h264 | :av1],
7272
default: [:vp8, :h264],
7373
description: """
7474
Video codecs, that #{inspect(__MODULE__)} will try to negotiatie in SDP
@@ -103,6 +103,7 @@ defmodule Membrane.WebRTC.Sink do
103103
any_of(
104104
%Membrane.H264{alignment: :nalu},
105105
%Membrane.RemoteStream{content_format: Membrane.VP8},
106+
%Membrane.RemoteStream{content_format: Membrane.RTP},
106107
Membrane.VP8,
107108
Membrane.Opus,
108109
Membrane.RTP
@@ -146,8 +147,10 @@ defmodule Membrane.WebRTC.Sink do
146147
spec =
147148
cond do
148149
not state.payload_rtp ->
150+
codec = if kind == :video, do: ensure_single_video_codec(state.video_codec), else: :opus
151+
149152
bin_input(pad_ref)
150-
|> via_in(pad_ref, options: [kind: kind])
153+
|> via_in(pad_ref, options: [kind: kind, codec: codec])
151154
|> get_child(:webrtc)
152155

153156
kind == :audio ->
@@ -175,7 +178,6 @@ defmodule Membrane.WebRTC.Sink do
175178
{[], state}
176179
end
177180

178-
@impl true
179181
def handle_child_notification(
180182
{:stream_format, _connector_pad, stream_format},
181183
{:connector, pad_ref},
@@ -184,9 +186,21 @@ defmodule Membrane.WebRTC.Sink do
184186
) do
185187
codec =
186188
case stream_format do
187-
%H264{} -> :h264
188-
%VP8{} -> :vp8
189-
%RemoteStream{content_format: VP8} -> :vp8
189+
%H264{} ->
190+
:h264
191+
192+
%VP8{} ->
193+
:vp8
194+
195+
%RemoteStream{content_format: VP8} ->
196+
:vp8
197+
198+
other ->
199+
raise """
200+
Unsupported stream format for payloading: #{inspect(other)}
201+
If you're sending raw RTP or using a codec without a built-in payloader (like AV1),
202+
set `payload_rtp: false` in the Sink options.
203+
"""
190204
end
191205

192206
payloader =
@@ -231,4 +245,8 @@ defmodule Membrane.WebRTC.Sink do
231245
end
232246

233247
def default_ice_ip_filter(_ip), do: true
248+
249+
defp ensure_single_video_codec(codec) when is_atom(codec), do: codec
250+
defp ensure_single_video_codec([codec]), do: codec
251+
defp ensure_single_video_codec(_codecs), do: nil
234252
end

lib/membrane_webrtc/source.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ defmodule Membrane.WebRTC.Source do
6464
"""
6565
],
6666
allowed_video_codecs: [
67-
spec: :vp8 | :h264 | [:vp8 | :h264],
67+
spec: :vp8 | :h264 | :av1 | [:vp8 | :h264 | :av1],
6868
default: :vp8,
6969
description: """
7070
Specifies, which video codecs can be accepted by the source during the SDP
@@ -260,6 +260,14 @@ defmodule Membrane.WebRTC.Source do
260260

261261
state.negotiated_video_codecs == nil ->
262262
raise "Cannot select depayloader before end of SDP messages exchange"
263+
264+
true ->
265+
raise """
266+
Unsupported video codec for depayloading: #{inspect(state.negotiated_video_codecs)}
267+
268+
If you're receiving raw RTP or using a codec without a built-in depayloader (like AV1),
269+
set `depayload_rtp: false` in the Source options.
270+
"""
263271
end
264272
end
265273

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Membrane.WebRTC.Plugin.Mixfile do
22
use Mix.Project
33

4-
@version "0.26.1"
4+
@version "0.26.2"
55
@github_url "https://github.com/membraneframework/membrane_webrtc_plugin"
66

77
def project do

test/membrane_webrtc/integration_test.exs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ defmodule Membrane.WebRTC.IntegrationTest do
6969
defmodule Utils do
7070
import ExUnit.Assertions
7171

72-
def fixture_processing_timeout, do: 30_000
72+
def fixture_processing_timeout(), do: 30_000
7373

7474
def prepare_input(pipeline, opts) do
7575
demuxer_name = {:demuxer, make_ref()}
@@ -504,4 +504,58 @@ defmodule Membrane.WebRTC.IntegrationTest do
504504
end)
505505
end
506506
end
507+
508+
defmodule AV1RawRTPPassthrough do
509+
use ExUnit.Case, async: true
510+
511+
import Membrane.ChildrenSpec
512+
import Membrane.Testing.Assertions
513+
514+
require Membrane.Pad, as: Pad
515+
516+
alias Membrane.Testing
517+
alias Membrane.WebRTC
518+
alias Membrane.WebRTC.Signaling
519+
520+
test "send and receive AV1 via raw RTP passthrough" do
521+
signaling = Signaling.new()
522+
523+
send_pipeline = Testing.Pipeline.start_link_supervised!()
524+
receive_pipeline = Testing.Pipeline.start_link_supervised!()
525+
526+
Testing.Pipeline.execute_actions(send_pipeline,
527+
spec: [
528+
child(:video_source, %KeyframeTestSource{
529+
stream_format: %Membrane.RTP{}
530+
})
531+
|> via_in(:input, options: [kind: :video])
532+
|> child(:webrtc, %WebRTC.Sink{
533+
signaling: signaling,
534+
payload_rtp: false,
535+
video_codec: :av1,
536+
tracks: [:video]
537+
})
538+
]
539+
)
540+
541+
Testing.Pipeline.execute_actions(receive_pipeline,
542+
spec: [
543+
child(:webrtc, %WebRTC.Source{
544+
signaling: signaling,
545+
depayload_rtp: false,
546+
allowed_video_codecs: :av1
547+
})
548+
|> via_out(Pad.ref(:output, :video), options: [kind: :video])
549+
|> child(:video_sink, KeyframeTestSink)
550+
]
551+
)
552+
553+
assert_pipeline_notified(send_pipeline, :webrtc, {:negotiated_video_codecs, [:av1]})
554+
assert_pipeline_notified(receive_pipeline, :webrtc, {:negotiated_video_codecs, [:av1]})
555+
assert_pipeline_notified(receive_pipeline, :video_sink, {:buffer, _buffer}, 5_000)
556+
557+
Testing.Pipeline.terminate(send_pipeline)
558+
Testing.Pipeline.terminate(receive_pipeline)
559+
end
560+
end
507561
end

0 commit comments

Comments
 (0)