Skip to content

Commit bc4a103

Browse files
committed
Add ssrc attributes to the SDP
1 parent 1d09628 commit bc4a103

File tree

3 files changed

+153
-42
lines changed

3 files changed

+153
-42
lines changed

lib/ex_webrtc/peer_connection.ex

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -870,18 +870,19 @@ defmodule ExWebRTC.PeerConnection do
870870
@impl true
871871
def handle_call({:add_track, %MediaStreamTrack{kind: kind} = track}, _from, state) do
872872
# we ignore the condition that sender has never been used to send
873-
{ssrc, rtx_ssrc} = generate_ssrcs(state)
874873

875874
{transceivers, sender} =
876875
state.transceivers
877876
|> Enum.with_index()
878877
|> Enum.find(fn {tr, _idx} -> RTPTransceiver.can_add_track?(tr, kind) end)
879878
|> case do
880879
{tr, idx} ->
881-
tr = RTPTransceiver.add_track(tr, track, ssrc, rtx_ssrc)
880+
tr = RTPTransceiver.add_track(tr, track)
882881
{List.replace_at(state.transceivers, idx, tr), tr.sender}
883882

884883
nil ->
884+
{ssrc, rtx_ssrc} = generate_ssrcs(state)
885+
885886
options = [
886887
direction: :sendrecv,
887888
added_by_add_track: true,
@@ -910,8 +911,7 @@ defmodule ExWebRTC.PeerConnection do
910911
{:reply, {:error, :invalid_track_type}, state}
911912

912913
{tr, idx} when tr.direction in [:sendrecv, :sendonly] ->
913-
{ssrc, rtx_ssrc} = generate_ssrcs(state)
914-
tr = RTPTransceiver.replace_track(tr, track, ssrc, rtx_ssrc)
914+
tr = RTPTransceiver.replace_track(tr, track)
915915
transceivers = List.replace_at(state.transceivers, idx, tr)
916916
state = %{state | transceivers: transceivers}
917917
{:reply, :ok, state}
@@ -1649,7 +1649,13 @@ defmodule ExWebRTC.PeerConnection do
16491649
transceivers =
16501650
sdp.media
16511651
|> Enum.reject(&SDPUtils.data_channel?/1)
1652-
|> process_mlines_remote(state.transceivers, type, state.config, state.owner)
1652+
|> process_mlines_remote(
1653+
state.transceivers,
1654+
type,
1655+
Map.keys(state.demuxer.ssrc_to_mid),
1656+
state.config,
1657+
state.owner
1658+
)
16531659

16541660
# infer our role from the remote role
16551661
dtls_role = if dtls_role in [:actpass, :passive], do: :active, else: :passive
@@ -1835,25 +1841,40 @@ defmodule ExWebRTC.PeerConnection do
18351841
end
18361842

18371843
# See W3C WebRTC 4.4.1.5-4.7.10.2
1838-
defp process_mlines_remote(mlines, transceivers, sdp_type, config, owner) do
1844+
defp process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner) do
18391845
mlines_idx = Enum.with_index(mlines)
1840-
do_process_mlines_remote(mlines_idx, transceivers, sdp_type, config, owner)
1846+
do_process_mlines_remote(mlines_idx, transceivers, sdp_type, demuxer_ssrcs, config, owner)
18411847
end
18421848

1843-
defp do_process_mlines_remote([], transceivers, _sdp_type, _config, _owner), do: transceivers
1849+
defp do_process_mlines_remote([], transceivers, _sdp_type, _demuxer_ssrcs, _config, _owner),
1850+
do: transceivers
18441851

1845-
defp do_process_mlines_remote([{mline, idx} | mlines], transceivers, sdp_type, config, owner) do
1852+
defp do_process_mlines_remote(
1853+
[{mline, idx} | mlines],
1854+
transceivers,
1855+
sdp_type,
1856+
demuxer_ssrcs,
1857+
config,
1858+
owner
1859+
) do
18461860
direction =
18471861
if SDPUtils.rejected?(mline),
18481862
do: :inactive,
18491863
else: SDPUtils.get_media_direction(mline) |> reverse_direction()
18501864

1865+
rtp_sender_ssrcs = Enum.map(transceivers, & &1.sender.ssrc)
1866+
ssrcs = MapSet.new(demuxer_ssrcs ++ rtp_sender_ssrcs)
1867+
1868+
ssrc = do_generate_ssrc(ssrcs)
1869+
ssrcs = MapSet.put(ssrcs, ssrc)
1870+
rtx_ssrc = do_generate_ssrc(ssrcs)
1871+
18511872
# Note: in theory we should update transceiver codecs
18521873
# after processing remote track but this shouldn't have any impact
18531874
{idx, tr} =
18541875
case find_transceiver_from_remote(transceivers, mline) do
18551876
{idx, tr} -> {idx, RTPTransceiver.update(tr, mline, config)}
1856-
nil -> {nil, RTPTransceiver.from_mline(mline, idx, config)}
1877+
nil -> {nil, RTPTransceiver.from_mline(mline, idx, ssrc, rtx_ssrc, config)}
18571878
end
18581879

18591880
tr = process_remote_track(tr, direction, owner)
@@ -1867,11 +1888,11 @@ defmodule ExWebRTC.PeerConnection do
18671888
case idx do
18681889
nil ->
18691890
transceivers = transceivers ++ [tr]
1870-
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
1891+
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)
18711892

18721893
idx ->
18731894
transceivers = List.replace_at(transceivers, idx, tr)
1874-
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
1895+
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)
18751896
end
18761897
end
18771898

@@ -2186,6 +2207,8 @@ defmodule ExWebRTC.PeerConnection do
21862207

21872208
# this is practically impossible so it's easier to raise
21882209
# than to propagate the error up to the user
2210+
defp do_generate_ssrc(ssrcs, max_attempts \\ 200)
2211+
21892212
defp do_generate_ssrc(_ssrcs, 0), do: raise("Couldn't find free SSRC")
21902213

21912214
defp do_generate_ssrc(ssrcs, max_attempts) do

lib/ex_webrtc/rtp_sender.ex

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ defmodule ExWebRTC.RTPSender do
2121
mid: String.t() | nil,
2222
pt: non_neg_integer() | nil,
2323
rtx_pt: non_neg_integer() | nil,
24-
ssrc: non_neg_integer() | nil,
25-
rtx_ssrc: non_neg_integer() | nil,
24+
# ssrc and rtx_ssrc are always present, even if there is no track
25+
# or transceiver direction is recvonly.
26+
# We preallocate them so they can be included in SDP when needed.
27+
ssrc: non_neg_integer(),
28+
rtx_ssrc: non_neg_integer(),
2629
packets_sent: non_neg_integer(),
2730
bytes_sent: non_neg_integer(),
2831
retransmitted_packets_sent: non_neg_integer(),
@@ -68,8 +71,8 @@ defmodule ExWebRTC.RTPSender do
6871
RTPCodecParameters.t() | nil,
6972
[Extmap.t()],
7073
String.t() | nil,
71-
non_neg_integer() | nil,
72-
non_neg_integer() | nil,
74+
non_neg_integer(),
75+
non_neg_integer(),
7376
[atom()]
7477
) :: sender()
7578
def new(track, codec, rtx_codec, rtp_hdr_exts, mid, ssrc, rtx_ssrc, features) do
@@ -131,6 +134,81 @@ defmodule ExWebRTC.RTPSender do
131134
}
132135
end
133136

137+
@spec get_mline_attrs(sender()) :: [ExSDP.Attribute.t()]
138+
def get_mline_attrs(sender) do
139+
# Don't include track id. See RFC 8829 sec. 5.2.1
140+
msid_attrs =
141+
case sender.track do
142+
nil ->
143+
# In theory, we should do this "for each MediaStream that was associated with the transceiver"
144+
# but web browsers (chrome, ff), include MSID even when there aren't any MediaStreams
145+
[ExSDP.Attribute.MSID.new("-", nil)]
146+
147+
%MediaStreamTrack{streams: streams} ->
148+
case Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, nil)) do
149+
[] -> [ExSDP.Attribute.MSID.new("-", nil)]
150+
other -> other
151+
end
152+
end
153+
154+
ssrc_attrs =
155+
get_ssrc_attrs(sender.pt, sender.rtx_pt, sender.ssrc, sender.rtx_ssrc, sender.track)
156+
157+
msid_attrs ++ ssrc_attrs
158+
end
159+
160+
# we didn't manage to negotiate any codec
161+
defp get_ssrc_attrs(nil, _rtx_pt, _ssrc, _rtx_ssrc, _track) do
162+
[]
163+
end
164+
165+
# we have a codec but not rtx
166+
defp get_ssrc_attrs(_pt, nil, ssrc, _rtx_ssrc, track) do
167+
streams = (track && track.streams) || []
168+
169+
case streams do
170+
[] ->
171+
[%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"}]
172+
173+
streams ->
174+
Enum.map(streams, fn stream ->
175+
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
176+
end)
177+
end
178+
end
179+
180+
# we have both codec and rtx
181+
defp get_ssrc_attrs(_pt, _rtx_pt, ssrc, rtx_ssrc, track) do
182+
streams = (track && track.streams) || []
183+
184+
case streams do
185+
[] ->
186+
[
187+
%ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]},
188+
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"},
189+
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: "-"}
190+
]
191+
192+
streams ->
193+
{ssrc_attrs, rtx_ssrc_attrs} =
194+
Enum.reduce(streams, {[], []}, fn stream, {ssrc_attrs, rtx_ssrc_attrs} ->
195+
ssrc_attr = [%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}]
196+
ssrc_attrs = [ssrc_attr | ssrc_attrs]
197+
198+
rtx_ssrc_attr = [
199+
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: stream}
200+
]
201+
202+
rtx_ssrc_attrs = [rtx_ssrc_attr | rtx_ssrc_attrs]
203+
204+
{ssrc_attrs, rtx_ssrc_attrs}
205+
end)
206+
207+
fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
208+
[fid | Enum.reverse(ssrc_attrs) ++ Enum.reverse(rtx_ssrc_attrs)]
209+
end
210+
end
211+
134212
@doc false
135213
@spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()}
136214
def send_packet(sender, packet, rtx?) do

lib/ex_webrtc/rtp_transceiver.ex

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,14 @@ defmodule ExWebRTC.RTPTransceiver do
174174
end
175175

176176
@doc false
177-
@spec from_mline(ExSDP.Media.t(), non_neg_integer(), Configuration.t()) :: transceiver()
178-
def from_mline(mline, mline_idx, config) do
177+
@spec from_mline(
178+
ExSDP.Media.t(),
179+
non_neg_integer(),
180+
non_neg_integer(),
181+
non_neg_integer(),
182+
Configuration.t()
183+
) :: transceiver()
184+
def from_mline(mline, mline_idx, ssrc, rtx_ssrc, config) do
179185
header_extensions = Configuration.intersect_extensions(config, mline)
180186
codecs = Configuration.intersect_codecs(config, mline)
181187

@@ -205,7 +211,16 @@ defmodule ExWebRTC.RTPTransceiver do
205211
receiver = RTPReceiver.new(track, codec, header_extensions, config.features)
206212

207213
sender =
208-
RTPSender.new(nil, codec, codec_rtx, header_extensions, mid, nil, nil, config.features)
214+
RTPSender.new(
215+
nil,
216+
codec,
217+
codec_rtx,
218+
header_extensions,
219+
mid,
220+
ssrc,
221+
rtx_ssrc,
222+
config.features
223+
)
209224

210225
%{
211226
id: id,
@@ -281,10 +296,9 @@ defmodule ExWebRTC.RTPTransceiver do
281296
end
282297

283298
@doc false
284-
@spec add_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) ::
285-
transceiver()
286-
def add_track(transceiver, track, ssrc, rtx_ssrc) do
287-
sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc}
299+
@spec add_track(transceiver(), MediaStreamTrack.t()) :: transceiver()
300+
def add_track(transceiver, track) do
301+
sender = %{transceiver.sender | track: track}
288302

289303
direction =
290304
case transceiver.direction do
@@ -297,11 +311,9 @@ defmodule ExWebRTC.RTPTransceiver do
297311
end
298312

299313
@doc false
300-
@spec replace_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) ::
301-
transceiver()
302-
def replace_track(transceiver, track, ssrc, rtx_ssrc) do
303-
ssrc = transceiver.sender.ssrc || ssrc
304-
sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc}
314+
@spec replace_track(transceiver(), MediaStreamTrack.t()) :: transceiver()
315+
def replace_track(transceiver, track) do
316+
sender = %{transceiver.sender | track: track}
305317
%{transceiver | sender: sender}
306318
end
307319

@@ -526,39 +538,37 @@ defmodule ExWebRTC.RTPTransceiver do
526538
[rtp_mapping, codec.sdp_fmtp_line, codec.rtcp_fbs]
527539
end)
528540

529-
msids =
530-
case transceiver.sender.track do
531-
nil ->
532-
[]
533-
534-
%MediaStreamTrack{id: id, streams: streams} ->
535-
case Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, id)) do
536-
[] -> [ExSDP.Attribute.MSID.new("-", id)]
537-
other -> other
538-
end
539-
end
541+
direction = Keyword.get(opts, :direction, transceiver.direction)
540542

541543
attributes =
542544
if(Keyword.get(opts, :rtcp, false), do: [{"rtcp", "9 IN IP4 0.0.0.0"}], else: []) ++
543545
Keyword.get(opts, :simulcast, []) ++
544546
[
545-
Keyword.get(opts, :direction, transceiver.direction),
547+
direction,
546548
{:mid, transceiver.mid},
547549
{:ice_ufrag, Keyword.fetch!(opts, :ice_ufrag)},
548550
{:ice_pwd, Keyword.fetch!(opts, :ice_pwd)},
549551
{:ice_options, Keyword.fetch!(opts, :ice_options)},
550552
{:fingerprint, Keyword.fetch!(opts, :fingerprint)},
551553
{:setup, Keyword.fetch!(opts, :setup)},
552554
:rtcp_mux
553-
] ++ transceiver.header_extensions ++ msids
555+
] ++ transceiver.header_extensions
556+
557+
# add sender attrs only if we send
558+
sender_attrs =
559+
if direction in [:sendonly, :sendrecv] do
560+
RTPSender.get_mline_attrs(transceiver.sender)
561+
else
562+
[]
563+
end
554564

555565
%ExSDP.Media{
556566
ExSDP.Media.new(transceiver.kind, 9, "UDP/TLS/RTP/SAVPF", pt)
557567
| # mline must be followed by a cline, which must contain
558568
# the default value "IN IP4 0.0.0.0" (as there are no candidates yet)
559569
connection_data: [%ExSDP.ConnectionData{address: {0, 0, 0, 0}}]
560570
}
561-
|> ExSDP.add_attributes(attributes ++ media_formats)
571+
|> ExSDP.add_attributes(attributes ++ media_formats ++ sender_attrs)
562572
end
563573

564574
# RFC 3264 (6.1) + RFC 8829 (5.3.1)

0 commit comments

Comments
 (0)