defmodule Membrane.G711.FFmpeg.Decoder do
@moduledoc """
Membrane element that decodes audio in G711 format. It is backed by decoder from FFmpeg.
At the moment, only A-law is supported.
"""
use Membrane.Filter
require Membrane.G711
require Membrane.Logger
alias __MODULE__.Native
alias Membrane.G711.FFmpeg.Common
alias Membrane.{G711, RawAudio, RemoteStream}
def_input_pad :input,
flow_control: :auto,
accepted_format: any_of(%RemoteStream{}, %G711{encoding: :PCMA})
def_output_pad :output,
flow_control: :auto,
accepted_format: %RawAudio{
channels: G711.num_channels(),
sample_rate: G711.sample_rate()
}
@impl true
def handle_init(_ctx, _opts) do
state = %{decoder_ref: nil}
{[], state}
end
@impl true
def handle_buffer(:input, buffer, _ctx, state) do
case Native.decode(buffer.payload, state.decoder_ref) do
{:ok, frames} ->
{Common.wrap_frames(frames), state}
{:error, reason} ->
raise "Native decoder failed to decode the payload: #{inspect(reason)}"
end
end
@impl true
def handle_stream_format(:input, _stream_format, _ctx, state) do
with buffers <- flush_decoder_if_exists(state),
{:ok, new_decoder_ref} <- Native.create() do
stream_format = generate_stream_format(new_decoder_ref)
actions = buffers ++ [stream_format: {:output, stream_format}]
{actions, %{state | decoder_ref: new_decoder_ref}}
else
{:error, reason} -> raise "Failed to create native decoder: #{inspect(reason)}"
end
end
@impl true
def handle_end_of_stream(:input, _ctx, state) do
buffers = flush_decoder_if_exists(state)
actions = buffers ++ [end_of_stream: :output]
{actions, state}
end
defp flush_decoder_if_exists(%{decoder_ref: nil}), do: []
defp flush_decoder_if_exists(%{decoder_ref: decoder_ref}) do
with {:ok, frames} <- Native.flush(decoder_ref) do
Common.wrap_frames(frames)
else
{:error, reason} -> raise "Native decoder failed to flush: #{inspect(reason)}"
end
end
defp generate_stream_format(decoder_ref) do
with {:ok, sample_format} <- Native.get_metadata(decoder_ref) do
%RawAudio{
channels: G711.num_channels(),
sample_rate: G711.sample_rate(),
sample_format: sample_format
}
else
{:error, reason} -> raise "Native encoder failed to provide metadata: #{inspect(reason)}"
end
end
end