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