From f03b4c21cac3d2cb39d080bec1e5a9ff6f358b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Wed, 15 Jan 2025 14:28:43 +0100 Subject: [PATCH] Add tests --- lib/ex_webrtc/rtp_sender.ex | 13 ++- lib/ex_webrtc/rtp_sender/nack_responder.ex | 8 +- test/ex_webrtc/rtp_sender_test.exs | 92 +++++++++++++++++++--- test/ex_webrtc/rtp_transceiver_test.exs | 85 ++++++++++++++++++++ 4 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 test/ex_webrtc/rtp_transceiver_test.exs diff --git a/lib/ex_webrtc/rtp_sender.ex b/lib/ex_webrtc/rtp_sender.ex index fb9ea6d..8f65454 100644 --- a/lib/ex_webrtc/rtp_sender.ex +++ b/lib/ex_webrtc/rtp_sender.ex @@ -21,7 +21,7 @@ defmodule ExWebRTC.RTPSender do mid: String.t() | nil, pt: non_neg_integer() | nil, rtx_pt: non_neg_integer() | nil, - # ssrc and rtx_ssrc are always present, even if there is no track + # ssrc and rtx_ssrc are always present, even if there is no track, # or transceiver direction is recvonly. # We preallocate them so they can be included in SDP when needed. ssrc: non_neg_integer(), @@ -140,8 +140,8 @@ defmodule ExWebRTC.RTPSender do msid_attrs = case sender.track do nil -> - # In theory, we should do this "for each MediaStream that was associated with the transceiver" - # but web browsers (chrome, ff), include MSID even when there aren't any MediaStreams + # In theory, we should do this "for each MediaStream that was associated with the transceiver", + # but web browsers (chrome, ff) include MSID even when there aren't any MediaStreams [ExSDP.Attribute.MSID.new("-", nil)] %MediaStreamTrack{streams: streams} -> @@ -192,13 +192,10 @@ defmodule ExWebRTC.RTPSender do streams -> {ssrc_attrs, rtx_ssrc_attrs} = Enum.reduce(streams, {[], []}, fn stream, {ssrc_attrs, rtx_ssrc_attrs} -> - ssrc_attr = [%ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream}] + ssrc_attr = %ExSDP.Attribute.SSRC{id: ssrc, attribute: "msid", value: stream} ssrc_attrs = [ssrc_attr | ssrc_attrs] - rtx_ssrc_attr = [ - %ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: stream} - ] - + rtx_ssrc_attr = %ExSDP.Attribute.SSRC{id: rtx_ssrc, attribute: "msid", value: stream} rtx_ssrc_attrs = [rtx_ssrc_attr | rtx_ssrc_attrs] {ssrc_attrs, rtx_ssrc_attrs} diff --git a/lib/ex_webrtc/rtp_sender/nack_responder.ex b/lib/ex_webrtc/rtp_sender/nack_responder.ex index c999342..ea091b4 100644 --- a/lib/ex_webrtc/rtp_sender/nack_responder.ex +++ b/lib/ex_webrtc/rtp_sender/nack_responder.ex @@ -28,8 +28,12 @@ defmodule ExWebRTC.RTPSender.NACKResponder do {packets, seq_no} = seq_nos - |> Enum.map(fn seq_no -> {seq_no, Map.get(responder.packets, rem(seq_no, @max_packets))} end) - |> Enum.filter(fn {seq_no, packet} -> packet != nil and packet.sequence_number == seq_no end) + |> Enum.map(fn seq_no -> + {seq_no, Map.get(responder.packets, rem(seq_no, @max_packets))} + end) + |> Enum.filter(fn {seq_no, packet} -> + packet != nil and packet.sequence_number == seq_no + end) # ssrc will be assigned by the sender |> Enum.map_reduce(responder.seq_no, fn {seq_no, packet}, rtx_seq_no -> rtx_packet = %Packet{ diff --git a/test/ex_webrtc/rtp_sender_test.exs b/test/ex_webrtc/rtp_sender_test.exs index b6eae87..2e9ae1e 100644 --- a/test/ex_webrtc/rtp_sender_test.exs +++ b/test/ex_webrtc/rtp_sender_test.exs @@ -9,20 +9,28 @@ defmodule ExWebRTC.RTPSenderTest do @ssrc 354_947 @rtx_ssrc 123_455 - setup do - track = MediaStreamTrack.new(:audio) - - codec = %RTPCodecParameters{ - payload_type: 111, - mime_type: "audio/opus", - clock_rate: 48_000, - channels: 2, - sdp_fmtp_line: %FMTP{pt: 111, minptime: 10, useinbandfec: true} + @rtp_hdr_exts [%Extmap{id: 1, uri: "urn:ietf:params:rtp-hdrext:sdes:mid"}] + + @codec %RTPCodecParameters{ + payload_type: 96, + mime_type: "video/VP8", + clock_rate: 90_000 + } + + @rtx_codec %ExWebRTC.RTPCodecParameters{ + payload_type: 124, + mime_type: "video/rtx", + clock_rate: 90_000, + sdp_fmtp_line: %ExSDP.Attribute.FMTP{ + pt: 124, + apt: 96 } + } - rtp_hdr_exts = [%Extmap{id: 1, uri: "urn:ietf:params:rtp-hdrext:sdes:mid"}] + setup do + track = MediaStreamTrack.new(:video) - sender = RTPSender.new(track, codec, nil, rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) + sender = RTPSender.new(track, @codec, nil, @rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) %{sender: sender} end @@ -36,7 +44,7 @@ defmodule ExWebRTC.RTPSenderTest do assert packet.ssrc == @ssrc assert packet.marker == false - assert packet.payload_type == 111 + assert packet.payload_type == 96 # timestamp and sequence number shouldn't be overwritten assert packet.timestamp == 0 assert packet.sequence_number == 0 @@ -53,6 +61,66 @@ defmodule ExWebRTC.RTPSenderTest do assert packet.marker == true end + describe "get_mline_attrs/1" do + test "without rtx" do + stream_id = MediaStreamTrack.generate_stream_id() + track = MediaStreamTrack.new(:video, [stream_id]) + + sender = RTPSender.new(track, @codec, nil, @rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) + + assert [ + %ExSDP.Attribute.MSID{id: ^stream_id, app_data: nil}, + %ExSDP.Attribute.SSRC{id: @ssrc, attribute: "msid", value: ^stream_id} + ] = RTPSender.get_mline_attrs(sender) + end + + test "with rtx" do + stream_id = MediaStreamTrack.generate_stream_id() + track = MediaStreamTrack.new(:video, [stream_id]) + + sender = RTPSender.new(track, @codec, @rtx_codec, @rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) + + assert [ + %ExSDP.Attribute.MSID{id: ^stream_id, app_data: nil}, + %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [@ssrc, @rtx_ssrc]}, + %ExSDP.Attribute.SSRC{id: @ssrc, attribute: "msid", value: ^stream_id}, + %ExSDP.Attribute.SSRC{id: @rtx_ssrc, attribute: "msid", value: ^stream_id} + ] = RTPSender.get_mline_attrs(sender) + end + + test "without media stream" do + track = MediaStreamTrack.new(:video) + + sender = RTPSender.new(track, @codec, @rtx_codec, @rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) + + assert [ + %ExSDP.Attribute.MSID{id: "-", app_data: nil}, + %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [@ssrc, @rtx_ssrc]}, + %ExSDP.Attribute.SSRC{id: @ssrc, attribute: "msid", value: "-"}, + %ExSDP.Attribute.SSRC{id: @rtx_ssrc, attribute: "msid", value: "-"} + ] = RTPSender.get_mline_attrs(sender) + end + + test "with multiple media streams" do + s1_id = MediaStreamTrack.generate_stream_id() + s2_id = MediaStreamTrack.generate_stream_id() + + track = MediaStreamTrack.new(:video, [s1_id, s2_id]) + + sender = RTPSender.new(track, @codec, @rtx_codec, @rtp_hdr_exts, "1", @ssrc, @rtx_ssrc, []) + + assert [ + %ExSDP.Attribute.MSID{id: ^s1_id, app_data: nil}, + %ExSDP.Attribute.MSID{id: ^s2_id, app_data: nil}, + %ExSDP.Attribute.SSRCGroup{semantics: "FID", ssrcs: [@ssrc, @rtx_ssrc]}, + %ExSDP.Attribute.SSRC{id: @ssrc, attribute: "msid", value: ^s1_id}, + %ExSDP.Attribute.SSRC{id: @ssrc, attribute: "msid", value: ^s2_id}, + %ExSDP.Attribute.SSRC{id: @rtx_ssrc, attribute: "msid", value: ^s1_id}, + %ExSDP.Attribute.SSRC{id: @rtx_ssrc, attribute: "msid", value: ^s2_id} + ] = RTPSender.get_mline_attrs(sender) + end + end + test "get_stats/2", %{sender: sender} do timestamp = System.os_time(:millisecond) payload = <<1, 2, 3>> diff --git a/test/ex_webrtc/rtp_transceiver_test.exs b/test/ex_webrtc/rtp_transceiver_test.exs new file mode 100644 index 0000000..253899d --- /dev/null +++ b/test/ex_webrtc/rtp_transceiver_test.exs @@ -0,0 +1,85 @@ +defmodule ExWebRTC.RTPTransceiverTest do + use ExUnit.Case, async: true + + alias ExWebRTC.{MediaStreamTrack, PeerConnection, RTPTransceiver, Utils} + + {:ok, pc} = PeerConnection.start_link() + @config PeerConnection.get_configuration(pc) + :ok = PeerConnection.close(pc) + + @ssrc 1234 + @rtx_ssrc 2345 + + @track MediaStreamTrack.new(:video, [MediaStreamTrack.generate_stream_id()]) + + {_key, cert} = ExDTLS.generate_key_cert() + + @opts [ + ice_ufrag: "ice_ufrag", + ice_pwd: "ice_pwd", + ice_options: "trickle", + fingerprint: {:sha256, Utils.hex_dump(cert)}, + setup: :actpass + ] + + describe "to_offer_mline/1" do + test "with sendrecv direction" do + tr = RTPTransceiver.new(:video, @track, @config, ssrc: @ssrc, rtx_ssrc: @rtx_ssrc) + test_sender_attrs(tr) + end + + test "with sendonly direction" do + tr = + RTPTransceiver.new(:video, @track, @config, + ssrc: @ssrc, + rtx_ssrc: @rtx_ssrc, + direction: :sendonly + ) + + test_sender_attrs(tr) + end + + test "with recvonly direction" do + tr = + RTPTransceiver.new(:video, @track, @config, + ssrc: @ssrc, + rtx_ssrc: @rtx_ssrc, + direction: :recvonly + ) + + test_no_sender_attrs(tr) + end + + test "with inactive direction" do + tr = + RTPTransceiver.new(:video, @track, @config, + ssrc: @ssrc, + rtx_ssrc: @rtx_ssrc, + direction: :inactive + ) + + test_no_sender_attrs(tr) + end + end + + defp test_sender_attrs(tr) do + mline = RTPTransceiver.to_offer_mline(tr, @opts) + + # Assert rtp sender attributes are present. + # Their exact values are checked in rtp_sender_test.exs. + assert [%ExSDP.Attribute.MSID{}] = ExSDP.get_attributes(mline, ExSDP.Attribute.MSID) + assert [%ExSDP.Attribute.SSRCGroup{}] = ExSDP.get_attributes(mline, ExSDP.Attribute.SSRCGroup) + + assert [%ExSDP.Attribute.SSRC{}, %ExSDP.Attribute.SSRC{}] = + ExSDP.get_attributes(mline, ExSDP.Attribute.SSRC) + end + + defp test_no_sender_attrs(tr) do + mline = RTPTransceiver.to_offer_mline(tr, @opts) + + # assert there are no sender attributes + assert [] == ExSDP.get_attributes(mline, ExSDP.Attribute.MSID) + assert [] == ExSDP.get_attributes(mline, ExSDP.Attribute.SSRCGroup) + assert [] == ExSDP.get_attributes(mline, ExSDP.Attribute.SSRC) + end +end