diff --git a/lib/ex_sdp/attribute/fmtp.ex b/lib/ex_sdp/attribute/fmtp.ex index fe36402..f61cfe6 100644 --- a/lib/ex_sdp/attribute/fmtp.ex +++ b/lib/ex_sdp/attribute/fmtp.ex @@ -7,6 +7,7 @@ defmodule ExSDP.Attribute.FMTP do * H264 (not all, RFC 6184), * H265 (not all, RFC 7798) * VP8, VP9, OPUS (RFC 7587) + * AV1 (no RFC, https://aomediacodec.github.io/av1-rtp-spec/) * RTX (RFC 4588) * FLEXFEC (RFC 8627) * Telephone Events (RFC 4733) @@ -53,6 +54,10 @@ defmodule ExSDP.Attribute.FMTP do :usedtx, # VP8/9 :max_fr, + # AV1 + :profile, + :level_idx, + :tier, # RTX :apt, :rtx_time, @@ -97,6 +102,10 @@ defmodule ExSDP.Attribute.FMTP do usedtx: boolean() | nil, # VP8/9 max_fr: non_neg_integer() | nil, + # AV1 + profile: non_neg_integer() | nil, + level_idx: non_neg_integer() | nil, + tier: non_neg_integer() | nil, # RTX apt: RTPMapping.payload_type_t() | nil, rtx_time: non_neg_integer() | nil, @@ -289,6 +298,21 @@ defmodule ExSDP.Attribute.FMTP do do: {rest, %{fmtp | max_fr: value}} end + defp parse_param(["profile=" <> max_fr | rest], fmtp) do + with {:ok, value} <- Utils.parse_numeric_string(max_fr), + do: {rest, %{fmtp | profile: value}} + end + + defp parse_param(["level-idx=" <> max_fr | rest], fmtp) do + with {:ok, value} <- Utils.parse_numeric_string(max_fr), + do: {rest, %{fmtp | level_idx: value}} + end + + defp parse_param(["tier=" <> max_fr | rest], fmtp) do + with {:ok, value} <- Utils.parse_numeric_string(max_fr), + do: {rest, %{fmtp | tier: value}} + end + defp parse_param(["apt=" <> value | rest], fmtp) do with {:ok, value} <- Utils.parse_payload_type(value), do: {rest, %{fmtp | apt: value}} end @@ -397,6 +421,10 @@ defimpl String.Chars, for: ExSDP.Attribute.FMTP do # RTX Serializer.maybe_serialize("apt", fmtp.apt), Serializer.maybe_serialize("rtx-time", fmtp.rtx_time), + # AV1 + Serializer.maybe_serialize("profile", fmtp.profile), + Serializer.maybe_serialize("level-idx", fmtp.level_idx), + Serializer.maybe_serialize("tier", fmtp.tier), # FLEXFEC Serializer.maybe_serialize("repair-window", fmtp.repair_window), # Telephone Events diff --git a/test/ex_sdp/attribute/fmtp_test.exs b/test/ex_sdp/attribute/fmtp_test.exs index 4bd1cf1..b39d559 100644 --- a/test/ex_sdp/attribute/fmtp_test.exs +++ b/test/ex_sdp/attribute/fmtp_test.exs @@ -137,6 +137,19 @@ defmodule ExSDP.Attribute.FMTPTest do assert {:ok, expected} == FMTP.parse(fmtp) end + test "parses proper fmtp with av1 parameters" do + fmtp = "98 profile=2; level-idx=8; tier=1" + + expected = %FMTP{ + pt: 98, + profile: 2, + level_idx: 8, + tier: 1 + } + + assert {:ok, expected} == FMTP.parse(fmtp) + end + test "returns an error when DTMF tone is too big" do fmtp = "100 0-15,256" assert {:error, :invalid_dtmf_tones} = FMTP.parse(fmtp) diff --git a/test/webrtc_test.exs b/test/webrtc_test.exs index ec66ac8..50bd981 100644 --- a/test/webrtc_test.exs +++ b/test/webrtc_test.exs @@ -165,7 +165,7 @@ defmodule ExSDP.WebRTCTest do a=fmtp:63 111/111 a=ssrc:10136459 cname:fsp95kJbiDm+35qA a=ssrc:10136459 msid:040ad92b-583f-44d2-93e8-de4d40ac49ec 730bdafa-23f3-4111-85b4-757a666d462c - m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 + m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 c=IN IP4 0.0.0.0 a=ice-ufrag:zPE+ a=ice-pwd:5uuTJKfWTxRYyERtPlvUeKsU @@ -198,6 +198,19 @@ defmodule ExSDP.WebRTCTest do a=fmtp:98 profile-id=0 a=rtpmap:99 rtx/90000 a=fmtp:99 apt=98 + a=rtpmap:100 AV1/90000 + a=rtcp-fb:100 goog-remb + a=rtcp-fb:100 transport-cc + a=rtcp-fb:100 ccm fir + a=rtcp-fb:100 nack + a=rtcp-fb:100 nack pli + a=rtpmap:101 AV1/90000 + a=rtcp-fb:101 goog-remb + a=rtcp-fb:101 transport-cc + a=rtcp-fb:101 ccm fir + a=rtcp-fb:101 nack + a=rtcp-fb:101 nack pli + a=fmtp:101 profile=1 a=ssrc-group:FID 1984225447 2555509203 a=ssrc:1984225447 cname:fsp95kJbiDm+35qA a=ssrc:1984225447 msid:b5f40727-fa04-44db-9f4c-35c5fe8f2c3a a68d0021-2492-4f05-a211-e6b9b19c57ff @@ -226,6 +239,8 @@ defmodule ExSDP.WebRTCTest do assert %ExSDP.Attribute.RTCPFeedback{pt: 98, feedback_type: :pli} in video.attributes assert %ExSDP.Attribute.FMTP{pt: 99, apt: 98} in video.attributes + assert %ExSDP.Attribute.FMTP{pt: 101, profile: 1} in video.attributes + assert video.attributes |> Enum.at(-5) == %ExSDP.Attribute.SSRCGroup{ semantics: "FID", ssrcs: [1_984_225_447, 2_555_509_203]