defmodule Membrane.CameraCapture do
@moduledoc """
Membrane Source Element for capturing image from a camera through FFmpeg
"""
use Membrane.Source
alias __MODULE__.Native
alias Membrane.Buffer
def_output_pad :output, accepted_format: _any, flow_control: :push
def_options device: [
spec: String.t(),
default: "default",
description: "Name of the device used to capture video"
],
framerate: [
spec: non_neg_integer(),
default: 30,
description: "Framerate of device's output video stream"
]
@impl true
def handle_init(_ctx, %__MODULE__{} = options) do
with {:ok, native} <- Native.open(options.device, options.framerate) do
state = %{native: native, provider: nil, init_time: nil, framerate: options.framerate}
{[], state}
else
{:error, reason} -> raise "Failed to initialize camera, reason: #{reason}"
end
end
@impl true
def handle_playing(ctx, state) do
{:ok, width, height, pixel_format} = Native.stream_props(state.native)
stream_format = %Membrane.RawVideo{
width: width,
height: height,
pixel_format: pixel_format_to_atom(pixel_format),
aligned: true,
framerate: {state.framerate, 1}
}
element_pid = self()
{:ok, provider} =
Membrane.UtilitySupervisor.start_link_child(
ctx.utility_supervisor,
# The call to Supervisor.child_spec can be removed after
# https://github.com/membraneframework/membrane_core/pull/681 is merged and released.
Supervisor.child_spec({Task, fn -> frame_provider(state.native, element_pid) end}, [])
)
state = %{state | provider: provider}
actions = [stream_format: {:output, stream_format}]
{actions, state}
end
defp frame_provider(native, target) do
with {:ok, frame} <- Native.read_packet(native) do
send(target, {:frame, frame})
frame_provider(native, target)
else
{:error, reason} ->
raise "Error when reading packet from camera: #{inspect(reason)}"
end
end
@impl true
def handle_info({:frame, frame}, _ctx, state) do
time = Membrane.Time.monotonic_time()
init_time = state.init_time || time
buffer = %Buffer{payload: frame, pts: time - init_time}
{[buffer: {:output, buffer}], %{state | init_time: init_time}}
end
defp pixel_format_to_atom("yuv420p"), do: :I420
defp pixel_format_to_atom("yuv422p"), do: :I422
defp pixel_format_to_atom("yuv444p"), do: :I444
defp pixel_format_to_atom("rgb24"), do: :RGB
defp pixel_format_to_atom("rgba"), do: :RGBA
defp pixel_format_to_atom("yuyv422"), do: :YUY2
defp pixel_format_to_atom("nv12"), do: :NV12
defp pixel_format_to_atom("nv21"), do: :NV21
defp pixel_format_to_atom(pixel_format), do: raise("unsupported pixel format #{pixel_format}")
end