@@ -35,11 +35,22 @@ defmodule ExWebRTC.PeerConnection.Configuration do
35
35
clock_rate: 90_000 ,
36
36
sdp_fmtp_line: % FMTP {
37
37
pt: 98 ,
38
- level_asymmetry_allowed: 1 ,
38
+ level_asymmetry_allowed: true ,
39
39
packetization_mode: 0 ,
40
40
profile_level_id: 0x42E01F
41
41
}
42
42
} ,
43
+ % RTPCodecParameters {
44
+ payload_type: 99 ,
45
+ mime_type: "video/H264" ,
46
+ clock_rate: 90_000 ,
47
+ sdp_fmtp_line: % FMTP {
48
+ pt: 99 ,
49
+ level_asymmetry_allowed: true ,
50
+ packetization_mode: 1 ,
51
+ profile_level_id: 0x42E01F
52
+ }
53
+ } ,
43
54
% RTPCodecParameters {
44
55
payload_type: 45 ,
45
56
mime_type: "video/AV1" ,
@@ -252,10 +263,27 @@ defmodule ExWebRTC.PeerConnection.Configuration do
252
263
|> Keyword . put ( :audio_extensions , Enum . map ( audio_extensions , fn { _ , ext } -> ext end ) )
253
264
|> Keyword . put ( :video_extensions , Enum . map ( video_extensions , fn { _ , ext } -> ext end ) )
254
265
|> then ( & struct ( __MODULE__ , & 1 ) )
266
+ |> ensure_unique_payload_types ( )
255
267
|> populate_feedbacks ( feedbacks )
256
268
|> add_features ( )
257
269
end
258
270
271
+ defp ensure_unique_payload_types ( config ) do
272
+ audio_pt = Enum . map ( config . audio_codecs , fn codec -> codec . payload_type end )
273
+
274
+ if length ( audio_pt ) != length ( Enum . uniq ( audio_pt ) ) do
275
+ raise "Payload types in audio codecs are not unique."
276
+ end
277
+
278
+ video_pt = Enum . map ( config . video_codecs , fn codec -> codec . payload_type end )
279
+
280
+ if length ( video_pt ) != length ( Enum . uniq ( video_pt ) ) do
281
+ raise "Payload types in video codecs are not unique."
282
+ end
283
+
284
+ config
285
+ end
286
+
259
287
defp add_features ( config ) do
260
288
% __MODULE__ { features: features } = config
261
289
@@ -405,6 +433,9 @@ defmodule ExWebRTC.PeerConnection.Configuration do
405
433
config
406
434
|> update_extensions ( sdp )
407
435
|> update_codecs ( sdp )
436
+ # if update went wrong (there are duplicates in payload types),
437
+ # we should never continue as this may lead to hard to debug errors
438
+ |> ensure_unique_payload_types ( )
408
439
end
409
440
410
441
defp update_extensions ( config , sdp ) do
@@ -425,25 +456,41 @@ defmodule ExWebRTC.PeerConnection.Configuration do
425
456
defp do_update_extensions ( extensions , sdp_extensions , free_ids ) do
426
457
# we replace extension ids in config to ids from the SDP
427
458
# in case we have an extension in config but not in SDP, we replace
428
- # its id to some free (not present in SDP) id, so it doesn't conflict
459
+ # its id only when it's occupied to some free (not present in SDP) id, so it doesn't conflict
429
460
Enum . map_reduce ( extensions , free_ids , fn ext , free_ids ->
430
- sdp_extensions
431
- |> Enum . find ( & ( & 1 . uri == ext . uri ) )
432
- |> case do
433
- nil ->
461
+ case find_in_sdp_rtp_extensions ( sdp_extensions , ext ) do
462
+ { nil , false } ->
463
+ { ext , free_ids }
464
+
465
+ { nil , true } ->
434
466
[ id | rest ] = free_ids
435
467
{ % Extmap { ext | id: id } , rest }
436
468
437
- other ->
469
+ { other , _id_used } ->
438
470
{ % Extmap { ext | id: other . id } , free_ids }
439
471
end
440
472
end )
441
473
end
442
474
475
+ # Searches for rtp extension in sdp rtp extensions.
476
+ # If ext is not found, id_used determines whether ext's id
477
+ # is already present in sdp_extensions.
478
+ # Otherwise, id_used can have any value.
479
+ defp find_in_sdp_rtp_extensions ( sdp_extensions , ext , id_used \\ false )
480
+ defp find_in_sdp_rtp_extensions ( [ ] , _ext , id_used ) , do: { nil , id_used }
481
+
482
+ defp find_in_sdp_rtp_extensions ( [ sdp_ext | sdp_extensions ] , ext , id_used ) do
483
+ if sdp_ext . uri == ext . uri do
484
+ { sdp_ext , id_used }
485
+ else
486
+ find_in_sdp_rtp_extensions ( sdp_extensions , ext , id_used || sdp_ext . id == ext . id )
487
+ end
488
+ end
489
+
443
490
defp update_codecs ( config , sdp ) do
444
491
% __MODULE__ { audio_codecs: audio_codecs , video_codecs: video_codecs } = config
445
492
sdp_codecs = SDPUtils . get_rtp_codec_parameters ( sdp )
446
- free_pts = get_free_payload_types ( sdp_codecs )
493
+ free_pts = get_free_payload_types ( audio_codecs ++ video_codecs ++ sdp_codecs )
447
494
448
495
{ audio_codecs , free_pts } = do_update_codecs ( audio_codecs , sdp_codecs , free_pts )
449
496
{ video_codecs , _free_pts } = do_update_codecs ( video_codecs , sdp_codecs , free_pts )
@@ -452,29 +499,27 @@ defmodule ExWebRTC.PeerConnection.Configuration do
452
499
end
453
500
454
501
defp do_update_codecs ( codecs , sdp_codecs , free_pts ) do
455
- # we replace codec payload types in config to payload types from SDP
456
- # both normal codecs and rtx (we also update apt FMTP attribute in rtxs)
457
- # other codecs that are present in config but not in SDP
458
- # are also updated with values from a pool of free payload types (not present in SDP)
459
- # to make sure they don't conflict
460
- { sdp_rtxs , sdp_codecs } = Enum . split_with ( sdp_codecs , & rtx? / 1 )
502
+ # We replace codec payload types in config to payload types from SDP
503
+ # both for normal codecs and rtx (we also update apt FMTP attribute in rtxs).
504
+ # Other codecs that are present in config but not in SDP, and their
505
+ # payload type is already present in SDP, are also updated with values
506
+ # from a pool of free payload types (not present in SDP) to make sure they don't conflict
461
507
{ rtxs , codecs } = Enum . split_with ( codecs , & rtx? / 1 )
462
508
463
509
{ codecs , { free_pts , mapping } } =
464
510
Enum . map_reduce ( codecs , { free_pts , % { } } , fn codec , { free_pts , mapping } ->
465
- sdp_codecs
466
- |> Enum . find (
467
- & ( String . downcase ( & 1 . mime_type ) == String . downcase ( codec . mime_type ) and
468
- & 1 . clock_rate == codec . clock_rate and
469
- & 1 . channels == codec . channels )
470
- )
471
- |> case do
472
- nil ->
511
+ case find_in_sdp_codecs ( sdp_codecs , codec ) do
512
+ # there is no such codec and its payload type is not used
513
+ { nil , false } ->
514
+ { codec , { free_pts , Map . put ( mapping , codec . payload_type , codec . payload_type ) } }
515
+
516
+ # there is no such codec, but its payload type is used
517
+ { nil , true } ->
473
518
[ pt | rest ] = free_pts
474
519
new_codec = do_update_codec ( codec , pt )
475
520
{ new_codec , { rest , Map . put ( mapping , codec . payload_type , pt ) } }
476
521
477
- other ->
522
+ { other , _pt_used } ->
478
523
new_codec = do_update_codec ( codec , other . payload_type )
479
524
{ new_codec , { free_pts , Map . put ( mapping , codec . payload_type , other . payload_type ) } }
480
525
end
@@ -486,15 +531,18 @@ defmodule ExWebRTC.PeerConnection.Configuration do
486
531
% RTPCodecParameters { rtx | sdp_fmtp_line: % FMTP { fmtp | apt: Map . fetch! ( mapping , apt ) } }
487
532
end )
488
533
|> Enum . map_reduce ( free_pts , fn rtx , free_pts ->
489
- sdp_rtxs
490
- |> Enum . find ( & ( & 1 . sdp_fmtp_line . apt == rtx . sdp_fmtp_line . apt ) )
491
- |> case do
492
- nil ->
534
+ case find_in_sdp_codecs ( sdp_codecs , rtx ) do
535
+ # there is no such codec and its payload type is not used
536
+ { nil , false } ->
537
+ { rtx , free_pts }
538
+
539
+ # there is no such codec, but its payload type is used
540
+ { nil , true } ->
493
541
[ pt | rest ] = free_pts
494
542
rtx = do_update_codec ( rtx , pt )
495
543
{ rtx , rest }
496
544
497
- other ->
545
+ { other , _pt_used } ->
498
546
rtx = do_update_codec ( rtx , other . payload_type )
499
547
{ rtx , free_pts }
500
548
end
@@ -503,6 +551,38 @@ defmodule ExWebRTC.PeerConnection.Configuration do
503
551
{ codecs ++ rtxs , free_pts }
504
552
end
505
553
554
+ # Searches for codec in sdp_codecs.
555
+ # If codec is not found, pt_used determines whether
556
+ # codec's payload type is already present in sdp_codecs.
557
+ # Otherwise, pt_used can have any value.
558
+ defp find_in_sdp_codecs ( sdp_codecs , codec , pt_used \\ false )
559
+
560
+ defp find_in_sdp_codecs ( [ ] , _codec , pt_used ) , do: { nil , pt_used }
561
+
562
+ defp find_in_sdp_codecs ( [ sdp_codec | sdp_codecs ] , codec , pt_used ) do
563
+ if String . ends_with? ( codec . mime_type , "/rtx" ) do
564
+ if sdp_codec . sdp_fmtp_line != nil && sdp_codec . sdp_fmtp_line . apt == codec . sdp_fmtp_line . apt do
565
+ { sdp_codec , pt_used }
566
+ else
567
+ find_in_sdp_codecs (
568
+ sdp_codecs ,
569
+ codec ,
570
+ pt_used || sdp_codec . payload_type == codec . payload_type
571
+ )
572
+ end
573
+ else
574
+ if codec_equal_soft? ( sdp_codec , codec ) do
575
+ { sdp_codec , pt_used }
576
+ else
577
+ find_in_sdp_codecs (
578
+ sdp_codecs ,
579
+ codec ,
580
+ pt_used || sdp_codec . payload_type == codec . payload_type
581
+ )
582
+ end
583
+ end
584
+ end
585
+
506
586
defp do_update_codec ( codec , new_pt ) do
507
587
% RTPCodecParameters { rtcp_fbs: fbs , sdp_fmtp_line: fmtp } = codec
508
588
new_fbs = Enum . map ( fbs , & % RTCPFeedback { & 1 | pt: new_pt } )
@@ -515,7 +595,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
515
595
def intersect_codecs ( config , mline ) do
516
596
# we assume that this function is called after
517
597
# the config was updated based on the remote SDP
518
- # so the payload types should match
598
+ # so the payload types (in codec_equal?) should match
519
599
codecs =
520
600
case mline . type do
521
601
:audio -> config . audio_codecs
@@ -526,13 +606,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
526
606
|> SDPUtils . get_rtp_codec_parameters ( )
527
607
|> Enum . flat_map ( fn sdp_codec ->
528
608
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
- )
609
+ |> Enum . find ( & codec_equal? ( & 1 , sdp_codec ) )
536
610
|> case do
537
611
nil ->
538
612
[ ]
@@ -544,6 +618,36 @@ defmodule ExWebRTC.PeerConnection.Configuration do
544
618
end )
545
619
end
546
620
621
+ # soft functions does not compare payload types
622
+ @ doc false
623
+ @ spec codec_equal? ( RTPCodecParameters . t ( ) , RTPCodecParameters . t ( ) ) :: boolean ( )
624
+ def codec_equal? ( c1 , c2 ) do
625
+ String . downcase ( c1 . mime_type ) == String . downcase ( c2 . mime_type ) and
626
+ c1 . payload_type == c2 . payload_type and
627
+ c1 . clock_rate == c2 . clock_rate and
628
+ c1 . channels == c2 . channels and fmtp_equal? ( c1 , c2 )
629
+ end
630
+
631
+ defp codec_equal_soft? ( c1 , c2 ) do
632
+ String . downcase ( c1 . mime_type ) == String . downcase ( c2 . mime_type ) and
633
+ c1 . clock_rate == c2 . clock_rate and
634
+ c1 . channels == c2 . channels and fmtp_equal_soft? ( c1 , c2 )
635
+ end
636
+
637
+ defp fmtp_equal? ( % { sdp_fmtp_line: nil } , _c2 ) , do: true
638
+ defp fmtp_equal? ( _c1 , % { sdp_fmtp_line: nil } ) , do: true
639
+ defp fmtp_equal? ( c1 , c2 ) , do: c1 . sdp_fmtp_line == c2 . sdp_fmtp_line
640
+
641
+ defp fmtp_equal_soft? ( % { sdp_fmtp_line: nil } , _c2 ) , do: true
642
+ defp fmtp_equal_soft? ( _c1 , % { sdp_fmtp_line: nil } ) , do: true
643
+
644
+ defp fmtp_equal_soft? ( c1 , c2 ) do
645
+ fmtp1 = % { c1 . sdp_fmtp_line | pt: nil }
646
+ fmtp2 = % { c2 . sdp_fmtp_line | pt: nil }
647
+
648
+ fmtp1 == fmtp2
649
+ end
650
+
547
651
@ doc false
548
652
@ spec intersect_extensions ( t ( ) , ExSDP.Media . t ( ) ) :: [ Extmap . t ( ) ]
549
653
def intersect_extensions ( config , mline ) do
0 commit comments