Skip to content

Commit f87341e

Browse files
authored
Add ssrc attributes to the SDP (#185)
1 parent 1d09628 commit f87341e

File tree

6 files changed

+332
-63
lines changed

6 files changed

+332
-63
lines changed

lib/ex_webrtc/peer_connection.ex

Lines changed: 40 additions & 18 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
@@ -1803,7 +1809,7 @@ defmodule ExWebRTC.PeerConnection do
18031809
end
18041810
end
18051811

1806-
# See W3C WebRTC 4.4.1.5-4.7.10.1
1812+
# See W3C WebRTC 4.4.1.4-4.7.10.1
18071813
defp process_mlines_local(_mlines, transceivers, :offer, _owner), do: transceivers
18081814

18091815
defp process_mlines_local([], transceivers, :answer, _owner), do: transceivers
@@ -1834,26 +1840,36 @@ defmodule ExWebRTC.PeerConnection do
18341840
process_mlines_local(mlines, transceivers, :answer, owner)
18351841
end
18361842

1837-
# See W3C WebRTC 4.4.1.5-4.7.10.2
1838-
defp process_mlines_remote(mlines, transceivers, sdp_type, config, owner) do
1843+
# See W3C WebRTC 4.4.1.4-4.7.10.2
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+
{ssrc, rtx_ssrc} = generate_ssrcs(transceivers, demuxer_ssrcs)
1866+
18511867
# Note: in theory we should update transceiver codecs
18521868
# after processing remote track but this shouldn't have any impact
18531869
{idx, tr} =
18541870
case find_transceiver_from_remote(transceivers, mline) do
18551871
{idx, tr} -> {idx, RTPTransceiver.update(tr, mline, config)}
1856-
nil -> {nil, RTPTransceiver.from_mline(mline, idx, config)}
1872+
nil -> {nil, RTPTransceiver.from_mline(mline, idx, ssrc, rtx_ssrc, config)}
18571873
end
18581874

18591875
tr = process_remote_track(tr, direction, owner)
@@ -1867,11 +1883,11 @@ defmodule ExWebRTC.PeerConnection do
18671883
case idx do
18681884
nil ->
18691885
transceivers = transceivers ++ [tr]
1870-
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
1886+
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)
18711887

18721888
idx ->
18731889
transceivers = List.replace_at(transceivers, idx, tr)
1874-
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
1890+
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)
18751891
end
18761892
end
18771893

@@ -2177,15 +2193,21 @@ defmodule ExWebRTC.PeerConnection do
21772193
end
21782194

21792195
defp generate_ssrcs(state) do
2180-
rtp_sender_ssrcs = Enum.map(state.transceivers, & &1.sender.ssrc)
2181-
ssrcs = MapSet.new(Map.keys(state.demuxer.ssrc_to_mid) ++ rtp_sender_ssrcs)
2182-
ssrc = do_generate_ssrc(ssrcs, 200)
2183-
rtx_ssrc = do_generate_ssrc(MapSet.put(ssrcs, ssrc), 200)
2196+
generate_ssrcs(state.transceivers, Map.keys(state.demuxer.ssrc_to_mid))
2197+
end
2198+
2199+
defp generate_ssrcs(transceivers, demuxer_ssrcs) do
2200+
rtp_sender_ssrcs = Enum.map(transceivers, & &1.sender.ssrc)
2201+
ssrcs = MapSet.new(demuxer_ssrcs ++ rtp_sender_ssrcs)
2202+
ssrc = do_generate_ssrc(ssrcs)
2203+
rtx_ssrc = do_generate_ssrc(MapSet.put(ssrcs, ssrc))
21842204
{ssrc, rtx_ssrc}
21852205
end
21862206

21872207
# this is practically impossible so it's easier to raise
21882208
# than to propagate the error up to the user
2209+
defp do_generate_ssrc(ssrcs, max_attempts \\ 200)
2210+
21892211
defp do_generate_ssrc(_ssrcs, 0), do: raise("Couldn't find free SSRC")
21902212

21912213
defp do_generate_ssrc(ssrcs, max_attempts) do

lib/ex_webrtc/rtp_sender.ex

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

lib/ex_webrtc/rtp_sender/nack_responder.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ defmodule ExWebRTC.RTPSender.NACKResponder do
2828

2929
{packets, seq_no} =
3030
seq_nos
31-
|> Enum.map(fn seq_no -> {seq_no, Map.get(responder.packets, rem(seq_no, @max_packets))} end)
32-
|> Enum.filter(fn {seq_no, packet} -> packet != nil and packet.sequence_number == seq_no end)
31+
|> Enum.map(fn seq_no ->
32+
{seq_no, Map.get(responder.packets, rem(seq_no, @max_packets))}
33+
end)
34+
|> Enum.filter(fn {seq_no, packet} ->
35+
packet != nil and packet.sequence_number == seq_no
36+
end)
3337
# ssrc will be assigned by the sender
3438
|> Enum.map_reduce(responder.seq_no, fn {seq_no, packet}, rtx_seq_no ->
3539
rtx_packet = %Packet{

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)