Skip to content

Commit 1b36845

Browse files
authored
Add h265 fmtp fields (#35)
* Add H265 FMTP fields * Improve tests * Fix typos and error code
1 parent 09597b5 commit 1b36845

File tree

5 files changed

+155
-9
lines changed

5 files changed

+155
-9
lines changed

lib/ex_sdp/attribute/fmtp.ex

+69-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule ExSDP.Attribute.FMTP do
55
Parameters for:
66
77
* H264 (not all, RFC 6184),
8+
* H265 (not all, RFC 7798)
89
* VP8, VP9, OPUS (RFC 7587)
910
* RTX (RFC 4588)
1011
* FLEXFEC (RFC 8627)
@@ -30,6 +31,15 @@ defmodule ExSDP.Attribute.FMTP do
3031
:max_dpb,
3132
:max_br,
3233
:sprop_parameter_sets,
34+
# H265
35+
:profile_space,
36+
:profile_id,
37+
:tier_flag,
38+
:level_id,
39+
:interop_constraints,
40+
:sprop_vps,
41+
:sprop_sps,
42+
:sprop_pps,
3343
# OPUS
3444
:maxaveragebitrate,
3545
:maxplaybackrate,
@@ -42,7 +52,6 @@ defmodule ExSDP.Attribute.FMTP do
4252
:useinbandfec,
4353
:usedtx,
4454
# VP8/9
45-
:profile_id,
4655
:max_fr,
4756
# RTX
4857
:apt,
@@ -66,6 +75,15 @@ defmodule ExSDP.Attribute.FMTP do
6675
level_asymmetry_allowed: boolean() | nil,
6776
packetization_mode: non_neg_integer() | nil,
6877
sprop_parameter_sets: %{sps: binary(), pps: binary()} | nil,
78+
# H265
79+
profile_space: non_neg_integer() | nil,
80+
profile_id: non_neg_integer() | nil,
81+
tier_flag: non_neg_integer() | nil,
82+
level_id: non_neg_integer() | nil,
83+
interop_constraints: non_neg_integer() | nil,
84+
sprop_vps: [binary()] | nil,
85+
sprop_sps: [binary()] | nil,
86+
sprop_pps: [binary()] | nil,
6987
# OPUS
7088
maxaveragebitrate: non_neg_integer() | nil,
7189
maxplaybackrate: non_neg_integer() | nil,
@@ -78,7 +96,6 @@ defmodule ExSDP.Attribute.FMTP do
7896
useinbandfec: boolean() | nil,
7997
usedtx: boolean() | nil,
8098
# VP8/9
81-
profile_id: non_neg_integer() | nil,
8299
max_fr: non_neg_integer() | nil,
83100
# RTX
84101
apt: RTPMapping.payload_type_t() | nil,
@@ -103,6 +120,7 @@ defmodule ExSDP.Attribute.FMTP do
103120
"""
104121
@type reason ::
105122
:invalid_fmtp
123+
| :invalid_ps
106124
| :invalid_pt
107125
| :invalid_sprop_parameter_sets
108126
| :string_nan
@@ -153,6 +171,46 @@ defmodule ExSDP.Attribute.FMTP do
153171
do: {rest, %{fmtp | sprop_parameter_sets: value}}
154172
end
155173

174+
defp parse_param(["profile-space=" <> profile_space | rest], fmtp) do
175+
with {:ok, value} <- Utils.parse_numeric_string(profile_space),
176+
do: {rest, %{fmtp | profile_space: value}}
177+
end
178+
179+
defp parse_param(["profile-id=" <> profile_id | rest], fmtp) do
180+
with {:ok, value} <- Utils.parse_numeric_string(profile_id),
181+
do: {rest, %{fmtp | profile_id: value}}
182+
end
183+
184+
defp parse_param(["tier-flag=" <> tier_flag | rest], fmtp) do
185+
with {:ok, value} <- Utils.parse_numeric_bool_string(tier_flag),
186+
do: {rest, %{fmtp | tier_flag: value}}
187+
end
188+
189+
defp parse_param(["level-id=" <> level_id | rest], fmtp) do
190+
with {:ok, value} <- Utils.parse_numeric_string(level_id),
191+
do: {rest, %{fmtp | level_id: value}}
192+
end
193+
194+
defp parse_param(["interop-constraints=" <> interop_constraints | rest], fmtp) do
195+
with {:ok, value} <- Utils.parse_numeric_hex_string(interop_constraints),
196+
do: {rest, %{fmtp | interop_constraints: value}}
197+
end
198+
199+
defp parse_param(["sprop-vps=" <> vps | rest], fmtp) do
200+
with {:ok, value} <- Utils.parse_sprop_ps(vps),
201+
do: {rest, %{fmtp | sprop_vps: value}}
202+
end
203+
204+
defp parse_param(["sprop-sps=" <> sps | rest], fmtp) do
205+
with {:ok, value} <- Utils.parse_sprop_ps(sps),
206+
do: {rest, %{fmtp | sprop_sps: value}}
207+
end
208+
209+
defp parse_param(["sprop-pps=" <> pps | rest], fmtp) do
210+
with {:ok, value} <- Utils.parse_sprop_ps(pps),
211+
do: {rest, %{fmtp | sprop_pps: value}}
212+
end
213+
156214
defp parse_param(["max-mbps=" <> max_mbps | rest], fmtp) do
157215
with {:ok, value} <- Utils.parse_numeric_string(max_mbps),
158216
do: {rest, %{fmtp | max_mbps: value}}
@@ -231,11 +289,6 @@ defmodule ExSDP.Attribute.FMTP do
231289
do: {rest, %{fmtp | max_fr: value}}
232290
end
233291

234-
defp parse_param(["profile-id=" <> profile_id | rest], fmtp) do
235-
with {:ok, value} <- Utils.parse_numeric_string(profile_id),
236-
do: {rest, %{fmtp | profile_id: value}}
237-
end
238-
239292
defp parse_param(["apt=" <> value | rest], fmtp) do
240293
with {:ok, value} <- Utils.parse_payload_type(value), do: {rest, %{fmtp | apt: value}}
241294
end
@@ -319,6 +372,15 @@ defimpl String.Chars, for: ExSDP.Attribute.FMTP do
319372
Serializer.maybe_serialize("level-asymmetry-allowed", fmtp.level_asymmetry_allowed),
320373
Serializer.maybe_serialize("packetization-mode", fmtp.packetization_mode),
321374
Serializer.maybe_serialize("sprop-parameter-sets", fmtp.sprop_parameter_sets),
375+
# H265
376+
Serializer.maybe_serialize("profile-space", fmtp.profile_space),
377+
Serializer.maybe_serialize("profile-id", fmtp.profile_id),
378+
Serializer.maybe_serialize("tier-flag", fmtp.tier_flag),
379+
Serializer.maybe_serialize("level-id", fmtp.level_id),
380+
Serializer.maybe_serialize_hex("interop-constraints", fmtp.interop_constraints),
381+
Serializer.maybe_serialize_base64("sprop-vps", fmtp.sprop_vps),
382+
Serializer.maybe_serialize_base64("sprop-sps", fmtp.sprop_sps),
383+
Serializer.maybe_serialize_base64("sprop-pps", fmtp.sprop_pps),
322384
# OPUS
323385
Serializer.maybe_serialize("maxaveragebitrate", fmtp.maxaveragebitrate),
324386
Serializer.maybe_serialize("maxplaybackrate", fmtp.maxplaybackrate),
@@ -331,7 +393,6 @@ defimpl String.Chars, for: ExSDP.Attribute.FMTP do
331393
Serializer.maybe_serialize("useinbandfec", fmtp.useinbandfec),
332394
Serializer.maybe_serialize("usedtx", fmtp.usedtx),
333395
# VP8/9
334-
Serializer.maybe_serialize("profile-id", fmtp.profile_id),
335396
Serializer.maybe_serialize("max-fr", fmtp.max_fr),
336397
# RTX
337398
Serializer.maybe_serialize("apt", fmtp.apt),

lib/ex_sdp/encryption.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defmodule ExSDP.Encryption do
1313
@enforce_keys [:method]
1414
defstruct @enforce_keys ++ [:key]
1515

16-
@type methods :: :prompt | :base64 | :clear | :prompt | :uri
16+
@type methods :: :prompt | :base64 | :clear | :uri
1717

1818
@type t :: %__MODULE__{
1919
method: methods(),

lib/ex_sdp/serializer.ex

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ defmodule ExSDP.Serializer do
4848
def maybe_serialize_hex(type, value),
4949
do: "#{type}=#{Integer.to_string(value, 16) |> String.downcase()}"
5050

51+
@spec maybe_serialize_base64(String.t(), nil | [binary()]) :: binary()
52+
def maybe_serialize_base64(_type, nil), do: ""
53+
def maybe_serialize_base64(_type, []), do: ""
54+
55+
def maybe_serialize_base64(type, values) when is_list(values),
56+
do: "#{type}=" <> Enum.map_join(values, ",", &Base.encode64(&1))
57+
5158
@spec maybe_serialize_list([String.t()] | nil, String.t()) :: String.t()
5259
def maybe_serialize_list([], _sep), do: ""
5360
def maybe_serialize_list(nil, _sep), do: ""

lib/ex_sdp/utils.ex

+12
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,16 @@ defmodule ExSDP.Utils do
6363
{:error, :invalid_sprop_parameter_sets}
6464
end
6565
end
66+
67+
@spec parse_sprop_ps(binary()) :: {:error, :invalid_ps} | {:ok, [binary()]}
68+
def parse_sprop_ps(pss) do
69+
pss
70+
|> String.split(",")
71+
|> Enum.reduce_while({:ok, []}, fn parameter_set, {:ok, acc} ->
72+
case Base.decode64(parameter_set) do
73+
{:ok, decoded} -> {:cont, {:ok, acc ++ [decoded]}}
74+
:error -> {:halt, {:error, :invalid_ps}}
75+
end
76+
end)
77+
end
6678
end

test/ex_sdp/attribute/fmtp_test.exs

+66
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,39 @@ defmodule ExSDP.Attribute.FMTPTest do
104104
assert {:ok, expected} == FMTP.parse(fmtp)
105105
end
106106

107+
test "parses fmtp with sprop-*ps" do
108+
fmtp =
109+
"96 profile-space=0;profile-id=1;tier-flag=0;level-id=150;interop-constraints=B00000000000;" <>
110+
"sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJAAAAAQ==;" <>
111+
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqAB4CACHH+KrTuiS7IAAAAB,QgEBAWAAAAMAsAAAAwAAAwCZoAHgIAIcWNrkkUvzcBAQEAg=;" <>
112+
"sprop-pps=RAHAcvCcFAobJA==,RAHA8vA7NA=="
113+
114+
expected = %FMTP{
115+
pt: 96,
116+
profile_space: 0,
117+
profile_id: 1,
118+
tier_flag: false,
119+
level_id: 150,
120+
interop_constraints: 0xB00000000000,
121+
sprop_vps: [
122+
<<64, 1, 12, 1, 255, 255, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 150, 172, 9, 0,
123+
0, 0, 1>>
124+
],
125+
sprop_sps: [
126+
<<66, 1, 1, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 150, 160, 1, 224, 32, 2, 28,
127+
127, 138, 173, 59, 162, 75, 178, 0, 0, 0, 1>>,
128+
<<66, 1, 1, 1, 96, 0, 0, 3, 0, 176, 0, 0, 3, 0, 0, 3, 0, 153, 160, 1, 224, 32, 2, 28,
129+
88, 218, 228, 145, 75, 243, 112, 16, 16, 16, 8>>
130+
],
131+
sprop_pps: [
132+
<<68, 1, 192, 114, 240, 156, 20, 10, 27, 36>>,
133+
<<68, 1, 192, 242, 240, 59, 52>>
134+
]
135+
}
136+
137+
assert {:ok, expected} == FMTP.parse(fmtp)
138+
end
139+
107140
test "returns an error when DTMF tone is too big" do
108141
fmtp = "100 0-15,256"
109142
assert {:error, :invalid_dtmf_tones} = FMTP.parse(fmtp)
@@ -176,5 +209,38 @@ defmodule ExSDP.Attribute.FMTPTest do
176209

177210
assert "#{fmtp}" == expected
178211
end
212+
213+
test "serializes FMTP with sprop-*ps" do
214+
expected =
215+
"fmtp:96 profile-space=0;profile-id=1;tier-flag=0;level-id=150;interop-constraints=b00000000000;" <>
216+
"sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJAAAAAQ==;" <>
217+
"sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqAB4CACHH+KrTuiS7IAAAAB,QgEBAWAAAAMAsAAAAwAAAwCZoAHgIAIcWNrkkUvzcBAQEAg=;" <>
218+
"sprop-pps=RAHAcvCcFAobJA==,RAHA8vA7NA=="
219+
220+
fmtp = %FMTP{
221+
pt: 96,
222+
profile_space: 0,
223+
profile_id: 1,
224+
tier_flag: false,
225+
level_id: 150,
226+
interop_constraints: 0xB00000000000,
227+
sprop_vps: [
228+
<<64, 1, 12, 1, 255, 255, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 150, 172, 9, 0,
229+
0, 0, 1>>
230+
],
231+
sprop_sps: [
232+
<<66, 1, 1, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 150, 160, 1, 224, 32, 2, 28,
233+
127, 138, 173, 59, 162, 75, 178, 0, 0, 0, 1>>,
234+
<<66, 1, 1, 1, 96, 0, 0, 3, 0, 176, 0, 0, 3, 0, 0, 3, 0, 153, 160, 1, 224, 32, 2, 28,
235+
88, 218, 228, 145, 75, 243, 112, 16, 16, 16, 8>>
236+
],
237+
sprop_pps: [
238+
<<68, 1, 192, 114, 240, 156, 20, 10, 27, 36>>,
239+
<<68, 1, 192, 242, 240, 59, 52>>
240+
]
241+
}
242+
243+
assert "#{fmtp}" == expected
244+
end
179245
end
180246
end

0 commit comments

Comments
 (0)