Skip to content

Commit 54824be

Browse files
committed
Set sender codecs after negotiation
1 parent 3a9b73b commit 54824be

File tree

5 files changed

+307
-216
lines changed

5 files changed

+307
-216
lines changed

lib/ex_webrtc/rtp_sender.ex

Lines changed: 24 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defmodule ExWebRTC.RTPSender do
1919
track: MediaStreamTrack.t() | nil,
2020
codec: RTPCodecParameters.t() | nil,
2121
rtx_codec: RTPCodecParameters.t() | nil,
22+
selected_codec: RTPCodecParameters.t() | nil,
2223
codecs: [RTPCodecParameters.t()],
2324
rtp_hdr_exts: %{Extmap.extension_id() => Extmap.t()},
2425
mid: String.t() | nil,
@@ -66,32 +67,20 @@ defmodule ExWebRTC.RTPSender do
6667
end
6768

6869
@doc false
69-
@spec new(
70-
MediaStreamTrack.t() | nil,
71-
[RTPCodecParameters.t()],
72-
[Extmap.t()],
73-
String.t() | nil,
74-
non_neg_integer(),
75-
non_neg_integer(),
76-
[atom()]
77-
) :: sender()
78-
def new(track, codecs, rtp_hdr_exts, mid, ssrc, rtx_ssrc, features) do
79-
# convert to a map to be able to find extension id using extension uri
80-
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
81-
82-
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
83-
{codec, rtx_codec} = get_default_codec(codecs)
84-
70+
@spec new(MediaStreamTrack.t() | nil, non_neg_integer(), non_neg_integer(), [atom()]) ::
71+
sender()
72+
def new(track, ssrc, rtx_ssrc, features) do
8573
%{
8674
id: Utils.generate_id(),
8775
track: track,
88-
codec: codec,
89-
rtx_codec: rtx_codec,
90-
codecs: codecs,
91-
rtp_hdr_exts: rtp_hdr_exts,
76+
codec: nil,
77+
rtx_codec: nil,
78+
selected_codec: nil,
79+
codecs: [],
80+
rtp_hdr_exts: %{},
9281
ssrc: ssrc,
9382
rtx_ssrc: rtx_ssrc,
94-
mid: mid,
83+
mid: nil,
9584
packets_sent: 0,
9685
bytes_sent: 0,
9786
retransmitted_packets_sent: 0,
@@ -113,11 +102,16 @@ defmodule ExWebRTC.RTPSender do
113102
# convert to a map to be able to find extension id using extension uri
114103
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
115104

116-
# Keep already selected codec if it is still supported.
117-
# Otherwise, clear it and wait until user sets it again.
118-
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
119-
codec = if supported?(codecs, sender.codec), do: sender.codec, else: nil
120-
rtx_codec = codec && find_associated_rtx_codec(codecs, codec)
105+
{codec, rtx_codec} =
106+
if sender.codec == nil and sender.selected_codec == nil do
107+
get_default_codec(codecs)
108+
else
109+
# Keep already selected codec if it is still supported.
110+
# Otherwise, clear it and wait until user sets it again.
111+
codec = if supported?(codecs, sender.selected_codec), do: sender.selected_codec, else: nil
112+
rtx_codec = codec && find_associated_rtx_codec(codecs, codec)
113+
{codec, rtx_codec}
114+
end
121115

122116
log_codec_change(sender, codec, codecs)
123117
log_rtx_codec_change(sender, rtx_codec, codecs)
@@ -127,6 +121,7 @@ defmodule ExWebRTC.RTPSender do
127121
| mid: mid,
128122
codec: codec,
129123
rtx_codec: rtx_codec,
124+
selected_codec: codec || sender.selected_codec,
130125
codecs: codecs,
131126
rtp_hdr_exts: rtp_hdr_exts
132127
}
@@ -135,7 +130,7 @@ defmodule ExWebRTC.RTPSender do
135130
defp log_codec_change(%{codec: codec} = sender, nil, neg_codecs) when codec != nil do
136131
Logger.warning("""
137132
Unselecting RTP sender codec as it is no longer supported by the remote side.
138-
Call set_sender_codec again passing supported codec.
133+
Call set_sender_codec passing supported codec.
139134
Codec: #{inspect(sender.codec)}
140135
Currently negotiated codecs: #{inspect(neg_codecs)}
141136
""")
@@ -147,97 +142,20 @@ defmodule ExWebRTC.RTPSender do
147142
when rtx_codec != nil do
148143
Logger.warning("""
149144
Unselecting RTP sender RTX codec as it is no longer supported by the remote side.
150-
Call set_sender_codec again passing supported codec.
145+
Call set_sender_codec passing supported codec.
151146
Codec: #{inspect(sender.rtx_codec)}
152147
Currently negotiated codecs: #{inspect(neg_codecs)}
153148
""")
154149
end
155150

156151
defp log_rtx_codec_change(_sender, _rtx_codec, _neg_codecs), do: :ok
157152

158-
@spec get_mline_attrs(sender()) :: [ExSDP.Attribute.t()]
159-
def get_mline_attrs(sender) do
160-
# Don't include track id. See RFC 8829 sec. 5.2.1
161-
msid_attrs =
162-
case sender.track do
163-
%MediaStreamTrack{streams: streams} when streams != [] ->
164-
Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, nil))
165-
166-
_other ->
167-
# In theory, we should do this "for each MediaStream that was associated with the transceiver",
168-
# but web browsers (chrome, ff) include MSID even when there aren't any MediaStreams
169-
[ExSDP.Attribute.MSID.new("-", nil)]
170-
end
171-
172-
ssrc_attrs =
173-
get_ssrc_attrs(sender.codec, sender.rtx_codec, sender.ssrc, sender.rtx_ssrc, sender.track)
174-
175-
msid_attrs ++ ssrc_attrs
176-
end
177-
178-
# we didn't manage to negotiate any codec
179-
defp get_ssrc_attrs(nil, _rtx_codec, _ssrc, _rtx_ssrc, _track) do
180-
[]
181-
end
182-
183-
# we have a codec but not rtx codec
184-
defp get_ssrc_attrs(_codec, nil, ssrc, _rtx_ssrc, track) do
185-
streams = (track && track.streams) || []
186-
187-
case streams do
188-
[] ->
189-
[%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"}]
190-
191-
streams ->
192-
Enum.map(streams, fn stream ->
193-
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
194-
end)
195-
end
196-
end
197-
198-
# we have both codec and rtx codec
199-
defp get_ssrc_attrs(_codec, _rtx_codec, ssrc, rtx_ssrc, track) do
200-
streams = (track && track.streams) || []
201-
202-
fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
203-
204-
ssrc_attrs =
205-
case streams do
206-
[] ->
207-
[
208-
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"},
209-
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: "-"}
210-
]
211-
212-
streams ->
213-
{ssrc_attrs, rtx_ssrc_attrs} =
214-
Enum.reduce(streams, {[], []}, fn stream, {ssrc_attrs, rtx_ssrc_attrs} ->
215-
ssrc_attr = %ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
216-
ssrc_attrs = [ssrc_attr | ssrc_attrs]
217-
218-
rtx_ssrc_attr = %ExSDP.Attribute.SSRC{
219-
id: rtx_ssrc,
220-
attribute: "msid",
221-
value: stream
222-
}
223-
224-
rtx_ssrc_attrs = [rtx_ssrc_attr | rtx_ssrc_attrs]
225-
226-
{ssrc_attrs, rtx_ssrc_attrs}
227-
end)
228-
229-
Enum.reverse(ssrc_attrs) ++ Enum.reverse(rtx_ssrc_attrs)
230-
end
231-
232-
[fid | ssrc_attrs]
233-
end
234-
235153
@doc false
236154
@spec set_codec(sender(), RTPCodecParameters.t()) :: {:ok, sender()} | {:error, term()}
237155
def set_codec(sender, codec) do
238156
if not rtx?(codec) and supported?(sender.codecs, codec) and same_clock_rate?(sender, codec) do
239157
rtx_codec = find_associated_rtx_codec(sender.codecs, codec)
240-
sender = %{sender | codec: codec, rtx_codec: rtx_codec}
158+
sender = %{sender | codec: codec, rtx_codec: rtx_codec, selected_codec: codec}
241159
{:ok, sender}
242160
else
243161
{:error, :invalid_codec}

lib/ex_webrtc/rtp_transceiver.ex

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,7 @@ defmodule ExWebRTC.RTPTransceiver do
137137

138138
receiver = RTPReceiver.new(track, codecs, header_extensions, config.features)
139139

140-
sender =
141-
RTPSender.new(
142-
sender_track,
143-
codecs,
144-
header_extensions,
145-
nil,
146-
options[:ssrc],
147-
options[:rtx_ssrc],
148-
config.features
149-
)
140+
sender = RTPSender.new(sender_track, options[:ssrc], options[:rtx_ssrc], config.features)
150141

151142
%{
152143
id: id,
@@ -202,16 +193,8 @@ defmodule ExWebRTC.RTPTransceiver do
202193

203194
receiver = RTPReceiver.new(track, codecs, header_extensions, config.features)
204195

205-
sender =
206-
RTPSender.new(
207-
nil,
208-
codecs,
209-
header_extensions,
210-
mid,
211-
ssrc,
212-
rtx_ssrc,
213-
config.features
214-
)
196+
sender = RTPSender.new(nil, ssrc, rtx_ssrc, config.features)
197+
sender = RTPSender.update(sender, mid, codecs, header_extensions)
215198

216199
%{
217200
id: id,
@@ -561,7 +544,19 @@ defmodule ExWebRTC.RTPTransceiver do
561544
# add sender attrs only if we send
562545
sender_attrs =
563546
if direction in [:sendonly, :sendrecv] do
564-
RTPSender.get_mline_attrs(transceiver.sender)
547+
# sender codecs are set when negotiation completes,
548+
# hence, to generate the first offer, we need to use transceiver codecs
549+
codecs =
550+
if transceiver.sender.codecs == [],
551+
do: transceiver.codecs,
552+
else: transceiver.sender.codecs
553+
554+
get_sender_attrs(
555+
transceiver.sender.track,
556+
codecs,
557+
transceiver.sender.ssrc,
558+
transceiver.sender.rtx_ssrc
559+
)
565560
else
566561
[]
567562
end
@@ -575,6 +570,89 @@ defmodule ExWebRTC.RTPTransceiver do
575570
|> ExSDP.add_attributes(attributes ++ media_formats ++ sender_attrs)
576571
end
577572

573+
@doc false
574+
defp get_sender_attrs(track, codecs, ssrc, rtx_ssrc) do
575+
# Don't include track id. See RFC 8829 sec. 5.2.1
576+
msid_attrs =
577+
case track do
578+
%MediaStreamTrack{streams: streams} when streams != [] ->
579+
Enum.map(streams, &ExSDP.Attribute.MSID.new(&1, nil))
580+
581+
_other ->
582+
# In theory, we should do this "for each MediaStream that was associated with the transceiver",
583+
# but web browsers (chrome, ff) include MSID even when there aren't any MediaStreams
584+
[ExSDP.Attribute.MSID.new("-", nil)]
585+
end
586+
587+
ssrc_attrs = get_ssrc_attrs(codecs, ssrc, rtx_ssrc, track)
588+
589+
msid_attrs ++ ssrc_attrs
590+
end
591+
592+
defp get_ssrc_attrs(codecs, ssrc, rtx_ssrc, track) do
593+
codec = Enum.any?(codecs, fn codec -> not String.ends_with?(codec.mime_type, "/rtx") end)
594+
rtx_codec = Enum.any?(codecs, fn codec -> String.ends_with?(codec.mime_type, "/rtx") end)
595+
596+
do_get_ssrc_attrs(codec, rtx_codec, ssrc, rtx_ssrc, track)
597+
end
598+
599+
# we didn't manage to negotiate any codec
600+
defp do_get_ssrc_attrs(false, _rtx_codec, _ssrc, _rtx_ssrc, _track) do
601+
[]
602+
end
603+
604+
# we have a codec but not rtx codec
605+
defp do_get_ssrc_attrs(_codec, false, ssrc, _rtx_ssrc, track) do
606+
streams = (track && track.streams) || []
607+
608+
case streams do
609+
[] ->
610+
[%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"}]
611+
612+
streams ->
613+
Enum.map(streams, fn stream ->
614+
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
615+
end)
616+
end
617+
end
618+
619+
# we have both codec and rtx codec
620+
defp do_get_ssrc_attrs(_codec, _rtx_codec, ssrc, rtx_ssrc, track) do
621+
streams = (track && track.streams) || []
622+
623+
fid = %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [ssrc, rtx_ssrc]}
624+
625+
ssrc_attrs =
626+
case streams do
627+
[] ->
628+
[
629+
%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: "-"},
630+
%ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: "-"}
631+
]
632+
633+
streams ->
634+
{ssrc_attrs, rtx_ssrc_attrs} =
635+
Enum.reduce(streams, {[], []}, fn stream, {ssrc_attrs, rtx_ssrc_attrs} ->
636+
ssrc_attr = %ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}
637+
ssrc_attrs = [ssrc_attr | ssrc_attrs]
638+
639+
rtx_ssrc_attr = %ExSDP.Attribute.SSRC{
640+
id: rtx_ssrc,
641+
attribute: "msid",
642+
value: stream
643+
}
644+
645+
rtx_ssrc_attrs = [rtx_ssrc_attr | rtx_ssrc_attrs]
646+
647+
{ssrc_attrs, rtx_ssrc_attrs}
648+
end)
649+
650+
Enum.reverse(ssrc_attrs) ++ Enum.reverse(rtx_ssrc_attrs)
651+
end
652+
653+
[fid | ssrc_attrs]
654+
end
655+
578656
# RFC 3264 (6.1) + RFC 8829 (5.3.1)
579657
# AFAIK one of the cases should always match
580658
# bc we won't assign/create an inactive transceiver to i.e. sendonly mline

test/ex_webrtc/peer_connection_test.exs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,20 +220,30 @@ defmodule ExWebRTC.PeerConnectionTest do
220220
end
221221

222222
test "set_sender_codec/3" do
223-
{:ok, pid} = PeerConnection.start_link()
224-
{:ok, tr} = PeerConnection.add_transceiver(pid, :video)
223+
{:ok, pc1} = PeerConnection.start_link()
224+
{:ok, pc2} = PeerConnection.start_link()
225+
{:ok, tr} = PeerConnection.add_transceiver(pc1, :video)
225226

226227
{_rtx_codecs, media_codecs} = Utils.split_rtx_codecs(tr.codecs)
227228

228-
assert :ok = PeerConnection.set_sender_codec(pid, tr.sender.id, List.last(media_codecs))
229-
230229
assert {:error, :invalid_sender_id} =
231-
PeerConnection.set_sender_codec(pid, "invalid_id", List.last(media_codecs))
230+
PeerConnection.set_sender_codec(pc1, "invalid_id", List.last(media_codecs))
232231

233-
:ok = PeerConnection.set_transceiver_direction(pid, tr.id, :recvonly)
232+
:ok = PeerConnection.set_transceiver_direction(pc1, tr.id, :recvonly)
234233

235234
assert {:error, :invalid_transceiver_direction} =
236-
PeerConnection.set_sender_codec(pid, tr.sender.id, List.last(media_codecs))
235+
PeerConnection.set_sender_codec(pc1, tr.sender.id, List.last(media_codecs))
236+
237+
# reset transceiver direction
238+
:ok = PeerConnection.set_transceiver_direction(pc1, tr.id, :sendrecv)
239+
240+
# setting codec before negotiation should fail
241+
assert {:error, :invalid_codec} =
242+
PeerConnection.set_sender_codec(pc1, tr.sender.id, List.last(media_codecs))
243+
244+
:ok = negotiate(pc1, pc2)
245+
246+
assert :ok = PeerConnection.set_sender_codec(pc1, tr.sender.id, List.last(media_codecs))
237247
end
238248

239249
test "send_rtp/4" do

0 commit comments

Comments
 (0)