-
Notifications
You must be signed in to change notification settings - Fork 8
/
configuration.ex
326 lines (279 loc) · 10.4 KB
/
configuration.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
defmodule ExWebRTC.PeerConnection.Configuration do
@moduledoc """
`ExWebRTC.PeerConnection` configuration.
"""
alias ExWebRTC.{RTPCodecParameters, SDPUtils}
alias ExSDP.Attribute.{Extmap, FMTP, RTCPFeedback}
@default_audio_codecs [
%RTPCodecParameters{
payload_type: 111,
mime_type: "audio/opus",
clock_rate: 48_000,
channels: 2,
sdp_fmtp_line: %FMTP{pt: 111, minptime: 10, useinbandfec: true}
}
]
@default_video_codecs [
%RTPCodecParameters{
payload_type: 96,
mime_type: "video/VP8",
clock_rate: 90_000
},
%RTPCodecParameters{
payload_type: 45,
mime_type: "video/AV1",
clock_rate: 90_000
},
%RTPCodecParameters{
payload_type: 98,
mime_type: "video/H264",
clock_rate: 90_000,
sdp_fmtp_line: %FMTP{
pt: 98,
level_asymmetry_allowed: true,
packetization_mode: 1,
profile_level_id: 0x42001F
}
}
]
@rtp_hdr_extensions %{
:mid => %{media_type: :all, ext: %Extmap{id: 1, uri: "urn:ietf:params:rtp-hdrext:sdes:mid"}},
:audio_level => %{
media_type: :audio,
ext: %Extmap{id: 2, uri: "urn:ietf:params:rtp-hdrext:ssrc-audio-level"}
}
}
@mandatory_audio_rtp_hdr_exts Map.new([:mid], fn ext_shortcut ->
extmap = Map.fetch!(@rtp_hdr_extensions, ext_shortcut).ext
{extmap.uri, extmap}
end)
@mandatory_video_rtp_hdr_exts Map.new([:mid], fn ext_shortcut ->
extmap = Map.fetch!(@rtp_hdr_extensions, ext_shortcut).ext
{extmap.uri, extmap}
end)
@typedoc """
Supported RTP header extensions.
"""
@type rtp_hdr_extension() :: :audio_level
@type ice_server() :: %{
optional(:credential) => String.t(),
optional(:username) => String.t(),
:urls => [String.t()] | String.t()
}
@typedoc """
Options that can be passed to `ExWebRTC.PeerConnection.start_link/1`.
* `ice_servers` - list of STUN servers to use.
TURN servers are not supported right now and will be filtered out.
* `ice_ip_filter` - filter applied when gathering local candidates
* `audio_codecs` - list of audio codecs to use.
Use `default_audio_codecs/0` to get a list of default audio codecs.
This option overrides default audio codecs.
If you wish to add codecs to default ones do
`audio_codecs: Configuration.default_audio_codecs() ++ my_codecs`
* `video_codecs` - the same as `audio_codecs` but for video.
If you wish to e.g. only use AV1, pass as video_codecs:
```
video_codecs: [
%ExWebRTC.RTPCodecParameters{
payload_type: 45,
mime_type: "video/AV1",
clock_rate: 90_000
}
]
```
* `rtp_hdr_extensions` - list of RTP header extensions to use.
MID extension is enabled by default and cannot be turned off.
If an extension can be used both for audio and video media, it
will be added to every mline.
If an extension is audio-only, it will only be added to audio mlines.
If an extension is video-only, it will only be added to video mlines.
Besides options listed above, ExWebRTC uses the following config:
* bundle_policy - max_bundle
* ice_candidate_pool_size - 0
* ice_transport_policy - all
* rtcp_mux_policy - require
This config cannot be changed.
"""
@type options() :: [
ice_servers: [ice_server()],
ice_ip_filter: (:inet.ip_address() -> boolean()),
audio_codecs: [RTPCodecParameters.t()],
video_codecs: [RTPCodecParameters.t()],
rtp_hdr_extensions: [rtp_hdr_extension()]
]
@typedoc false
@type t() :: %__MODULE__{
ice_servers: [ice_server()],
ice_ip_filter: (:inet.ip_address() -> boolean()),
audio_codecs: [RTPCodecParameters.t()],
video_codecs: [RTPCodecParameters.t()],
audio_rtp_hdr_exts: %{(uri :: String.t()) => Extmap.t()},
video_rtp_hdr_exts: %{(uri :: String.t()) => Extmap.t()}
}
defstruct ice_servers: [],
ice_ip_filter: nil,
audio_codecs: @default_audio_codecs,
video_codecs: @default_video_codecs,
audio_rtp_hdr_exts: @mandatory_audio_rtp_hdr_exts,
video_rtp_hdr_exts: @mandatory_video_rtp_hdr_exts
@doc """
Returns a list of default audio codecs.
"""
@spec default_audio_codecs() :: [RTPCodecParameters.t()]
def default_audio_codecs(), do: @default_audio_codecs
@doc """
Returns a list of default video codecs.
"""
@spec default_video_codecs() :: [RTPCodecParameters.t()]
def default_video_codecs(), do: @default_video_codecs
@doc false
@spec from_options!(options()) :: t()
def from_options!(options) do
options =
options
|> resolve_rtp_hdr_extensions()
|> add_mandatory_rtp_hdr_extensions()
# ATM, ExICE does not support relay via TURN
|> reject_turn_servers()
struct!(__MODULE__, options)
end
@doc false
@spec supported_codec?(t(), RTPCodecParameters.t()) :: boolean()
def supported_codec?(config, codec) do
# This function doesn't check if rtcp-fb is supported.
# Instead, `supported_rtcp_fb?` has to be used to filter out
# rtcp-fb that are not supported.
# TODO: this function doesn't compare fmtp at all
Enum.any?(config.audio_codecs ++ config.video_codecs, fn supported_codec ->
%{supported_codec | rtcp_fbs: codec.rtcp_fbs, sdp_fmtp_line: codec.sdp_fmtp_line} == codec
end)
end
@doc false
@spec supported_rtp_hdr_extension?(t(), Extmap.t(), :audio | :video) ::
boolean()
def supported_rtp_hdr_extension?(config, rtp_hdr_extension, media_type) do
supported_uris =
case media_type do
:audio -> Map.keys(config.audio_rtp_hdr_exts)
:video -> Map.keys(config.video_rtp_hdr_exts)
end
rtp_hdr_extension.uri in supported_uris
end
@doc false
@spec supported_rtcp_fb?(t(), RTCPFeedback.t()) :: boolean()
def supported_rtcp_fb?(_config, _rtcp_fb), do: false
@doc false
@spec update(t(), ExSDP.t()) :: t()
def update(config, sdp) do
sdp_extmaps = SDPUtils.get_extensions(sdp)
sdp_codecs = SDPUtils.get_rtp_codec_parameters(sdp)
{audio_exts, video_exts} =
update_rtp_hdr_extensions(sdp_extmaps, config.audio_rtp_hdr_exts, config.video_rtp_hdr_exts)
{audio_codecs, video_codecs} =
update_codecs(sdp_codecs, config.audio_codecs, config.video_codecs)
%__MODULE__{
config
| audio_rtp_hdr_exts: audio_exts,
video_rtp_hdr_exts: video_exts,
audio_codecs: audio_codecs,
video_codecs: video_codecs
}
end
defp update_rtp_hdr_extensions(sdp_extmaps, audio_exts, video_exts)
defp update_rtp_hdr_extensions([], audio_exts, video_exts), do: {audio_exts, video_exts}
defp update_rtp_hdr_extensions([extmap | sdp_extmaps], audio_exts, video_exts)
when is_map_key(audio_exts, extmap.uri) do
update_rtp_hdr_extensions(sdp_extmaps, Map.put(audio_exts, extmap.uri, extmap), video_exts)
end
defp update_rtp_hdr_extensions([extmap | sdp_extmaps], audio_exts, video_exts)
when is_map_key(video_exts, extmap.uri) do
update_rtp_hdr_extensions(sdp_extmaps, audio_exts, Map.put(video_exts, extmap.uri, extmap))
end
defp update_rtp_hdr_extensions([_extmap | sdp_extmaps], audio_exts, video_exts) do
update_rtp_hdr_extensions(sdp_extmaps, audio_exts, video_exts)
end
defp update_codecs(sdp_codecs, audio_codecs, video_codecs)
defp update_codecs([], audio_codecs, video_codecs) do
{audio_codecs, video_codecs}
end
defp update_codecs([sdp_codec | sdp_codecs], audio_codecs, video_codecs) do
type =
case sdp_codec.mime_type do
"audio/" <> _ -> :audio
"video/" <> _ -> :video
end
codecs = if type == :audio, do: audio_codecs, else: video_codecs
codec =
codecs
|> Enum.with_index()
|> Enum.find(fn {codec, _idx} ->
# For the time of comparision, assume the same payload type and rtcp_fbs and fmtp.
# We don't want to take into account rtcp_fbs as they can be negotiated
# i.e. we can reject those that are not supported by us.
codec = %RTPCodecParameters{
codec
| payload_type: sdp_codec.payload_type,
sdp_fmtp_line: sdp_codec.sdp_fmtp_line,
rtcp_fbs: sdp_codec.rtcp_fbs
}
codec == sdp_codec
end)
case codec do
nil ->
update_codecs(sdp_codecs, audio_codecs, video_codecs)
{codec, idx} ->
fmtp =
if codec.sdp_fmtp_line != nil do
%{codec.sdp_fmtp_line | pt: sdp_codec.payload_type}
else
nil
end
codec = %RTPCodecParameters{
codec
| payload_type: sdp_codec.payload_type,
sdp_fmtp_line: fmtp
}
codecs = List.insert_at(codecs, idx, codec)
case type do
:audio -> update_codecs(sdp_codecs, codecs, video_codecs)
:video -> update_codecs(sdp_codecs, audio_codecs, codecs)
end
end
end
defp add_mandatory_rtp_hdr_extensions(options) do
options
|> Keyword.update(:audio_rtp_hdr_exts, %{}, fn exts ->
Map.merge(exts, @mandatory_audio_rtp_hdr_exts)
end)
|> Keyword.update(:video_rtp_hdr_exts, %{}, fn exts ->
Map.merge(exts, @mandatory_video_rtp_hdr_exts)
end)
end
defp resolve_rtp_hdr_extensions(options) do
{audio_exts, video_exts} =
Keyword.get(options, :rtp_hdr_extensions, [])
|> Enum.reduce({%{}, %{}}, fn ext, {audio_exts, video_exts} ->
resolved_ext = Map.fetch!(@rtp_hdr_extensions, ext)
case resolved_ext.media_type do
:audio ->
audio_exts = Map.put(audio_exts, resolved_ext.ext.uri, resolved_ext.ext)
{audio_exts, video_exts}
:all ->
audio_exts = Map.put(audio_exts, resolved_ext.ext.uri, resolved_ext.ext)
video_exts = Map.put(video_exts, resolved_ext.ext.uri, resolved_ext.ext)
{audio_exts, video_exts}
end
end)
options
|> Keyword.put(:audio_rtp_hdr_exts, audio_exts)
|> Keyword.put(:video_rtp_hdr_exts, video_exts)
|> Keyword.delete(:rtp_hdr_extensions)
end
defp reject_turn_servers(options) do
Keyword.update(options, :ice_servers, [], fn ice_servers ->
ice_servers
|> Enum.flat_map(&List.wrap(&1.urls))
|> Enum.filter(&String.starts_with?(&1, "stun:"))
end)
end
end