Current section

Files

Jump to
rockbox_ex_ffi lib rockbox decoder.ex
Raw

lib/rockbox/decoder.ex

defmodule Rockbox.Decoder do
@moduledoc """
Decode an audio file to interleaved-stereo S16LE PCM, one chunk at a time.
Wraps the Rockbox codec engine. The codec state is process-wide, so only one
decoder may decode at a time — opening a second one blocks until the first is
freed. The handle is a NIF resource, freed by the BEAM garbage collector (no
explicit close needed).
"""
@typedoc "Opaque decoder handle (a NIF resource)."
@opaque t :: reference()
@doc """
Open a decoder for the local audio file at `path`. Returns the handle, or
`nil` if the file cannot be opened / no codec recognises it.
"""
@spec open(Path.t()) :: t() | nil
def open(path), do: :rockbox_ffi_nif.decoder_open(to_bin(path))
@doc """
Tags and stream properties (same shape as `Rockbox.Metadata.read/1`).
Returns a map with atom keys (`:codec`, `:title`, `:duration_ms`,
`:sample_rate`, a nested `:replaygain` map, …).
"""
@spec metadata(t()) :: map()
def metadata(d), do: Rockbox.decode_json(:rockbox_ffi_nif.decoder_metadata_json(d))
@doc """
Next buffer of decoded PCM as `{samples, sample_rate}`, or `:eof` at end of
track (or after a codec error — see `finished/1`).
`samples` is a little-endian int16 binary of interleaved stereo PCM (use
`Rockbox.Dsp.binary_to_samples/1` to convert it to a list).
"""
@spec next_chunk(t()) :: {binary(), pos_integer()} | :eof
def next_chunk(d) do
case :rockbox_ffi_nif.decoder_next_chunk(d) do
nil -> :eof
{samples, sample_rate} -> {samples, sample_rate}
end
end
@doc "Request a seek to `ms` milliseconds."
@spec seek_ms(t(), non_neg_integer()) :: :ok
def seek_ms(d, ms), do: nilok(:rockbox_ffi_nif.decoder_seek_ms(d, ms))
@doc "Playback position last reported by the codec, in milliseconds."
@spec elapsed_ms(t()) :: non_neg_integer()
def elapsed_ms(d), do: :rockbox_ffi_nif.decoder_elapsed_ms(d)
@doc """
`{done, code}`: `done` is `true` once the track has ended; `code` is the exit
status (`0` = clean end, negative = codec error), valid only when `done`.
"""
@spec finished(t()) :: {boolean(), integer()}
def finished(d), do: :rockbox_ffi_nif.decoder_finished(d)
defp to_bin(p), do: IO.iodata_to_binary([p])
defp nilok(nil), do: :ok
defp nilok(other), do: other
end