lib/silence_generator.ex

defmodule Membrane.SilenceGenerator do
  @moduledoc """
  Element responsible for generating silence as raw audio.
  """

  use Membrane.Source

  alias Membrane.{Buffer, Time}
  alias Membrane.RawAudio

  def_options stream_format: [
                type: :struct,
                spec: RawAudio.t(),
                description:
                  "Audio stream_format of generated samples (`t:Membrane.RawAudio.t/0`)"
              ],
              duration: [
                type: :timeout,
                spec: Time.t() | :infinity,
                description: "Duration of the generated silent samples"
              ],
              frames_per_buffer: [
                type: :integer,
                spec: pos_integer(),
                description: """
                Assumed number of raw audio frames in each buffer.
                Used when converting demand from buffers into bytes.
                """,
                default: 2048
              ]

  def_output_pad :output, accepted_format: RawAudio, flow_control: :manual

  @impl true
  def handle_init(_context, opts) do
    state =
      opts
      |> Map.from_struct()
      |> Map.put(:passed_time, 0)

    {[], state}
  end

  @impl true
  def handle_playing(_context, %{stream_format: stream_format} = state) do
    {[stream_format: {:output, stream_format}], state}
  end

  @impl true
  def handle_demand(:output, size, :bytes, _ctx, %{stream_format: stream_format} = state) do
    time = RawAudio.bytes_to_time(size, stream_format)
    do_handle_demand(time, time, state)
  end

  def handle_demand(:output, buffers, :buffers, _ctx, state) do
    %{stream_format: stream_format, frames_per_buffer: frames_per_buffer} = state

    time = RawAudio.frames_to_time(frames_per_buffer, stream_format)
    do_handle_demand(time * buffers, time, state)
  end

  defp do_handle_demand(
         total_time,
         chunk_time,
         %{stream_format: stream_format, duration: :infinity, passed_time: passed_time} = state
       ) do
    buffers = generate_buffers(passed_time, chunk_time, total_time, stream_format)
    state = %{state | passed_time: passed_time + total_time}

    {[buffer: {:output, buffers}], state}
  end

  defp do_handle_demand(
         total_time,
         chunk_time,
         %{stream_format: stream_format, duration: duration, passed_time: passed_time} = state
       ) do
    total_time = min(total_time, duration - passed_time)
    buffers = generate_buffers(passed_time, chunk_time, total_time, stream_format)
    state = %{state | passed_time: passed_time + total_time}

    if state.passed_time == duration,
      do: {[buffer: {:output, buffers}, end_of_stream: :output], state},
      else: {[buffer: {:output, buffers}], state}
  end

  defp generate_buffers(start_time, chunk_time, total_time, stream_format, buffers \\ [])

  defp generate_buffers(_start_time, _chunk_time, 0, _stream_format, buffers),
    do: Enum.reverse(buffers)

  defp generate_buffers(start_time, chunk_time, total_time, stream_format, buffers) do
    buffer_time = min(total_time, chunk_time)

    buffer = %Buffer{
      payload: RawAudio.silence(stream_format, buffer_time),
      pts: start_time
    }

    generate_buffers(
      start_time + buffer_time,
      chunk_time,
      total_time - buffer_time,
      stream_format,
      [
        buffer | buffers
      ]
    )
  end
end