Skip to content

Commit cb04a0a

Browse files
committed
Correctly generate SSRC for RTPSender
1 parent 490f43a commit cb04a0a

File tree

4 files changed

+90
-8
lines changed

4 files changed

+90
-8
lines changed

lib/ex_webrtc/peer_connection.ex

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ defmodule ExWebRTC.PeerConnection do
55

66
use GenServer
77

8+
import Bitwise
9+
810
require Logger
911

1012
alias __MODULE__.{Configuration, Demuxer}
@@ -321,6 +323,7 @@ defmodule ExWebRTC.PeerConnection do
321323
@impl true
322324
def handle_call({:add_transceiver, kind, options}, _from, state)
323325
when kind in [:audio, :video] do
326+
options = Keyword.put(options, :ssrc, generate_ssrc(state))
324327
transceiver = RTPTransceiver.new(kind, nil, state.config, options)
325328
transceivers = state.transceivers ++ [transceiver]
326329

@@ -329,6 +332,7 @@ defmodule ExWebRTC.PeerConnection do
329332

330333
@impl true
331334
def handle_call({:add_transceiver, %MediaStreamTrack{} = track, options}, _from, state) do
335+
options = Keyword.put(options, :ssrc, generate_ssrc(state))
332336
transceiver = RTPTransceiver.new(track.kind, track, state.config, options)
333337
transceivers = state.transceivers ++ [transceiver]
334338

@@ -355,12 +359,13 @@ defmodule ExWebRTC.PeerConnection do
355359
{transceivers, sender} =
356360
case free_transceiver_idx do
357361
nil ->
358-
tr = RTPTransceiver.new(kind, track, state.config, direction: :sendrecv)
362+
options = [direction: :sendrecv, ssrc: generate_ssrc(state)]
363+
tr = RTPTransceiver.new(kind, track, state.config, options)
359364
{state.transceivers ++ [tr], tr.sender}
360365

361366
idx ->
362367
tr = Enum.at(state.transceivers, idx)
363-
sender = %RTPSender{tr.sender | track: track}
368+
sender = %RTPSender{tr.sender | track: track, ssrc: generate_ssrc(state)}
364369

365370
direction =
366371
case tr.direction do
@@ -788,5 +793,20 @@ defmodule ExWebRTC.PeerConnection do
788793
%{state | conn_state: new_conn_state}
789794
end
790795

796+
defp generate_ssrc(state) do
797+
rtp_sender_ssrcs = Enum.map(state.transceivers, & &1.sender.ssrc)
798+
ssrcs = MapSet.new(Map.keys(state.demuxer.ssrc_to_mid) ++ rtp_sender_ssrcs)
799+
do_generate_ssrc(ssrcs, 200)
800+
end
801+
802+
# this is practically impossible so it's easier to raise
803+
# than to propagate the error up to the user
804+
defp do_generate_ssrc(_ssrcs, 0), do: raise("Couldn't find free SSRC")
805+
806+
defp do_generate_ssrc(ssrcs, max_attempts) do
807+
ssrc = Enum.random(0..((1 <<< 32) - 1))
808+
if ssrc in ssrcs, do: do_generate_ssrc(ssrcs, max_attempts - 1), else: ssrc
809+
end
810+
791811
defp notify(pid, msg), do: send(pid, {:ex_webrtc, self(), msg})
792812
end

lib/ex_webrtc/rtp_sender.ex

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@ defmodule ExWebRTC.RTPSender do
1515
rtp_hdr_exts: %{Extmap.extension_id() => Extmap.t()},
1616
mid: String.t() | nil,
1717
pt: non_neg_integer(),
18-
ssrc: non_neg_integer(),
18+
ssrc: non_neg_integer() | nil,
1919
last_seq_num: non_neg_integer()
2020
}
2121

2222
defstruct [:track, :codec, :rtp_hdr_exts, :mid, :pt, :ssrc, :last_seq_num]
2323

2424
@doc false
25-
@spec new(MediaStreamTrack.t() | nil, RTPCodecParameters.t() | nil, [Extmap.t()]) :: t()
26-
def new(track, codec, rtp_hdr_exts, mid \\ nil) do
25+
@spec new(
26+
MediaStreamTrack.t() | nil,
27+
RTPCodecParameters.t() | nil,
28+
[Extmap.t()],
29+
String.t() | nil,
30+
non_neg_integer | nil
31+
) :: t()
32+
def new(track, codec, rtp_hdr_exts, mid \\ nil, ssrc) do
2733
# convert to a map to be able to find extension id using extension uri
2834
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
2935
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
@@ -34,7 +40,7 @@ defmodule ExWebRTC.RTPSender do
3440
codec: codec,
3541
rtp_hdr_exts: rtp_hdr_exts,
3642
pt: pt,
37-
ssrc: 1234,
43+
ssrc: ssrc,
3844
last_seq_num: random_seq_num(),
3945
mid: mid
4046
}

lib/ex_webrtc/rtp_transceiver.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ defmodule ExWebRTC.RTPTransceiver do
6666
codecs: codecs,
6767
rtp_hdr_exts: rtp_hdr_exts,
6868
receiver: %RTPReceiver{track: track},
69-
sender: RTPSender.new(sender_track, List.first(codecs), rtp_hdr_exts)
69+
sender: RTPSender.new(sender_track, List.first(codecs), rtp_hdr_exts, options[:ssrc])
7070
}
7171
end
7272

@@ -87,7 +87,7 @@ defmodule ExWebRTC.RTPTransceiver do
8787
codecs: codecs,
8888
rtp_hdr_exts: rtp_hdr_exts,
8989
receiver: %RTPReceiver{track: track},
90-
sender: RTPSender.new(nil, List.first(codecs), rtp_hdr_exts, mid)
90+
sender: RTPSender.new(nil, List.first(codecs), rtp_hdr_exts, mid, nil)
9191
}
9292
end
9393

test/ex_webrtc/rtp_sender_test.exs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule ExWebRTC.RTPSenderTest do
2+
use ExUnit.Case, async: true
3+
4+
import Bitwise
5+
6+
alias ExRTP.Packet.Extension.SourceDescription
7+
alias ExSDP.Attribute.{Extmap, FMTP}
8+
9+
alias ExWebRTC.{MediaStreamTrack, RTPCodecParameters, RTPSender}
10+
11+
@max_seq_num (1 <<< 32) - 1
12+
@ssrc 354_947
13+
14+
@tag :debug
15+
test "send/2" do
16+
track = MediaStreamTrack.new(:audio)
17+
18+
codec = %RTPCodecParameters{
19+
payload_type: 111,
20+
mime_type: "audio/opus",
21+
clock_rate: 48_000,
22+
channels: 2,
23+
sdp_fmtp_line: %FMTP{pt: 111, minptime: 10, useinbandfec: true}
24+
}
25+
26+
rtp_hdr_exts = [%Extmap{id: 1, uri: "urn:ietf:params:rtp-hdrext:sdes:mid"}]
27+
28+
sender = RTPSender.new(track, codec, rtp_hdr_exts, "1", @ssrc)
29+
sender = %RTPSender{sender | last_seq_num: 10_000}
30+
31+
packet = ExRTP.Packet.new(<<>>, 0, 0, 0, 0)
32+
33+
{packet, sender} = RTPSender.send(sender, packet)
34+
35+
{:ok, packet} = ExRTP.Packet.decode(packet)
36+
37+
assert packet.ssrc == @ssrc
38+
assert packet.marker == false
39+
assert packet.payload_type == 111
40+
assert packet.sequence_number == 10_001
41+
# timestamp shouldn't be overwritten
42+
assert packet.timestamp == 0
43+
# there should only be one extension
44+
assert [ext] = packet.extensions
45+
assert {:ok, %{text: "1"}} = SourceDescription.from_raw(ext)
46+
47+
# check sequence number rollover and marker flag
48+
sender = %RTPSender{sender | last_seq_num: @max_seq_num}
49+
packet = ExRTP.Packet.new(<<>>, 0, 1, 0, 0, marker: true)
50+
{packet, _sender} = RTPSender.send(sender, packet)
51+
{:ok, packet} = ExRTP.Packet.decode(packet)
52+
assert packet.sequence_number == 0
53+
# marker flag shouldn't be overwritten
54+
assert packet.marker == true
55+
end
56+
end

0 commit comments

Comments
 (0)