Skip to content

Commit

Permalink
Add ssrc attributes to the SDP
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Jan 15, 2025
1 parent e0ff731 commit 05fe01a
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 42 deletions.
50 changes: 38 additions & 12 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -855,18 +855,19 @@ defmodule ExWebRTC.PeerConnection do
@impl true
def handle_call({:add_track, %MediaStreamTrack{kind: kind} = track}, _from, state) do
# we ignore the condition that sender has never been used to send
{ssrc, rtx_ssrc} = generate_ssrcs(state)

{transceivers, sender} =
state.transceivers
|> Enum.with_index()
|> Enum.find(fn {tr, _idx} -> RTPTransceiver.can_add_track?(tr, kind) end)
|> case do
{tr, idx} ->
tr = RTPTransceiver.add_track(tr, track, ssrc, rtx_ssrc)
tr = RTPTransceiver.add_track(tr, track)
{List.replace_at(state.transceivers, idx, tr), tr.sender}

nil ->
{ssrc, rtx_ssrc} = generate_ssrcs(state)

options = [
direction: :sendrecv,
added_by_add_track: true,
Expand Down Expand Up @@ -895,8 +896,7 @@ defmodule ExWebRTC.PeerConnection do
{:reply, {:error, :invalid_track_type}, state}

{tr, idx} when tr.direction in [:sendrecv, :sendonly] ->
{ssrc, rtx_ssrc} = generate_ssrcs(state)
tr = RTPTransceiver.replace_track(tr, track, ssrc, rtx_ssrc)
tr = RTPTransceiver.replace_track(tr, track)
transceivers = List.replace_at(state.transceivers, idx, tr)
state = %{state | transceivers: transceivers}
{:reply, :ok, state}
Expand Down Expand Up @@ -1097,6 +1097,9 @@ defmodule ExWebRTC.PeerConnection do
|> Enum.find(fn {tr, _idx} -> tr.sender.track && tr.sender.track.id == track_id end)
|> case do
{tr, idx} ->
# clear extensions
packet = ExRTP.Packet.remove_extensions(packet)

{packet, state} =
case state.twcc_extension_id do
nil ->
Expand Down Expand Up @@ -1631,7 +1634,13 @@ defmodule ExWebRTC.PeerConnection do
transceivers =
sdp.media
|> Enum.reject(&SDPUtils.data_channel?/1)
|> process_mlines_remote(state.transceivers, type, state.config, state.owner)
|> process_mlines_remote(
state.transceivers,
type,
Map.keys(state.demuxer.ssrc_to_mid),
state.config,
state.owner
)

# infer our role from the remote role
dtls_role = if dtls_role in [:actpass, :passive], do: :active, else: :passive
Expand Down Expand Up @@ -1817,25 +1826,40 @@ defmodule ExWebRTC.PeerConnection do
end

# See W3C WebRTC 4.4.1.5-4.7.10.2
defp process_mlines_remote(mlines, transceivers, sdp_type, config, owner) do
defp process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner) do
mlines_idx = Enum.with_index(mlines)
do_process_mlines_remote(mlines_idx, transceivers, sdp_type, config, owner)
do_process_mlines_remote(mlines_idx, transceivers, sdp_type, demuxer_ssrcs, config, owner)
end

defp do_process_mlines_remote([], transceivers, _sdp_type, _config, _owner), do: transceivers
defp do_process_mlines_remote([], transceivers, _sdp_type, _demuxer_ssrcs, _config, _owner),
do: transceivers

defp do_process_mlines_remote([{mline, idx} | mlines], transceivers, sdp_type, config, owner) do
defp do_process_mlines_remote(
[{mline, idx} | mlines],
transceivers,
sdp_type,
demuxer_ssrcs,
config,
owner
) do
direction =
if SDPUtils.rejected?(mline),
do: :inactive,
else: SDPUtils.get_media_direction(mline) |> reverse_direction()

rtp_sender_ssrcs = Enum.map(transceivers, & &1.sender.ssrc)
ssrcs = MapSet.new(demuxer_ssrcs ++ rtp_sender_ssrcs)

ssrc = do_generate_ssrc(ssrcs)
ssrcs = MapSet.put(ssrcs, ssrc)
rtx_ssrc = do_generate_ssrc(ssrcs)

# Note: in theory we should update transceiver codecs
# after processing remote track but this shouldn't have any impact
{idx, tr} =
case find_transceiver_from_remote(transceivers, mline) do
{idx, tr} -> {idx, RTPTransceiver.update(tr, mline, config)}
nil -> {nil, RTPTransceiver.from_mline(mline, idx, config)}
nil -> {nil, RTPTransceiver.from_mline(mline, idx, ssrc, rtx_ssrc, config)}
end

tr = process_remote_track(tr, direction, owner)
Expand All @@ -1849,11 +1873,11 @@ defmodule ExWebRTC.PeerConnection do
case idx do
nil ->
transceivers = transceivers ++ [tr]
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)

idx ->
transceivers = List.replace_at(transceivers, idx, tr)
do_process_mlines_remote(mlines, transceivers, sdp_type, config, owner)
do_process_mlines_remote(mlines, transceivers, sdp_type, demuxer_ssrcs, config, owner)
end
end

Expand Down Expand Up @@ -2168,6 +2192,8 @@ defmodule ExWebRTC.PeerConnection do

# this is practically impossible so it's easier to raise
# than to propagate the error up to the user
defp do_generate_ssrc(ssrcs, max_attempts \\ 200)

defp do_generate_ssrc(_ssrcs, 0), do: raise("Couldn't find free SSRC")

defp do_generate_ssrc(ssrcs, max_attempts) do
Expand Down
86 changes: 82 additions & 4 deletions lib/ex_webrtc/rtp_sender.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ defmodule ExWebRTC.RTPSender do
mid: String.t() | nil,
pt: non_neg_integer() | nil,
rtx_pt: non_neg_integer() | nil,
ssrc: non_neg_integer() | nil,
rtx_ssrc: non_neg_integer() | nil,
# ssrc and rtx_ssrc are always present, even if there is no track
# or transceiver direction is recvonly.
# We preallocate them so they can be included in SDP when needed.
ssrc: non_neg_integer(),
rtx_ssrc: non_neg_integer(),
packets_sent: non_neg_integer(),
bytes_sent: non_neg_integer(),
retransmitted_packets_sent: non_neg_integer(),
Expand Down Expand Up @@ -68,8 +71,8 @@ defmodule ExWebRTC.RTPSender do
RTPCodecParameters.t() | nil,
[Extmap.t()],
String.t() | nil,
non_neg_integer() | nil,
non_neg_integer() | nil,
non_neg_integer(),
non_neg_integer(),
[atom()]
) :: sender()
def new(track, codec, rtx_codec, rtp_hdr_exts, mid, ssrc, rtx_ssrc, features) do
Expand Down Expand Up @@ -131,6 +134,81 @@ defmodule ExWebRTC.RTPSender do
}
end

@spec get_mline_attrs(sender()) :: [ExSDP.Attribute.t()]
def get_mline_attrs(sender) do
# Don't include track id. See RFC 8829 sec. 5.2.1
msid_attrs =
case sender.track do
nil ->
# In theory, we should do this "for each MediaStream that was associated with the transceiver"
# but web browsers (chrome, ff), include MSID even when there aren't any MediaStreams
[ExSDP.Attribute.MSID.new("-", nil)]

%MediaStreamTrack{streams: streams} ->
case Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, nil)) do
[] -> [ExSDP.Attribute.MSID.new("-", nil)]
other -> other
end
end

ssrc_attrs =
get_ssrc_attrs(sender.pt, sender.rtx_pt, sender.ssrc, sender.rtx_ssrc, sender.track)

msid_attrs ++ ssrc_attrs
end

# we didn't manage to negotiate any codec
defp get_ssrc_attrs(nil, _rtx_pt, _ssrc, _rtx_ssrc, _track) do
[]
end

# we have a codec but not rtx
defp get_ssrc_attrs(_pt, nil, ssrc, _rtx_ssrc, track) do
streams = (track && track.streams) || []

case streams do
[] ->
[%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"}]

streams ->
Enum.map(streams, fn stream ->
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
end)
end
end

# we have both codec and rtx
defp get_ssrc_attrs(_pt, _rtx_pt, ssrc, rtx_ssrc, track) do
streams = (track && track.streams) || []

case streams do
[] ->
[
%ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]},
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"},
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: "-"}
]

streams ->
{ssrc_attrs, rtx_ssrc_attrs} =
Enum.reduce(streams, {[], []}, fn stream, {ssrc_attrs, rtx_ssrc_attrs} ->
ssrc_attr = [%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}]
ssrc_attrs = [ssrc_attr | ssrc_attrs]

rtx_ssrc_attr = [
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: stream}
]

rtx_ssrc_attrs = [rtx_ssrc_attr | rtx_ssrc_attrs]

{ssrc_attrs, rtx_ssrc_attrs}
end)

fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
[fid | Enum.reverse(ssrc_attrs) ++ Enum.reverse(rtx_ssrc_attrs)]
end
end

@doc false
@spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()}
def send_packet(sender, packet, rtx?) do
Expand Down
62 changes: 36 additions & 26 deletions lib/ex_webrtc/rtp_transceiver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,14 @@ defmodule ExWebRTC.RTPTransceiver do
end

@doc false
@spec from_mline(ExSDP.Media.t(), non_neg_integer(), Configuration.t()) :: transceiver()
def from_mline(mline, mline_idx, config) do
@spec from_mline(
ExSDP.Media.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
Configuration.t()
) :: transceiver()
def from_mline(mline, mline_idx, ssrc, rtx_ssrc, config) do
header_extensions = Configuration.intersect_extensions(config, mline)
codecs = Configuration.intersect_codecs(config, mline)

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

sender =
RTPSender.new(nil, codec, codec_rtx, header_extensions, mid, nil, nil, config.features)
RTPSender.new(
nil,
codec,
codec_rtx,
header_extensions,
mid,
ssrc,
rtx_ssrc,
config.features
)

%{
id: id,
Expand Down Expand Up @@ -281,10 +296,9 @@ defmodule ExWebRTC.RTPTransceiver do
end

@doc false
@spec add_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) ::
transceiver()
def add_track(transceiver, track, ssrc, rtx_ssrc) do
sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc}
@spec add_track(transceiver(), MediaStreamTrack.t()) :: transceiver()
def add_track(transceiver, track) do
sender = %{transceiver.sender | track: track}

direction =
case transceiver.direction do
Expand All @@ -297,11 +311,9 @@ defmodule ExWebRTC.RTPTransceiver do
end

@doc false
@spec replace_track(transceiver(), MediaStreamTrack.t(), non_neg_integer(), non_neg_integer()) ::
transceiver()
def replace_track(transceiver, track, ssrc, rtx_ssrc) do
ssrc = transceiver.sender.ssrc || ssrc
sender = %{transceiver.sender | track: track, ssrc: ssrc, rtx_ssrc: rtx_ssrc}
@spec replace_track(transceiver(), MediaStreamTrack.t()) :: transceiver()
def replace_track(transceiver, track) do
sender = %{transceiver.sender | track: track}
%{transceiver | sender: sender}
end

Expand Down Expand Up @@ -526,39 +538,37 @@ defmodule ExWebRTC.RTPTransceiver do
[rtp_mapping, codec.sdp_fmtp_line, codec.rtcp_fbs]
end)

msids =
case transceiver.sender.track do
nil ->
[]

%MediaStreamTrack{id: id, streams: streams} ->
case Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, id)) do
[] -> [ExSDP.Attribute.MSID.new("-", id)]
other -> other
end
end
direction = Keyword.get(opts, :direction, transceiver.direction)

attributes =
if(Keyword.get(opts, :rtcp, false), do: [{"rtcp", "9 IN IP4 0.0.0.0"}], else: []) ++
Keyword.get(opts, :simulcast, []) ++
[
Keyword.get(opts, :direction, transceiver.direction),
direction,
{:mid, transceiver.mid},
{:ice_ufrag, Keyword.fetch!(opts, :ice_ufrag)},
{:ice_pwd, Keyword.fetch!(opts, :ice_pwd)},
{:ice_options, Keyword.fetch!(opts, :ice_options)},
{:fingerprint, Keyword.fetch!(opts, :fingerprint)},
{:setup, Keyword.fetch!(opts, :setup)},
:rtcp_mux
] ++ transceiver.header_extensions ++ msids
] ++ transceiver.header_extensions

# add sender attrs only if we send
sender_attrs =
if direction in [:sendonly, :sendrecv] do
RTPSender.get_mline_attrs(transceiver.sender)
else
[]
end

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

# RFC 3264 (6.1) + RFC 8829 (5.3.1)
Expand Down

0 comments on commit 05fe01a

Please sign in to comment.