Skip to content

Commit 13d1a15

Browse files
Add H265 support to Sink and Source
1 parent bf33a1e commit 13d1a15

9 files changed

Lines changed: 117 additions & 10 deletions

File tree

lib/membrane_webrtc/ex_webrtc/sink.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ defmodule Membrane.WebRTC.ExWebRTCSink do
161161
{[], state}
162162
end
163163

164-
defp set_pc_sender_video_codec(state, pad, codec) when codec in [:vp8, :h264, :av1] do
164+
defp set_pc_sender_video_codec(state, pad, codec) when codec in [:vp8, :h264, :h265, :av1] do
165165
mime_type = mime_type_from_codec(codec)
166166
transceiver = get_transceiver(state, pad)
167167

@@ -383,6 +383,7 @@ defmodule Membrane.WebRTC.ExWebRTCSink do
383383
end
384384

385385
defp mime_type_from_codec(:h264), do: "video/H264"
386+
defp mime_type_from_codec(:h265), do: "video/H265"
386387
defp mime_type_from_codec(:vp8), do: "video/VP8"
387388
defp mime_type_from_codec(:av1), do: "video/AV1"
388389
end

lib/membrane_webrtc/ex_webrtc/source.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ defmodule Membrane.WebRTC.ExWebRTCSource do
4545
status: :init | :connecting | :connected | :closed,
4646
audio_params: [ExWebRTC.RTPCodecParameters.t()],
4747
video_params: [ExWebRTC.RTPCodecParameters.t()],
48-
allowed_video_codecs: [:h264 | :vp8],
49-
preferred_video_codec: :h264 | :vp8,
48+
allowed_video_codecs: [:h264 | :h265 | :vp8],
49+
preferred_video_codec: :h264 | :h265 | :vp8,
5050
ice_servers: [ExWebRTC.PeerConnection.Configuration.ice_server()],
5151
ice_port_range: Enumerable.t(non_neg_integer()),
5252
ice_ip_filter: (:inet.ip_address() -> boolean()),

lib/membrane_webrtc/ex_webrtc/utils.ex

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
33

44
alias ExWebRTC.RTPCodecParameters
55

6-
@type codec :: :opus | :h264 | :vp8 | :av1
6+
@type codec :: :opus | :h264 | :h265 | :vp8 | :av1
77
@type codec_or_codecs :: codec() | [codec()]
88

99
@spec codec_params(codec_or_codecs()) :: [RTPCodecParameters.t()]
@@ -33,6 +33,22 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
3333
]
3434
end
3535

36+
def codec_params(:h265) do
37+
[
38+
%RTPCodecParameters{
39+
payload_type: 98,
40+
mime_type: "video/H265",
41+
clock_rate: codec_clock_rate(:h265),
42+
sdp_fmtp_line: %ExSDP.Attribute.FMTP{
43+
pt: 98,
44+
profile_id: 1,
45+
tier_flag: false,
46+
level_id: 153
47+
}
48+
}
49+
]
50+
end
51+
3652
def codec_params(:vp8) do
3753
[
3854
%RTPCodecParameters{
@@ -67,19 +83,22 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
6783
def codec_clock_rate(:opus), do: 48_000
6884
def codec_clock_rate(:vp8), do: 90_000
6985
def codec_clock_rate(:h264), do: 90_000
86+
def codec_clock_rate(:h265), do: 90_000
7087
def codec_clock_rate(:av1), do: 90_000
7188

7289
def codec_clock_rate(codecs) when is_list(codecs) do
7390
cond do
7491
codecs == [:opus] ->
7592
48_000
7693

77-
codecs != [] and Enum.all?(codecs, &(&1 in [:vp8, :h264])) ->
94+
codecs != [] and Enum.all?(codecs, &(&1 in [:vp8, :h264, :h265])) ->
7895
90_000
7996
end
8097
end
8198

82-
@spec get_video_codecs_from_sdp(ExWebRTC.SessionDescription.t()) :: [:h264 | :vp8 | :av1]
99+
@spec get_video_codecs_from_sdp(ExWebRTC.SessionDescription.t()) :: [
100+
:h264 | :h265 | :vp8 | :av1
101+
]
83102
def get_video_codecs_from_sdp(%ExWebRTC.SessionDescription{sdp: sdp}) do
84103
ex_sdp = ExSDP.parse!(sdp)
85104

@@ -90,6 +109,7 @@ defmodule Membrane.WebRTC.ExWebRTCUtils do
90109
end)
91110
|> Enum.flat_map(fn
92111
%ExSDP.Attribute.RTPMapping{encoding: "H264"} -> [:h264]
112+
%ExSDP.Attribute.RTPMapping{encoding: "H265"} -> [:h265]
93113
%ExSDP.Attribute.RTPMapping{encoding: "VP8"} -> [:vp8]
94114
%ExSDP.Attribute.RTPMapping{encoding: "AV1"} -> [:av1]
95115
_attribute -> []

lib/membrane_webrtc/sink.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule Membrane.WebRTC.Sink do
1818
use Membrane.Bin
1919

2020
alias Membrane.H264
21+
alias Membrane.H265
2122
alias Membrane.RemoteStream
2223
alias Membrane.VP8
2324
alias Membrane.WebRTC.{ExWebRTCSink, Signaling, SimpleWebSocketServer}
@@ -68,7 +69,7 @@ defmodule Membrane.WebRTC.Sink do
6869
"""
6970
],
7071
video_codec: [
71-
spec: :vp8 | :h264 | :av1 | [:vp8 | :h264 | :av1],
72+
spec: :vp8 | :h264 | :h265 | :av1 | [:vp8 | :h264 | :h265 | :av1],
7273
default: [:vp8, :h264],
7374
description: """
7475
Video codecs, that #{inspect(__MODULE__)} will try to negotiatie in SDP
@@ -102,6 +103,7 @@ defmodule Membrane.WebRTC.Sink do
102103
accepted_format:
103104
any_of(
104105
%Membrane.H264{alignment: :nalu},
106+
%Membrane.H265{alignment: :nalu},
105107
%Membrane.RemoteStream{content_format: Membrane.VP8},
106108
%Membrane.RemoteStream{content_format: Membrane.RTP},
107109
Membrane.VP8,
@@ -189,6 +191,9 @@ defmodule Membrane.WebRTC.Sink do
189191
%H264{} ->
190192
:h264
191193

194+
%H265{} ->
195+
:h265
196+
192197
%VP8{} ->
193198
:vp8
194199

@@ -206,6 +211,7 @@ defmodule Membrane.WebRTC.Sink do
206211
payloader =
207212
case codec do
208213
:h264 -> %Membrane.RTP.H264.Payloader{max_payload_size: 1000}
214+
:h265 -> %Membrane.RTP.H265.Payloader{max_payload_size: 1000}
209215
:vp8 -> Membrane.RTP.VP8.Payloader
210216
end
211217

lib/membrane_webrtc/source.ex

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ defmodule Membrane.WebRTC.Source do
6464
"""
6565
],
6666
allowed_video_codecs: [
67-
spec: :vp8 | :h264 | :av1 | [:vp8 | :h264 | :av1],
67+
spec: :vp8 | :h264 | :h265 | :av1 | [:vp8 | :h264 | :h265 | :av1],
6868
default: :vp8,
6969
description: """
7070
Specifies, which video codecs can be accepted by the source during the SDP
7171
negotiaion.
7272
73-
Either `:vp8`, `:h264` or a list containing both options.
73+
Either `:vp8`, `:h264`, `:h265` or a list containing these options.
7474
7575
Event if it is set to `[:h264, :vp8]`, the source will negotiate at most
7676
one video codec. Negotiated codec can be deduced from
@@ -83,7 +83,7 @@ defmodule Membrane.WebRTC.Source do
8383
"""
8484
],
8585
preferred_video_codec: [
86-
spec: :vp8 | :h264,
86+
spec: :vp8 | :h264 | :h265,
8787
default: :vp8,
8888
description: """
8989
Specyfies, which video codec will be preferred by the source, if both of
@@ -129,6 +129,7 @@ defmodule Membrane.WebRTC.Source do
129129
accepted_format:
130130
any_of(
131131
Membrane.H264,
132+
Membrane.H265,
132133
%Membrane.RemoteStream{content_format: Membrane.VP8},
133134
%Membrane.RemoteStream{content_format: Membrane.Opus},
134135
Membrane.RTP
@@ -252,12 +253,18 @@ defmodule Membrane.WebRTC.Source do
252253
state.allowed_video_codecs == [:h264] ->
253254
get_h264_depayloader(builder)
254255

256+
state.allowed_video_codecs == [:h265] ->
257+
get_h265_depayloader(builder)
258+
255259
state.negotiated_video_codecs == [:vp8] ->
256260
get_vp8_depayloader(builder)
257261

258262
state.negotiated_video_codecs == [:h264] ->
259263
get_h264_depayloader(builder)
260264

265+
state.negotiated_video_codecs == [:h265] ->
266+
get_h265_depayloader(builder)
267+
261268
state.negotiated_video_codecs == nil ->
262269
raise "Cannot select depayloader before end of SDP messages exchange"
263270

@@ -285,5 +292,12 @@ defmodule Membrane.WebRTC.Source do
285292
})
286293
end
287294

295+
defp get_h265_depayloader(builder) do
296+
child(builder, {:depayloader_bin, make_ref()}, %Membrane.RTP.DepayloaderBin{
297+
depayloader: Membrane.RTP.H265.Depayloader,
298+
clock_rate: ExWebRTCUtils.codec_clock_rate(:h265)
299+
})
300+
end
301+
288302
def default_ice_ip_filter(_ip), do: true
289303
end

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ defmodule Membrane.WebRTC.Plugin.Mixfile do
4646
{:membrane_core, "~> 1.2 and >= 1.2.2"},
4747
{:membrane_rtp_plugin, "~> 0.31.1"},
4848
{:membrane_rtp_h264_plugin, "~> 0.20.1"},
49+
{:membrane_rtp_h265_plugin, "~> 0.5.3"},
4950
{:membrane_rtp_vp8_plugin, "~> 0.9.4"},
5051
{:membrane_rtp_opus_plugin, "~> 0.10.0"},
5152

mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"membrane_realtimer_plugin": {:hex, :membrane_realtimer_plugin, "0.10.1", "6f00f499570609f0fd717edb3c069fdcd8ece893ae683867c9ea3f7aa3de9dd0", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "e961cf8aab3857f686eba9a61aaa91a818fe70b1e1282d09f4d6db06acb9dd67"},
5454
"membrane_rtp_format": {:hex, :membrane_rtp_format, "0.11.0", "0091465530ab946bd4d8affe849e9a01a79e46b750f6ec420521bcb806d5be49", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "bea52c2f286d5edb90d647bf3916f4e9e0270d5c6eddb708b65880570c1e42d2"},
5555
"membrane_rtp_h264_plugin": {:hex, :membrane_rtp_h264_plugin, "0.20.5", "31220642282806f87226d8e472be09873d48ed5a519feeb0ccdb884be8410fd0", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.11.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "ea1cd860874527d7c63afdc68ec4d90aa46c3bb25ea320019af2a89ba7dc0937"},
56+
"membrane_rtp_h265_plugin": {:hex, :membrane_rtp_h265_plugin, "0.5.4", "578dc8e0afc62396e9fd37ab83b32106265e3188ac53a83c57bd96cb592c6cc7", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.11.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "1f643c8a25a5e721ea4eeacbf7b1ae5e5a5423e1f17400414aefa85a54c2cd47"},
5657
"membrane_rtp_opus_plugin": {:hex, :membrane_rtp_opus_plugin, "0.10.2", "cf614e5f62e4896e48030016f4ac04d690351185aad83ca874c09383fe376698", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.11.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}], "hexpm", "1f02501a017e55f74a47fa6b724ae73d5821887fa4a207a2ecf7cb7d7f7539c1"},
5758
"membrane_rtp_plugin": {:hex, :membrane_rtp_plugin, "0.31.4", "671144c967f9ec1935d8c08fe7c7ebdfca081a69ef588e0806e2bc0081482766", [:mix], [{:bimap, "~> 1.2", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_libsrtp, "~> 0.6.0 or ~> 0.7.0", [hex: :ex_libsrtp, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:heap, "~> 2.0.2", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_funnel_plugin, "~> 0.9.0", [hex: :membrane_funnel_plugin, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.11.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_telemetry_metrics, "~> 0.1.0", [hex: :membrane_telemetry_metrics, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "5a1e6b2512628619a597ef609d2583e8891411446122da55f248c745a2f1d348"},
5859
"membrane_rtp_vp8_plugin": {:hex, :membrane_rtp_vp8_plugin, "0.9.6", "49b93f09cd23f8d5a69b622466df02c6db1b8467cfbabf4715421a51f2eea3f9", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_rtp_format, "~> 0.11.0", [hex: :membrane_rtp_format, repo: "hexpm", optional: false]}, {:membrane_vp8_format, "~> 0.4.0 or ~> 0.5.0", [hex: :membrane_vp8_format, repo: "hexpm", optional: false]}], "hexpm", "ad74006ad4b22218676558c44e15057c389651887dea82c1e581139c16176630"},

test/fixtures/input_bbb.h265

21.5 KB
Binary file not shown.

test/membrane_webrtc/integration_test.exs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,70 @@ defmodule Membrane.WebRTC.IntegrationTest do
226226
end
227227
end
228228

229+
defmodule H265SendRecv do
230+
use ExUnit.Case, async: true
231+
232+
import Membrane.Testing.Assertions
233+
import Utils
234+
235+
test "send and receive an H265 stream" do
236+
signaling = Signaling.new()
237+
238+
send_pipeline =
239+
Testing.Pipeline.start_link_supervised!(
240+
spec:
241+
child(%Membrane.File.Source{location: "test/fixtures/input_bbb.h265"})
242+
|> child(%Membrane.H265.Parser{
243+
output_alignment: :nalu,
244+
output_stream_structure: :annexb,
245+
generate_best_effort_timestamps: %{framerate: {24, 1}}
246+
})
247+
|> child(Membrane.Realtimer)
248+
|> via_in(Pad.ref(:input, :video), options: [kind: :video])
249+
|> child(:webrtc, %WebRTC.Sink{
250+
signaling: signaling,
251+
tracks: [:video],
252+
video_codec: [:h265]
253+
})
254+
)
255+
256+
receive_pipeline =
257+
Testing.Pipeline.start_link_supervised!(
258+
spec:
259+
child(:webrtc, %WebRTC.Source{
260+
signaling: signaling,
261+
allowed_video_codecs: [:h265],
262+
preferred_video_codec: :h265
263+
})
264+
|> via_out(Pad.ref(:output, :video), options: [kind: :video])
265+
|> child(:video_sink, Membrane.Testing.Sink)
266+
)
267+
268+
[send_pipeline, receive_pipeline]
269+
|> Enum.each(fn pipeline ->
270+
assert_pipeline_notified(
271+
pipeline,
272+
:webrtc,
273+
{:negotiated_video_codecs, [:h265]},
274+
fixture_processing_timeout()
275+
)
276+
end)
277+
278+
assert_sink_stream_format(receive_pipeline, :video_sink, %Membrane.H265{}, 10_000)
279+
assert_sink_buffer(receive_pipeline, :video_sink, %Membrane.Buffer{}, 10_000)
280+
281+
assert_pipeline_notified(
282+
send_pipeline,
283+
:webrtc,
284+
{:end_of_stream, :video},
285+
fixture_processing_timeout()
286+
)
287+
288+
Testing.Pipeline.terminate(send_pipeline)
289+
Testing.Pipeline.terminate(receive_pipeline)
290+
end
291+
end
292+
229293
defmodule DynamicTracks do
230294
use ExUnit.Case, async: true
231295

0 commit comments

Comments
 (0)