lib/membrane_h264_ffmpeg/parser/decoder_configuration_record.ex

defmodule Membrane.H264.FFmpeg.Parser.DecoderConfiguration do
  @moduledoc """
  Utility functions for parsing AVC Configuration Record
  """
  @enforce_keys [
    :sps,
    :pps,
    :avc_profile_indication,
    :avc_level,
    :profile_compatibility,
    :length_size
  ]
  defstruct @enforce_keys

  @type t() :: %__MODULE__{
          sps: [binary()],
          pps: [binary()],
          avc_profile_indication: non_neg_integer(),
          profile_compatibility: non_neg_integer(),
          avc_level: non_neg_integer(),
          length_size: non_neg_integer()
        }

  @spec parse(binary()) :: {:ok, t()} | {:error, any()}
  def parse(
        <<1::8, avc_profile_indication::8, profile_compatibility::8, avc_level::8, 0b111111::6,
          length_size::2, 0b111::3, rest::bitstring>>
      ) do
    {sps, rest} = parse_sps(rest)
    {pps, _rest} = parse_pps(rest)

    %__MODULE__{
      sps: sps,
      pps: pps,
      avc_profile_indication: avc_profile_indication,
      profile_compatibility: profile_compatibility,
      avc_level: avc_level,
      length_size: length_size
    }
    |> then(&{:ok, &1})
  end

  def parse(_data), do: {:error, :unknown_pattern}

  defp parse_sps(<<num_of_sps::5, rest::bitstring>>) do
    do_parse_array(num_of_sps, rest)
  end

  defp parse_pps(<<num_of_pps::8, rest::bitstring>>), do: do_parse_array(num_of_pps, rest)

  defp do_parse_array(amount, rest, acc \\ [])
  defp do_parse_array(0, rest, acc), do: {Enum.reverse(acc), rest}

  defp do_parse_array(remaining, <<size::16, data::binary-size(size), rest::bitstring>>, acc),
    do: do_parse_array(remaining - 1, rest, [data | acc])
end