lib/membrane_opus/decoder.ex

defmodule Membrane.Opus.Decoder do
  @moduledoc """
  This element performs decoding of Opus audio.
  """

  use Membrane.Filter

  alias __MODULE__.Native
  alias Membrane.{Buffer, Opus, RemoteStream}
  alias Membrane.Opus.Util
  alias Membrane.RawAudio

  def_options sample_rate: [
                spec: 8_000 | 12_000 | 16_000 | 24_000 | 48_000,
                default: 48_000,
                description: """
                Sample rate to decode at. Note: Opus is able to decode any stream
                at any supported sample rate. 48 kHz is recommended. For details,
                see https://tools.ietf.org/html/rfc7845#section-5.1 point 5.
                """
              ]

  def_input_pad :input,
    demand_unit: :buffers,
    demand_mode: :auto,
    caps: [
      {Opus, self_delimiting?: false},
      {RemoteStream, type: :packetized, content_format: one_of([Opus, nil])}
    ]

  def_output_pad :output, caps: {RawAudio, sample_format: :s16le}, demand_mode: :auto

  @impl true
  def handle_init(%__MODULE__{} = options) do
    state =
      options
      |> Map.from_struct()
      |> Map.merge(%{native: nil, channels: nil})

    {:ok, state}
  end

  @impl true
  def handle_caps(:input, %Opus{channels: channels}, _ctx, state) do
    {caps, state} = maybe_make_native(channels, state)
    {{:ok, caps}, state}
  end

  @impl true
  def handle_caps(:input, _caps, _ctx, state) do
    {:ok, state}
  end

  @impl true
  def handle_process(:input, buffer, _ctx, state) do
    {:ok, _config_number, stereo_flag, _frame_packing} = Util.parse_toc_byte(buffer.payload)
    channels = Util.parse_channels(stereo_flag)
    {caps, state} = maybe_make_native(channels, state)

    decoded = Native.decode_packet(state.native, buffer.payload)
    buffer = %Buffer{buffer | payload: decoded}
    {{:ok, caps ++ [buffer: {:output, buffer}]}, state}
  end

  @impl true
  def handle_prepared_to_stopped(_ctx, state) do
    {:ok, %{state | native: nil}}
  end

  defp maybe_make_native(channels, %{channels: channels} = state) do
    {[], state}
  end

  defp maybe_make_native(channels, state) do
    native = Native.create(state.sample_rate, channels)
    caps = %RawAudio{sample_format: :s16le, channels: channels, sample_rate: state.sample_rate}
    {[caps: {:output, caps}], %{state | native: native, channels: channels}}
  end
end