Packages
membrane_h264_ffmpeg_plugin
0.10.0
0.32.7
0.32.6
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.1
0.30.0
0.29.0
0.28.2
0.28.1
0.28.0
0.27.0
0.26.2
0.26.1
0.26.0
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.0
0.23.0
0.22.1
0.22.0
0.21.6
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.3
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
Membrane H264 decoder and encoder based on FFmpeg and x264
Current section
Files
Jump to
Current section
Files
lib/membrane_h264_ffmpeg/decoder.ex
defmodule Membrane.H264.FFmpeg.Decoder do
@moduledoc """
Membrane element that decodes video in H264 format. It is backed by decoder from FFmpeg.
The element expects the data for each frame (Access Unit) to be received in a separate buffer,
so the parser (`Membrane.H264.FFmpeg.Parser`) may be required in a pipeline before
decoder (e.g. when input is read from `Membrane.File.Source`).
"""
use Membrane.Filter
alias __MODULE__.Native
alias Membrane.Buffer
alias Membrane.Caps.Video.{H264, Raw}
alias Membrane.H264.FFmpeg.Common
def_input_pad :input,
demand_unit: :buffers,
caps: {H264, stream_format: :byte_stream, alignment: :au}
def_output_pad :output,
caps: {Raw, format: one_of([:I420, :I422]), aligned: true}
@impl true
def handle_init(_opts) do
state = %{decoder_ref: nil}
{:ok, state}
end
@impl true
def handle_stopped_to_prepared(_ctx, state) do
with {:ok, decoder_ref} <- Native.create() do
{:ok, %{state | decoder_ref: decoder_ref}}
else
{:error, reason} -> {{:error, reason}, state}
end
end
@impl true
def handle_demand(:output, size, :buffers, _ctx, state) do
{{:ok, demand: {:input, size}}, state}
end
@impl true
def handle_process(:input, %Buffer{metadata: metadata, payload: payload}, ctx, state) do
%{decoder_ref: decoder_ref} = state
dts = metadata[:dts] || 0
with {:ok, pts_list_h264_base, frames} <-
Native.decode(payload, Common.to_h264_time_base(dts), decoder_ref),
bufs = wrap_frames(pts_list_h264_base, frames),
in_caps = ctx.pads.input.caps,
out_caps = ctx.pads.output.caps,
{:ok, caps} <- get_caps_if_needed(in_caps, out_caps, decoder_ref) do
# redemand actually makes sense only for the first call (because decoder keeps 2 frames buffered)
# but it is noop otherwise, so there is no point in implementing special logic for that case
actions = Enum.concat([caps, bufs, [redemand: :output]])
{{:ok, actions}, state}
else
{:error, reason} ->
{{:error, reason}, state}
end
end
@impl true
def handle_caps(:input, _caps, _ctx, state) do
# ignoring caps, new ones will be generated from decoder metadata
{:ok, state}
end
@impl true
def handle_end_of_stream(:input, _ctx, state) do
with {:ok, best_effort_pts_list, frames} <- Native.flush(state.decoder_ref),
bufs <- wrap_frames(best_effort_pts_list, frames) do
actions = bufs ++ [end_of_stream: :output, notify: {:end_of_stream, :input}]
{{:ok, actions}, state}
else
{:error, reason} -> {{:error, reason}, state}
end
end
@impl true
def handle_prepared_to_stopped(_ctx, state) do
{:ok, %{state | decoder_ref: nil}}
end
defp wrap_frames([], []), do: []
defp wrap_frames(pts_list, frames) do
Enum.zip(pts_list, frames)
|> Enum.map(fn {pts, frame} ->
%Buffer{metadata: %{pts: Common.to_membrane_time_base(pts)}, payload: frame}
end)
|> then(&[buffer: {:output, &1}])
end
defp get_caps_if_needed(input_caps, nil, decoder_ref) do
with {:ok, width, height, pix_fmt} <- Native.get_metadata(decoder_ref) do
framerate =
case input_caps do
nil -> {0, 1}
%H264{framerate: in_framerate} -> in_framerate
end
caps = %Raw{
aligned: true,
format: pix_fmt,
framerate: framerate,
height: height,
width: width
}
{:ok, caps: {:output, caps}}
end
end
defp get_caps_if_needed(_in_caps, _out_caps, _decoder_ref), do: {:ok, []}
end