Packages
vk video plugin for Membrane Framework
Current section
Files
Jump to
Current section
Files
lib/encoder.ex
defmodule Membrane.VKVideo.Encoder do
@moduledoc """
H.264 encoder taking advantage of hardware acceleration provided
by Vulkan video extensions.
"""
use Membrane.Filter
require Membrane.Logger
alias Membrane.VKVideo.{DeviceServer, Native}
def_input_pad :input, accepted_format: %Membrane.RawVideo{pixel_format: :NV12}
def_output_pad :output,
accepted_format: %Membrane.H264{stream_structure: :annexb, alignment: :au}
def_options tune: [
spec: :low_latency | :high_quality,
default: :low_latency,
description: """
Specifies whether the encoder should be optimized for minimal latency (which is
important in case of livestreams) or for higher quality (applicable to offline encoding).
"""
],
approx_framerate: [
spec: {non_neg_integer(), pos_integer()} | nil,
default: nil,
description: """
Framerate of the stream expressed in number of frames per second.
It's only used by the rate control mechanism and therefore it does not need to be an exact
value. If nil, the framerate will be read from the stream format's structure or set
to fixed value of 30 frames per second if framerate is not provided by the stream format.
"""
],
rate_control: [
spec:
:encoder_default
| :disabled
| {:variable_bitrate, __MODULE__.VariableBitrate.t()}
| {:constant_bitrate, __MODULE__.ConstantBitrate.t()},
default: :encoder_default,
description: """
Specifies which rate control mechanism should by used by the encoder.
"""
]
@impl true
def handle_init(_ctx, opts) do
state = %{
encoder: nil,
width: nil,
height: nil,
override_framerate?: opts.approx_framerate != nil,
approx_framerate: opts.approx_framerate,
rate_control: opts.rate_control,
tune: opts.tune
}
{[], state}
end
@impl true
def handle_stream_format(:input, stream_format, _ctx, state) do
cond do
state.override_framerate? and
(stream_format.width != state.width or
stream_format.height != state.height) ->
%{
state
| width: stream_format.width,
height: stream_format.height
}
|> spawn_encoder()
not state.override_framerate? and
(stream_format.width != state.width or stream_format.height != state.height or
stream_format.framerate != state.approx_framerate) ->
%{
state
| width: stream_format.width,
height: stream_format.height,
approx_framerate: resolve_framerate(stream_format, state)
}
|> spawn_encoder()
true ->
{[], state}
end
end
defp spawn_encoder(state) do
device = DeviceServer.get_device()
encoder =
Native.new_encoder(
device,
state.width,
state.height,
state.approx_framerate,
state.tune,
state.rate_control
)
state = put_in(state, [:encoder], encoder)
stream_format =
%Membrane.H264{
stream_structure: :annexb,
alignment: :au,
width: state.width,
height: state.height
}
stream_format =
if state.override_framerate? do
stream_format
else
%{stream_format | framerate: state.approx_framerate}
end
{[stream_format: {:output, stream_format}], state}
end
defp resolve_framerate(stream_format, state) do
if is_nil(stream_format.framerate) and requires_framerate?(state.rate_control) do
Membrane.Logger.warning("""
Framerate is required when using #{inspect(elem(state.rate_control, 0))} rate control but it
wasn't provided in the stream format nor via options. Please provide approximate framerate
using `approx_framerate` option of the element.
""")
end
stream_format.framerate || {30, 1}
end
defp requires_framerate?({:constant_bitrate, _constant_bitrate}), do: true
defp requires_framerate?({:variable_bitrate, _variable_bitrate}), do: true
defp requires_framerate?(_other_rate_control), do: false
@impl true
def handle_buffer(:input, buffer, _ctx, state) do
encoded_frame = Native.encode(state.encoder, buffer.payload, buffer.pts)
pts =
if encoded_frame.pts_ns != nil,
do: Membrane.Time.nanoseconds(encoded_frame.pts_ns),
else: nil
{[
buffer:
{:output,
%Membrane.Buffer{
payload: encoded_frame.payload,
pts: pts,
dts: buffer.dts
}}
], state}
end
@impl true
def handle_end_of_stream(:input, _ctx, state) do
:ok = Native.destroy(state.encoder)
state = %{state | encoder: nil}
{[end_of_stream: :output], state}
end
end