Current section

Files

Jump to
membrane_element_ffmpeg_h264 lib membrane_element_ffmpeg_h264 parser.ex
Raw

lib/membrane_element_ffmpeg_h264/parser.ex

defmodule Membrane.Element.FFmpeg.H264.Parser do
@moduledoc """
Membrane element providing parser for H264 encoded video stream.
Uses the parser provided by FFmpeg.
It receives buffers with binary payloads and splits them into frames.
"""
use Membrane.Filter
alias __MODULE__.Native
alias Membrane.Buffer
alias Membrane.Caps.Video.H264
use Membrane.Log
def_input_pad :input,
demand_unit: :buffers,
caps: :any
def_output_pad :output,
caps: {H264, stream_format: :byte_stream, alignment: :au}
def_options framerate: [
type: :framerate,
spec: H264.framerate_t(),
default: {0, 1},
description: """
Framerate of video stream, see `t:Membrane.Caps.Video.H264.framerate_t/0`
"""
]
@impl true
def handle_init(opts) do
{:ok, opts |> Map.merge(%{parser_ref: nil, partial_frame: ""})}
end
@impl true
def handle_stopped_to_prepared(_ctx, state) do
with {:ok, parser_ref} <- Native.create() do
{:ok, %{state | parser_ref: parser_ref}}
else
{:error, reason} -> {{:error, reason}, state}
end
end
@impl true
def handle_demand(:output, _size, :buffers, _ctx, state) do
{{:ok, demand: :input}, state}
end
@impl true
def handle_process(:input, %Buffer{payload: payload}, ctx, state) do
%{parser_ref: parser_ref, partial_frame: partial_frame} = state
with {:ok, sizes} <- Native.parse(payload, parser_ref),
{bufs, rest} <- gen_bufs_by_sizes(partial_frame <> payload, sizes) do
state = %{state | partial_frame: rest}
actions = [buffer: {:output, bufs}, redemand: :output]
actions =
if ctx.pads.output.caps == nil and bufs != [] do
{:ok, width, height, profile} = Native.get_parsed_meta(parser_ref)
caps = %H264{
width: width,
height: height,
framerate: state.framerate,
alignment: :au,
stream_format: :byte_stream,
profile: profile
}
[{:caps, {:output, caps}} | actions]
else
actions
end
{{:ok, actions}, state}
else
{:error, reason} -> {{:error, reason}, state}
end
end
@impl true
def handle_end_of_stream(:input, _ctx, state) do
%{parser_ref: parser_ref, partial_frame: partial_frame} = state
with {:ok, sizes} <- Native.flush(parser_ref) do
{bufs, rest} = gen_bufs_by_sizes(partial_frame, sizes)
if rest != "" do
warn("Discarding incomplete frame because of end of stream")
end
state = %{state | partial_frame: ""}
actions = [
buffer: {:output, bufs},
end_of_stream: :output,
notify: {:end_of_stream, :input}
]
{{:ok, actions}, state}
end
end
@impl true
def handle_prepared_to_stopped(_ctx, state) do
{:ok, %{state | parser_ref: nil}}
end
defp gen_bufs_by_sizes(input, sizes) do
Enum.map_reduce(sizes, input, fn size, stream ->
<<frame::bytes-size(size), rest::binary>> = stream
{%Buffer{payload: frame}, rest}
end)
end
end