@@ -35,11 +35,22 @@ defmodule ExWebRTC.PeerConnection.Configuration do
3535 clock_rate: 90_000 ,
3636 sdp_fmtp_line: % FMTP {
3737 pt: 98 ,
38- level_asymmetry_allowed: 1 ,
38+ level_asymmetry_allowed: true ,
3939 packetization_mode: 0 ,
4040 profile_level_id: 0x42E01F
4141 }
4242 } ,
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+ } ,
4354 % RTPCodecParameters {
4455 payload_type: 45 ,
4556 mime_type: "video/AV1" ,
@@ -252,10 +263,27 @@ defmodule ExWebRTC.PeerConnection.Configuration do
252263 |> Keyword . put ( :audio_extensions , Enum . map ( audio_extensions , fn { _ , ext } -> ext end ) )
253264 |> Keyword . put ( :video_extensions , Enum . map ( video_extensions , fn { _ , ext } -> ext end ) )
254265 |> then ( & struct ( __MODULE__ , & 1 ) )
266+ |> ensure_unique_payload_types ( )
255267 |> populate_feedbacks ( feedbacks )
256268 |> add_features ( )
257269 end
258270
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+
259287 defp add_features ( config ) do
260288 % __MODULE__ { features: features } = config
261289
@@ -405,6 +433,9 @@ defmodule ExWebRTC.PeerConnection.Configuration do
405433 config
406434 |> update_extensions ( sdp )
407435 |> 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 ( )
408439 end
409440
410441 defp update_extensions ( config , sdp ) do
@@ -425,25 +456,41 @@ defmodule ExWebRTC.PeerConnection.Configuration do
425456 defp do_update_extensions ( extensions , sdp_extensions , free_ids ) do
426457 # we replace extension ids in config to ids from the SDP
427458 # 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
429460 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 } ->
434466 [ id | rest ] = free_ids
435467 { % Extmap { ext | id: id } , rest }
436468
437- other ->
469+ { other , _id_used } ->
438470 { % Extmap { ext | id: other . id } , free_ids }
439471 end
440472 end )
441473 end
442474
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+
443490 defp update_codecs ( config , sdp ) do
444491 % __MODULE__ { audio_codecs: audio_codecs , video_codecs: video_codecs } = config
445492 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 )
447494
448495 { audio_codecs , free_pts } = do_update_codecs ( audio_codecs , sdp_codecs , free_pts )
449496 { video_codecs , _free_pts } = do_update_codecs ( video_codecs , sdp_codecs , free_pts )
@@ -452,29 +499,27 @@ defmodule ExWebRTC.PeerConnection.Configuration do
452499 end
453500
454501 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
461507 { rtxs , codecs } = Enum . split_with ( codecs , & rtx? / 1 )
462508
463509 { codecs , { free_pts , mapping } } =
464510 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 } ->
473518 [ pt | rest ] = free_pts
474519 new_codec = do_update_codec ( codec , pt )
475520 { new_codec , { rest , Map . put ( mapping , codec . payload_type , pt ) } }
476521
477- other ->
522+ { other , _pt_used } ->
478523 new_codec = do_update_codec ( codec , other . payload_type )
479524 { new_codec , { free_pts , Map . put ( mapping , codec . payload_type , other . payload_type ) } }
480525 end
@@ -486,15 +531,18 @@ defmodule ExWebRTC.PeerConnection.Configuration do
486531 % RTPCodecParameters { rtx | sdp_fmtp_line: % FMTP { fmtp | apt: Map . fetch! ( mapping , apt ) } }
487532 end )
488533 |> 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 } ->
493541 [ pt | rest ] = free_pts
494542 rtx = do_update_codec ( rtx , pt )
495543 { rtx , rest }
496544
497- other ->
545+ { other , _pt_used } ->
498546 rtx = do_update_codec ( rtx , other . payload_type )
499547 { rtx , free_pts }
500548 end
@@ -503,6 +551,38 @@ defmodule ExWebRTC.PeerConnection.Configuration do
503551 { codecs ++ rtxs , free_pts }
504552 end
505553
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+
506586 defp do_update_codec ( codec , new_pt ) do
507587 % RTPCodecParameters { rtcp_fbs: fbs , sdp_fmtp_line: fmtp } = codec
508588 new_fbs = Enum . map ( fbs , & % RTCPFeedback { & 1 | pt: new_pt } )
@@ -515,7 +595,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
515595 def intersect_codecs ( config , mline ) do
516596 # we assume that this function is called after
517597 # 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
519599 codecs =
520600 case mline . type do
521601 :audio -> config . audio_codecs
@@ -526,13 +606,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do
526606 |> SDPUtils . get_rtp_codec_parameters ( )
527607 |> Enum . flat_map ( fn sdp_codec ->
528608 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 ) )
536610 |> case do
537611 nil ->
538612 [ ]
@@ -544,6 +618,36 @@ defmodule ExWebRTC.PeerConnection.Configuration do
544618 end )
545619 end
546620
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+
547651 @ doc false
548652 @ spec intersect_extensions ( t ( ) , ExSDP.Media . t ( ) ) :: [ Extmap . t ( ) ]
549653 def intersect_extensions ( config , mline ) do
0 commit comments