Skip to content

Commit 46e9f20

Browse files
authored
Merge pull request #19 from membraneframework/add-vk-support-claude
Add optional Vulkan hardware acceleration for H.264 transcoding
2 parents 313b285 + d50e00a commit 46e9f20

13 files changed

Lines changed: 365 additions & 39 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The package can be installed by adding `membrane_transcoder_plugin` to your list
1717
```elixir
1818
def deps do
1919
[
20-
{:membrane_transcoder_plugin, "~> 0.3.3"}
20+
{:membrane_transcoder_plugin, "~> 0.3.4"}
2121
]
2222
end
2323
```

examples/vp8_to_h264.exs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
1-
Mix.install([
2-
:membrane_file_plugin,
3-
:membrane_ivf_plugin,
4-
{:membrane_transcoder_plugin, path: __DIR__ |> Path.join("..") |> Path.expand()}
5-
])
1+
use_native = System.argv() |> Enum.member?("--native")
2+
3+
vk_dep =
4+
if use_native &&
5+
match?({_, 0}, System.cmd("pkg-config", ["--exists", "vulkan"], stderr_to_stdout: true)) do
6+
[{:membrane_vk_video_plugin, "~> 0.2.0"}]
7+
else
8+
[]
9+
end
10+
11+
Mix.install(
12+
vk_dep ++
13+
[
14+
:membrane_file_plugin,
15+
:membrane_ivf_plugin,
16+
{:membrane_transcoder_plugin, path: __DIR__ |> Path.join("..") |> Path.expand()}
17+
]
18+
)
619

720
defmodule Example do
8-
alias Membrane.RCPipeline
9-
require Membrane.RCPipeline, as: RCPipeline
21+
alias Membrane.{H264, RCPipeline}
22+
require RCPipeline
23+
1024
import Membrane.ChildrenSpec
1125

12-
def convert(input_file, output_file) do
26+
def convert(input_file, output_file, native_acceleration) do
1327
pipeline = RCPipeline.start_link!()
1428

1529
spec =
1630
child(%Membrane.File.Source{
1731
location: input_file
1832
})
1933
|> child(:deserializer, Membrane.IVF.Deserializer)
20-
|> child(:transcoder, %Membrane.Transcoder{output_stream_format: Membrane.H264})
34+
|> child(:transcoder, %Membrane.Transcoder{
35+
output_stream_format: H264,
36+
native_acceleration: native_acceleration
37+
})
2138
|> child(:sink, %Membrane.File.Sink{location: output_file})
2239

2340
RCPipeline.subscribe(pipeline, _any)
@@ -27,5 +44,25 @@ defmodule Example do
2744
end
2845
end
2946

30-
File.mkdir("tmp")
31-
Example.convert(Path.join("./test/fixtures", "video_vp8.ivf"), Path.join("./tmp", "video.h264"))
47+
File.mkdir_p!("tmp")
48+
49+
if use_native && !Membrane.Transcoder.vulkan_available?() do
50+
raise "Vulkan is not available. Cannot run with --native flag."
51+
end
52+
53+
acceleration = if use_native, do: :if_available, else: :never
54+
55+
output_file = Path.join(__DIR__, "tmp/video.h264")
56+
57+
if use_native do
58+
IO.puts("Vulkan available: true")
59+
IO.puts("Using native_acceleration: :if_available")
60+
end
61+
62+
Example.convert(
63+
Path.join(__DIR__, "../test/fixtures/video_vp8.ivf"),
64+
output_file,
65+
acceleration
66+
)
67+
68+
IO.puts("Done. Output written to #{output_file}")

lib/transcoder.ex

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ defmodule Membrane.Transcoder do
2626
Now, the only supported stream parameters are:
2727
* `:pixel_format` in `Membrane.RawVideo`
2828
* `:alignment` and `:stream_structure` in `Membrane.H264` and `Membrane.H265`
29+
30+
When the `membrane_vk_video_plugin` dependency is present and Vulkan hardware is available,
31+
H.264 encode/decode can be offloaded to the GPU by setting `native_acceleration: :if_available`.
2932
"""
3033
use Membrane.Bin
3134

@@ -139,6 +142,17 @@ defmodule Membrane.Transcoder do
139142
140143
If nil or not set, the input stream format won't be overriden.
141144
"""
145+
],
146+
native_acceleration: [
147+
spec: :never | :if_available,
148+
default: :never,
149+
description: """
150+
Specifies whether to use Vulkan hardware acceleration for video transcoding.
151+
152+
Can be:
153+
* `:never` - Always use software-based transcoding (default)
154+
* `:if_available` - Use Vulkan acceleration when available on the system
155+
"""
142156
]
143157

144158
@impl true
@@ -155,12 +169,31 @@ defmodule Membrane.Transcoder do
155169
opts
156170
|> Map.from_struct()
157171
|> Map.merge(%{
158-
input_stream_format: nil
172+
input_stream_format: nil,
173+
use_hardware_acceleration?: should_use_hardware_acceleration?(opts.native_acceleration)
159174
})
160175

161176
{[spec: spec], state}
162177
end
163178

179+
defp should_use_hardware_acceleration?(:if_available), do: vulkan_available?()
180+
defp should_use_hardware_acceleration?(_native_acceleration), do: false
181+
182+
@doc """
183+
Returns `true` if the optional `membrane_vk_video_plugin` dependency is installed
184+
and its modules can be loaded in the current runtime.
185+
186+
Note: a `true` result only confirms the plugin is loadable - it does not guarantee that the
187+
host actually exposes Vulkan Video extensions with H.264 encode/decode capabilities. The
188+
underlying plugin may still fail at runtime if the GPU/driver does not support them.
189+
"""
190+
@spec vulkan_available?() :: boolean()
191+
def vulkan_available?() do
192+
Code.ensure_loaded?(Membrane.VKVideo.Decoder) and
193+
Code.ensure_loaded?(Membrane.VKVideo.Encoder) and
194+
Code.ensure_loaded?(Membrane.VKVideo.Native)
195+
end
196+
164197
defp maybe_plug_stream_format_changer(builder, nil), do: builder
165198

166199
defp maybe_plug_stream_format_changer(builder, enforced_stream_format) do
@@ -187,7 +220,8 @@ defmodule Membrane.Transcoder do
187220
|> plug_transcoding(
188221
format,
189222
state.output_stream_format,
190-
state.transcoding_policy
223+
state.transcoding_policy,
224+
state.use_hardware_acceleration?
191225
)
192226
|> get_child(:output_funnel)
193227

@@ -232,15 +266,32 @@ defmodule Membrane.Transcoder do
232266
end
233267
end
234268

235-
defp plug_transcoding(builder, input_format, output_format, transcoding_policy)
269+
defp plug_transcoding(
270+
builder,
271+
input_format,
272+
output_format,
273+
transcoding_policy,
274+
_use_hardware_acceleration?
275+
)
236276
when Audio.is_audio_format(input_format) do
237277
builder
238278
|> Audio.plug_audio_transcoding(input_format, output_format, transcoding_policy)
239279
end
240280

241-
defp plug_transcoding(builder, input_format, output_format, transcoding_policy)
281+
defp plug_transcoding(
282+
builder,
283+
input_format,
284+
output_format,
285+
transcoding_policy,
286+
use_hardware_acceleration?
287+
)
242288
when Video.is_video_format(input_format) do
243289
builder
244-
|> Video.plug_video_transcoding(input_format, output_format, transcoding_policy)
290+
|> Video.plug_video_transcoding(
291+
input_format,
292+
output_format,
293+
transcoding_policy,
294+
use_hardware_acceleration?
295+
)
245296
end
246297
end

0 commit comments

Comments
 (0)