defmodule Xav.Decoder do @moduledoc """ Audio/video decoder. """ @typedoc """ Supported codecs. """ @type codec() :: :opus | :vp8 | :h264 | :h265 @type t() :: reference() @typedoc """ Opts that can be passed to `new/2`. """ @type opts :: [ out_format: Xav.Frame.format(), out_sample_rate: integer(), out_channels: integer(), out_width: Xav.Frame.width(), out_height: Xav.Frame.height() ] @doc """ Creates a new decoder. `opts` can be used to specify desired output parameters. E.g. if you want to change audio samples format just pass: ```elixir [out_format: :f32] ``` or video samples format: ```elixir [out_format: :rgb24] ``` Audio samples are always in the packed form - samples from different channels are interleaved in the same, single binary: ``` <> ``` An alternative would be to return a list of binaries, where each binary represents different channel: ``` [ <>, <>, <> ] ``` """ @spec new(codec(), opts()) :: t() def new(codec, opts \\ []) do out_format = opts[:out_format] out_sample_rate = opts[:out_sample_rate] || 0 out_channels = opts[:out_channels] || 0 out_width = opts[:out_width] || -1 out_height = opts[:out_height] || -1 Xav.Decoder.NIF.new(codec, out_format, out_sample_rate, out_channels, out_width, out_height) end @doc """ Decodes an audio/video frame. Some video frames are meant for decoder only and will not contain actual video samples. Some audio frames might require more data to be converted to the desired output format. In both cases, `:ok` term is returned and more data needs to be provided. """ @spec decode(t(), binary(), pts: integer(), dts: integer()) :: :ok | {:ok, Xav.Frame.t()} | {:error, atom()} def decode(decoder, data, opts \\ []) do pts = opts[:pts] || 0 dts = opts[:dts] || 0 case Xav.Decoder.NIF.decode(decoder, data, pts, dts) do :ok -> :ok {:ok, {data, format, width, height, pts}} -> format = normalize_format(format) {:ok, Xav.Frame.new(data, format, width, height, pts)} # Sometimes, audio converter might not return data immediately. {:ok, {"", _format, _samples, _pts}} -> :ok {:ok, {data, format, samples, pts}} -> format = normalize_format(format) {:ok, Xav.Frame.new(data, format, samples, pts)} {:error, _reason} = error -> error end end @doc """ Flushes the decoder. Flushing signals end of stream and forces the decoder to return the buffered frames if there're any. """ @spec flush(t()) :: {:ok, [Xav.Frame.t()]} | {:error, atom()} def flush(decoder) do with {:ok, frames} <- Xav.Decoder.NIF.flush(decoder) do frames = Enum.map(frames, fn {data, format, width, height, pts} -> Xav.Frame.new(data, format, width, height, pts) end) {:ok, frames} end end @doc """ Same as `flush/1` but raises an exception on error. """ def flush!(decoder) do case flush(decoder) do {:ok, frames} -> frames {:error, reason} -> raise "Failed to flush decoder: #{inspect(reason)}" end end # Use the same formats as Nx defp normalize_format(:flt), do: :f32 defp normalize_format(:dbl), do: :f64 defp normalize_format(other), do: other end