Skip to content

Commit 9b8b624

Browse files
committed
Ensure FMTP compatibility
1 parent 3dac56e commit 9b8b624

File tree

5 files changed

+76
-54
lines changed

5 files changed

+76
-54
lines changed

lib/ex_webrtc/peer_connection/configuration.ex

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,33 @@ defmodule ExWebRTC.PeerConnection.Configuration do
2424
]
2525

2626
@default_video_codecs [
27-
%RTPCodecParameters{
28-
payload_type: 96,
29-
mime_type: "video/VP8",
30-
clock_rate: 90_000
31-
},
3227
%RTPCodecParameters{
3328
payload_type: 98,
3429
mime_type: "video/H264",
3530
clock_rate: 90_000,
3631
sdp_fmtp_line: %FMTP{
3732
pt: 98,
38-
level_asymmetry_allowed: 1,
33+
level_asymmetry_allowed: true,
3934
packetization_mode: 0,
4035
profile_level_id: 0x42E01F
4136
}
4237
},
38+
%RTPCodecParameters{
39+
payload_type: 99,
40+
mime_type: "video/H264",
41+
clock_rate: 90_000,
42+
sdp_fmtp_line: %FMTP{
43+
pt: 99,
44+
level_asymmetry_allowed: true,
45+
packetization_mode: 1,
46+
profile_level_id: 0x42E01F
47+
}
48+
},
49+
%RTPCodecParameters{
50+
payload_type: 96,
51+
mime_type: "video/VP8",
52+
clock_rate: 90_000
53+
},
4354
%RTPCodecParameters{
4455
payload_type: 45,
4556
mime_type: "video/AV1",
@@ -466,7 +477,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
466477
|> Enum.find(
467478
&(String.downcase(&1.mime_type) == String.downcase(codec.mime_type) and
468479
&1.clock_rate == codec.clock_rate and
469-
&1.channels == codec.channels)
480+
&1.channels == codec.channels and fmtp_equal_soft?(codec, &1))
470481
)
471482
|> case do
472483
nil ->
@@ -526,13 +537,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
526537
|> SDPUtils.get_rtp_codec_parameters()
527538
|> Enum.flat_map(fn sdp_codec ->
528539
codecs
529-
|> Enum.find(
530-
# as of now, we ignore sdp_fmtp_line
531-
&(String.downcase(&1.mime_type) == String.downcase(sdp_codec.mime_type) and
532-
&1.payload_type == sdp_codec.payload_type and
533-
&1.clock_rate == sdp_codec.clock_rate and
534-
&1.channels == sdp_codec.channels)
535-
)
540+
|> Enum.find(&codec_equal?(&1, sdp_codec))
536541
|> case do
537542
nil ->
538543
[]
@@ -544,6 +549,30 @@ defmodule ExWebRTC.PeerConnection.Configuration do
544549
end)
545550
end
546551

552+
@doc false
553+
@spec codec_equal?(RTPCodecParameters.t(), RTPCodecParameters.t()) :: boolean()
554+
def codec_equal?(c1, c2) do
555+
# as of now, we ignore sdp_fmtp_line
556+
String.downcase(c1.mime_type) == String.downcase(c2.mime_type) and
557+
c1.payload_type == c2.payload_type and
558+
c1.clock_rate == c2.clock_rate and
559+
c1.channels == c2.channels and fmtp_equal?(c1, c2)
560+
end
561+
562+
defp fmtp_equal?(%{sdp_fmtp_line: nil}, _c2), do: true
563+
defp fmtp_equal?(_c1, %{sdp_fmtp_line: nil}), do: true
564+
defp fmtp_equal?(c1, c2), do: c1.sdp_fmtp_line == c2.sdp_fmtp_line
565+
566+
defp fmtp_equal_soft?(%{sdp_fmtp_line: nil}, _c2), do: true
567+
defp fmtp_equal_soft?(_c1, %{sdp_fmtp_line: nil}), do: true
568+
569+
defp fmtp_equal_soft?(c1, c2) do
570+
fmtp1 = %{c1.sdp_fmtp_line | pt: nil}
571+
fmtp2 = %{c2.sdp_fmtp_line | pt: nil}
572+
573+
fmtp1 == fmtp2
574+
end
575+
547576
@doc false
548577
@spec intersect_extensions(t(), ExSDP.Media.t()) :: [Extmap.t()]
549578
def intersect_extensions(config, mline) do

lib/ex_webrtc/rtp_sender.ex

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule ExWebRTC.RTPSender do
44
"""
55
require Logger
66

7+
alias ExWebRTC.PeerConnection.Configuration
78
alias ExRTCP.Packet.{TransportFeedback.NACK, PayloadFeedback.PLI}
89
alias ExWebRTC.{MediaStreamTrack, RTPCodecParameters, Utils}
910
alias ExSDP.Attribute.Extmap
@@ -22,8 +23,6 @@ defmodule ExWebRTC.RTPSender do
2223
codecs: [RTPCodecParameters.t()],
2324
rtp_hdr_exts: %{Extmap.extension_id() => Extmap.t()},
2425
mid: String.t() | nil,
25-
pt: non_neg_integer() | nil,
26-
rtx_pt: non_neg_integer() | nil,
2726
# ssrc and rtx_ssrc are always present, even if there is no track,
2827
# or transceiver direction is recvonly.
2928
# We preallocate them so they can be included in SDP when needed.
@@ -81,15 +80,8 @@ defmodule ExWebRTC.RTPSender do
8180
# convert to a map to be able to find extension id using extension uri
8281
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
8382

84-
# We always only take one codec to avoid ambiguity when assigning payload type for RTP packets.
85-
# In other case, if PeerConnection negotiated multiple codecs,
86-
# user would have to pass RTP codec when sending RTP packets,
87-
# or assign payload type on their own.
88-
{codec, rtx_codec} = get_default_codec(codecs)
89-
9083
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
91-
pt = if codec != nil, do: codec.payload_type, else: nil
92-
rtx_pt = if rtx_codec != nil, do: rtx_codec.payload_type, else: nil
84+
{codec, rtx_codec} = get_default_codec(codecs)
9385

9486
%{
9587
id: Utils.generate_id(),
@@ -98,8 +90,6 @@ defmodule ExWebRTC.RTPSender do
9890
rtx_codec: rtx_codec,
9991
codecs: codecs,
10092
rtp_hdr_exts: rtp_hdr_exts,
101-
pt: pt,
102-
rtx_pt: rtx_pt,
10393
ssrc: ssrc,
10494
rtx_ssrc: rtx_ssrc,
10595
mid: mid,
@@ -126,42 +116,37 @@ defmodule ExWebRTC.RTPSender do
126116

127117
# Keep already selected codec if it is still supported.
128118
# Otherwise, clear it and wait until user sets it again.
129-
codec = if sender.codec in codecs, do: sender.codec, else: nil
119+
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
120+
codec = if supported?(codecs, sender.codec), do: sender.codec, else: nil
130121
rtx_codec = codec && find_associated_rtx_codec(codecs, codec)
131122

132123
log_codec_change(sender, codec, codecs)
133124
log_rtx_codec_change(sender, rtx_codec, codecs)
134125

135-
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
136-
pt = if codec != nil, do: codec.payload_type, else: nil
137-
rtx_pt = if rtx_codec != nil, do: rtx_codec.payload_type, else: nil
138-
139126
%{
140127
sender
141128
| mid: mid,
142129
codec: codec,
143130
rtx_codec: rtx_codec,
144131
codecs: codecs,
145-
rtp_hdr_exts: rtp_hdr_exts,
146-
pt: pt,
147-
rtx_pt: rtx_pt
132+
rtp_hdr_exts: rtp_hdr_exts
148133
}
149134
end
150135

151136
defp log_codec_change(%{codec: codec} = sender, nil, neg_codecs) when codec != nil do
152-
Logger.debug("""
137+
Logger.warning("""
153138
Unselecting RTP sender codec as it is no longer supported by the remote side.
154139
Call set_sender_codec again passing supported codec.
155-
Codec: #{inspect(sender.codec)}
156-
Currently negotiated codecs: #{inspect(neg_codecs)}
140+
Codec: #{inspect(sender.codec, pretty: true)}
141+
Currently negotiated codecs: #{inspect(neg_codecs, pretty: true)}
157142
""")
158143
end
159144

160145
defp log_codec_change(_sender, _codec, _neg_codecs), do: :ok
161146

162147
defp log_rtx_codec_change(%{rtx_codec: rtx_codec} = sender, nil, neg_codecs)
163148
when rtx_codec != nil do
164-
Logger.debug("""
149+
Logger.warning("""
165150
Unselecting RTP sender codec as it is no longer supported by the remote side.
166151
Call set_sender_codec again passing supported codec.
167152
Codec: #{inspect(sender.codec)}
@@ -186,18 +171,18 @@ defmodule ExWebRTC.RTPSender do
186171
end
187172

188173
ssrc_attrs =
189-
get_ssrc_attrs(sender.pt, sender.rtx_pt, sender.ssrc, sender.rtx_ssrc, sender.track)
174+
get_ssrc_attrs(sender.codec, sender.rtx_codec, sender.ssrc, sender.rtx_ssrc, sender.track)
190175

191176
msid_attrs ++ ssrc_attrs
192177
end
193178

194179
# we didn't manage to negotiate any codec
195-
defp get_ssrc_attrs(nil, _rtx_pt, _ssrc, _rtx_ssrc, _track) do
180+
defp get_ssrc_attrs(nil, _rtx_codec, _ssrc, _rtx_ssrc, _track) do
196181
[]
197182
end
198183

199-
# we have a codec but not rtx
200-
defp get_ssrc_attrs(_pt, nil, ssrc, _rtx_ssrc, track) do
184+
# we have a codec but not rtx codec
185+
defp get_ssrc_attrs(_codec, nil, ssrc, _rtx_ssrc, track) do
201186
streams = (track && track.streams) || []
202187

203188
case streams do
@@ -211,8 +196,8 @@ defmodule ExWebRTC.RTPSender do
211196
end
212197
end
213198

214-
# we have both codec and rtx
215-
defp get_ssrc_attrs(_pt, _rtx_pt, ssrc, rtx_ssrc, track) do
199+
# we have both codec and rtx codec
200+
defp get_ssrc_attrs(_codec, _rtx_codec, ssrc, rtx_ssrc, track) do
216201
streams = (track && track.streams) || []
217202

218203
fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
@@ -251,7 +236,7 @@ defmodule ExWebRTC.RTPSender do
251236
@doc false
252237
@spec set_codec(sender(), RTPCodecParameters.t()) :: {:ok, sender()} | {:error, term()}
253238
def set_codec(sender, codec) do
254-
if not rtx?(codec) and supported?(sender, codec) and same_clock_rate?(sender, codec) do
239+
if not rtx?(codec) and supported?(sender.codecs, codec) and same_clock_rate?(sender, codec) do
255240
rtx_codec = find_associated_rtx_codec(sender.codecs, codec)
256241
sender = %{sender | codec: codec, rtx_codec: rtx_codec}
257242
{:ok, sender}
@@ -261,7 +246,13 @@ defmodule ExWebRTC.RTPSender do
261246
end
262247

263248
defp rtx?(codec), do: String.ends_with?(codec.mime_type, "rtx")
264-
defp supported?(sender, codec), do: codec in sender.codecs
249+
250+
defp supported?(neg_codecs, codec) do
251+
Enum.find(neg_codecs, fn s_codec ->
252+
Configuration.codec_equal?(s_codec, codec) and
253+
MapSet.new(s_codec.rtcp_fbs) == MapSet.new(codec.rtcp_fbs)
254+
end) != nil
255+
end
265256

266257
# As long as report recorder is not initialized i.e. we have not sent any RTP packet,
267258
# allow for codec changes. Once we start sending RTP packets, require the same clock rate.
@@ -271,12 +262,10 @@ defmodule ExWebRTC.RTPSender do
271262
@doc false
272263
@spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()}
273264
def send_packet(%{rtx_codec: nil} = sender, _packet, true) do
274-
Logger.warning("Tried to retransmit packet but there is no selected RTX codec. Ignoring.")
275265
{<<>>, sender}
276266
end
277267

278268
def send_packet(%{codec: nil} = sender, _packet, false) do
279-
Logger.warning("Tried to send packet but there is no selected codec. Ignoring.")
280269
{<<>>, sender}
281270
end
282271

@@ -297,9 +286,9 @@ defmodule ExWebRTC.RTPSender do
297286
def do_send_packet(sender, packet, rtx?) do
298287
{pt, ssrc} =
299288
if rtx? do
300-
{sender.rtx_pt, sender.rtx_ssrc}
289+
{sender.rtx_codec.payload_type, sender.rtx_ssrc}
301290
else
302-
{sender.pt, sender.ssrc}
291+
{sender.codec.payload_type, sender.ssrc}
303292
end
304293

305294
packet = %{packet | payload_type: pt, ssrc: ssrc}

lib/ex_webrtc/rtp_transceiver.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,11 @@ defmodule ExWebRTC.RTPTransceiver do
282282
{:ok, transceiver()} | {:error, term()}
283283
def set_sender_codec(transceiver, codec) do
284284
case RTPSender.set_codec(transceiver.sender, codec) do
285-
{:ok, sender} -> {:ok, %{transceiver | sender: sender}}
286-
{:error, _reason} = error -> error
285+
{:ok, sender} ->
286+
{:ok, %{transceiver | sender: sender}}
287+
288+
{:error, _reason} = error ->
289+
error
287290
end
288291
end
289292

test/ex_webrtc/peer_sdp_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ defmodule ExWebRTC.PeerSDPTest do
33

44
alias ExWebRTC.{MediaStreamTrack, PeerConnection, RTPTransceiver, SessionDescription}
55

6-
for peer <- ["chromium", "firefox", "obs"] do
6+
for peer <- ["obs"] do
7+
@tag :debug
78
test "#{peer} SDP offer is functional and maintains tracks" do
89
{:ok, pc} = PeerConnection.start_link()
910

test/ex_webrtc/renegotiation_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ defmodule ExWebRTC.RenegotiationTest do
155155
test "add and remove tracks in a loop" do
156156
# Simulate the most basic videoconference scenario
157157
# where both sides join with audio and video,
158-
# start screensharing and remove screensharing.
158+
# start screensharing and remove screensharing.
159159
# pc1 adds audio and video tracks
160160
# pc2 adds audio and video tracks
161161
# pc1 adds screenshare track

0 commit comments

Comments
 (0)