lib/membrane_audio_filler.ex

defmodule Membrane.AudioFiller do
  @moduledoc """
  Element ensures that audio will be consistent by filling 'holes' with silence.
  In order for AudioFiller to work properly, all buffers processed have to have correct timestamps (pts).
  """

  use Membrane.Filter
  alias Membrane.Buffer
  alias Membrane.RawAudio

  def_options min_audio_loss: [
                spec: Membrane.Time.t(),
                default: Membrane.Time.millisecond(),
                description: """
                Minimal time of audio loss in nanoseconds that filler should fill with silence
                """
              ]

  def_input_pad :input, accepted_format: RawAudio
  def_output_pad :output, accepted_format: RawAudio

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

    {[], state}
  end

  @impl true
  def handle_buffer(:input, buffer, ctx, %{last_pts: nil} = state) do
    last_payload_duration =
      RawAudio.bytes_to_time(byte_size(buffer.payload), ctx.pads.input.stream_format)

    {[buffer: {:output, buffer}],
     %{state | last_pts: buffer.pts, last_payload_duration: last_payload_duration}}
  end

  @impl true
  def handle_buffer(:input, buffer, ctx, state) do
    pts_duration = buffer.pts - state.last_pts
    lost_audio_duration = pts_duration - state.last_payload_duration

    buffers =
      if lost_audio_duration > state.min_audio_loss do
        new_pts = state.last_pts + state.last_payload_duration

        [
          %Buffer{
            pts: new_pts,
            payload: RawAudio.silence(ctx.pads.input.stream_format, lost_audio_duration)
          },
          buffer
        ]
      else
        [buffer]
      end

    current_payload_duration =
      RawAudio.bytes_to_time(byte_size(buffer.payload), ctx.pads.input.stream_format)

    {[buffer: {:output, buffers}],
     %{state | last_pts: buffer.pts, last_payload_duration: current_payload_duration}}
  end
end