Skip to content

Commit 61e3a04

Browse files
committed
Ensure FMTP compatibility
1 parent 3dac56e commit 61e3a04

File tree

3 files changed

+67
-51
lines changed

3 files changed

+67
-51
lines changed

lib/ex_webrtc/peer_connection/configuration.ex

Lines changed: 43 additions & 15 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 ->
@@ -515,7 +526,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
515526
def intersect_codecs(config, mline) do
516527
# we assume that this function is called after
517528
# the config was updated based on the remote SDP
518-
# so the payload types should match
529+
# so the payload types (in codec_equal?) should match
519530
codecs =
520531
case mline.type do
521532
:audio -> config.audio_codecs
@@ -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,29 @@ 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+
String.downcase(c1.mime_type) == String.downcase(c2.mime_type) and
556+
c1.payload_type == c2.payload_type and
557+
c1.clock_rate == c2.clock_rate and
558+
c1.channels == c2.channels and fmtp_equal?(c1, c2)
559+
end
560+
561+
defp fmtp_equal?(%{sdp_fmtp_line: nil}, _c2), do: true
562+
defp fmtp_equal?(_c1, %{sdp_fmtp_line: nil}), do: true
563+
defp fmtp_equal?(c1, c2), do: c1.sdp_fmtp_line == c2.sdp_fmtp_line
564+
565+
defp fmtp_equal_soft?(%{sdp_fmtp_line: nil}, _c2), do: true
566+
defp fmtp_equal_soft?(_c1, %{sdp_fmtp_line: nil}), do: true
567+
568+
defp fmtp_equal_soft?(c1, c2) do
569+
fmtp1 = %{c1.sdp_fmtp_line | pt: nil}
570+
fmtp2 = %{c2.sdp_fmtp_line | pt: nil}
571+
572+
fmtp1 == fmtp2
573+
end
574+
547575
@doc false
548576
@spec intersect_extensions(t(), ExSDP.Media.t()) :: [Extmap.t()]
549577
def intersect_extensions(config, mline) do

lib/ex_webrtc/rtp_sender.ex

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule ExWebRTC.RTPSender do
55
require Logger
66

77
alias ExRTCP.Packet.{TransportFeedback.NACK, PayloadFeedback.PLI}
8-
alias ExWebRTC.{MediaStreamTrack, RTPCodecParameters, Utils}
8+
alias ExWebRTC.{MediaStreamTrack, RTPCodecParameters, Utils, PeerConnection.Configuration}
99
alias ExSDP.Attribute.Extmap
1010
alias __MODULE__.{NACKResponder, ReportRecorder}
1111

@@ -22,8 +22,6 @@ defmodule ExWebRTC.RTPSender do
2222
codecs: [RTPCodecParameters.t()],
2323
rtp_hdr_exts: %{Extmap.extension_id() => Extmap.t()},
2424
mid: String.t() | nil,
25-
pt: non_neg_integer() | nil,
26-
rtx_pt: non_neg_integer() | nil,
2725
# ssrc and rtx_ssrc are always present, even if there is no track,
2826
# or transceiver direction is recvonly.
2927
# We preallocate them so they can be included in SDP when needed.
@@ -81,15 +79,8 @@ defmodule ExWebRTC.RTPSender do
8179
# convert to a map to be able to find extension id using extension uri
8280
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
8381

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-
9082
# 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
83+
{codec, rtx_codec} = get_default_codec(codecs)
9384

9485
%{
9586
id: Utils.generate_id(),
@@ -98,8 +89,6 @@ defmodule ExWebRTC.RTPSender do
9889
rtx_codec: rtx_codec,
9990
codecs: codecs,
10091
rtp_hdr_exts: rtp_hdr_exts,
101-
pt: pt,
102-
rtx_pt: rtx_pt,
10392
ssrc: ssrc,
10493
rtx_ssrc: rtx_ssrc,
10594
mid: mid,
@@ -126,30 +115,25 @@ defmodule ExWebRTC.RTPSender do
126115

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

132122
log_codec_change(sender, codec, codecs)
133123
log_rtx_codec_change(sender, rtx_codec, codecs)
134124

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-
139125
%{
140126
sender
141127
| mid: mid,
142128
codec: codec,
143129
rtx_codec: rtx_codec,
144130
codecs: codecs,
145-
rtp_hdr_exts: rtp_hdr_exts,
146-
pt: pt,
147-
rtx_pt: rtx_pt
131+
rtp_hdr_exts: rtp_hdr_exts
148132
}
149133
end
150134

151135
defp log_codec_change(%{codec: codec} = sender, nil, neg_codecs) when codec != nil do
152-
Logger.debug("""
136+
Logger.warning("""
153137
Unselecting RTP sender codec as it is no longer supported by the remote side.
154138
Call set_sender_codec again passing supported codec.
155139
Codec: #{inspect(sender.codec)}
@@ -161,7 +145,7 @@ defmodule ExWebRTC.RTPSender do
161145

162146
defp log_rtx_codec_change(%{rtx_codec: rtx_codec} = sender, nil, neg_codecs)
163147
when rtx_codec != nil do
164-
Logger.debug("""
148+
Logger.warning("""
165149
Unselecting RTP sender codec as it is no longer supported by the remote side.
166150
Call set_sender_codec again passing supported codec.
167151
Codec: #{inspect(sender.codec)}
@@ -186,18 +170,18 @@ defmodule ExWebRTC.RTPSender do
186170
end
187171

188172
ssrc_attrs =
189-
get_ssrc_attrs(sender.pt, sender.rtx_pt, sender.ssrc, sender.rtx_ssrc, sender.track)
173+
get_ssrc_attrs(sender.codec, sender.rtx_codec, sender.ssrc, sender.rtx_ssrc, sender.track)
190174

191175
msid_attrs ++ ssrc_attrs
192176
end
193177

194178
# we didn't manage to negotiate any codec
195-
defp get_ssrc_attrs(nil, _rtx_pt, _ssrc, _rtx_ssrc, _track) do
179+
defp get_ssrc_attrs(nil, _rtx_codec, _ssrc, _rtx_ssrc, _track) do
196180
[]
197181
end
198182

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

203187
case streams do
@@ -211,8 +195,8 @@ defmodule ExWebRTC.RTPSender do
211195
end
212196
end
213197

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

218202
fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
@@ -251,7 +235,7 @@ defmodule ExWebRTC.RTPSender do
251235
@doc false
252236
@spec set_codec(sender(), RTPCodecParameters.t()) :: {:ok, sender()} | {:error, term()}
253237
def set_codec(sender, codec) do
254-
if not rtx?(codec) and supported?(sender, codec) and same_clock_rate?(sender, codec) do
238+
if not rtx?(codec) and supported?(sender.codecs, codec) and same_clock_rate?(sender, codec) do
255239
rtx_codec = find_associated_rtx_codec(sender.codecs, codec)
256240
sender = %{sender | codec: codec, rtx_codec: rtx_codec}
257241
{:ok, sender}
@@ -261,7 +245,13 @@ defmodule ExWebRTC.RTPSender do
261245
end
262246

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

266256
# As long as report recorder is not initialized i.e. we have not sent any RTP packet,
267257
# allow for codec changes. Once we start sending RTP packets, require the same clock rate.
@@ -271,12 +261,10 @@ defmodule ExWebRTC.RTPSender do
271261
@doc false
272262
@spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()}
273263
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.")
275264
{<<>>, sender}
276265
end
277266

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

@@ -297,9 +285,9 @@ defmodule ExWebRTC.RTPSender do
297285
def do_send_packet(sender, packet, rtx?) do
298286
{pt, ssrc} =
299287
if rtx? do
300-
{sender.rtx_pt, sender.rtx_ssrc}
288+
{sender.rtx_codec.payload_type, sender.rtx_ssrc}
301289
else
302-
{sender.pt, sender.ssrc}
290+
{sender.codec.payload_type, sender.ssrc}
303291
end
304292

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

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)