defmodule Membrane.AAC.Parser do
@moduledoc """
Parser for Advanced Audio Codec.
Supports both plain and ADTS-encapsulated output (configured by `out_encapsulation`).
Input with encapsulation `:none` is supported, but correct AAC caps need to be supplied with the stream.
Adds sample rate based timestamp to metadata if absent.
"""
use Membrane.Filter
alias __MODULE__.Helper
alias Membrane.{AAC, Buffer}
def_input_pad :input, demand_unit: :buffers, caps: :any
def_output_pad :output, caps: AAC
def_options samples_per_frame: [
spec: AAC.samples_per_frame_t(),
default: 1024,
description: "Count of audio samples in each AAC frame"
],
out_encapsulation: [
spec: AAC.encapsulation_t(),
default: :ADTS,
description: """
Determines whether output AAC frames should be prefixed with ADTS headers
"""
],
in_encapsulation: [
spec: AAC.encapsulation_t(),
default: :ADTS
]
@type timestamp_t :: Ratio.t() | Membrane.Time.t()
@impl true
def handle_init(options) do
state = options |> Map.from_struct() |> Map.merge(%{leftover: <<>>, timestamp: 0})
{:ok, state}
end
@impl true
def handle_caps(:input, %AAC{encapsulation: encapsulation} = caps, _ctx, state)
when state.in_encapsulation == encapsulation do
{{:ok, caps: {:output, %{caps | encapsulation: state.out_encapsulation}}}, state}
end
@impl true
def handle_caps(:input, %Membrane.AAC.RemoteStream{} = caps, _ctx, state) do
caps = Helper.parse_audio_specific_config!(caps.audio_specific_config)
{{:ok, caps: {:output, %{caps | encapsulation: state.out_encapsulation}}}, state}
end
@impl true
def handle_caps(:input, %AAC{encapsulation: encapsulation}, _ctx, state)
when encapsulation != state.in_encapsulation,
do:
raise(
"%AAC{encapsulation: #{inspect(state.in_encapsulation)}} caps are required when declaring in_encapsulation as #{inspect(state.in_encapsulation)}"
)
@impl true
def handle_caps(:input, _caps, _ctx, state) do
{:ok, state}
end
@impl true
def handle_process(:input, buffer, ctx, state) when state.in_encapsulation == :ADTS do
%{caps: caps} = ctx.pads.output
timestamp = Map.get(buffer.metadata, :timestamp, state.timestamp)
parse_opts = Map.take(state, [:samples_per_frame, :out_encapsulation, :in_encapsulation])
case Helper.parse_adts(state.leftover <> buffer.payload, caps, timestamp, parse_opts) do
{:ok, {output, leftover, timestamp}} ->
actions = Enum.map(output, fn {action, value} -> {action, {:output, value}} end)
{{:ok, actions ++ [redemand: :output]},
%{state | leftover: leftover, timestamp: timestamp}}
{:error, reason} ->
{{:error, reason}, state}
end
end
@impl true
def handle_process(:input, buffer, ctx, state) when state.in_encapsulation == :none do
timestamp = Helper.next_timestamp(state.timestamp, ctx.pads.output.caps)
buffer = %{buffer | pts: timestamp}
buffer =
case state.out_encapsulation do
:ADTS ->
%Buffer{buffer | payload: Helper.payload_to_adts(buffer.payload, ctx.pads.output.caps)}
_other ->
buffer
end
{{:ok, buffer: {:output, buffer}}, %{state | timestamp: timestamp}}
end
@impl true
def handle_demand(:output, size, :buffers, _ctx, state) do
{{:ok, demand: {:input, size}}, state}
end
end