lib/membrane_h265_plugin/parser/format.ex

defmodule Membrane.H265.Parser.Format do
  @moduledoc """
  Module providing functionalities for preparing H265
  format based on the parsed VPS and SPS NAL units.
  """

  alias Membrane.H265

  @profiles_description [
    main: [profile_idc: 1],
    main_10: [profile_idc: 2],
    main_still_picture: [profile_idc: 3],
    rext: [profile_idc: 4]
  ]

  @doc """
  Prepares the `Membrane.H265.t()` format based on the parsed SPS NALu.
  During the process, the function determines the profile of
  the h265 stream and the picture resolution.
  """
  @spec from_sps(
          sps_nalu :: H265.Parser.NALu.t(),
          output_raw_stream_structure :: H264.stream_structure(),
          options_fields :: [
            framerate: {pos_integer(), pos_integer()},
            output_alignment: :au | :nalu
          ]
        ) :: H265.t()
  def from_sps(sps_nalu, output_raw_stream_format, options_fields) do
    sps = sps_nalu.parsed_fields

    {sub_width_c, sub_height_c} =
      case sps.chroma_format_idc do
        0 -> {1, 1}
        1 -> {2, 2}
        2 -> {2, 1}
        3 -> {1, 1}
      end

    {width, height} =
      if sps.conformance_window_flag == 1 do
        {sps.pic_width_in_luma_samples -
           sub_width_c * (sps.conf_win_right_offset + sps.conf_win_left_offset),
         sps.pic_height_in_luma_samples -
           sub_height_c * (sps.conf_win_bottom_offset + sps.conf_win_top_offset)}
      else
        {sps.pic_width_in_luma_samples, sps.pic_height_in_luma_samples}
      end

    profile = parse_profile(sps_nalu)

    %H265{
      width: width,
      height: height,
      profile: profile,
      framerate: Keyword.get(options_fields, :framerate),
      alignment: Keyword.get(options_fields, :output_alignment),
      nalu_in_metadata?: true,
      stream_structure: output_raw_stream_format
    }
  end

  defp parse_profile(sps_nalu) do
    fields = sps_nalu.parsed_fields

    {profile_name, _constraints_list} =
      Enum.find(@profiles_description, {nil, nil}, fn {_profile_name, constraints_list} ->
        Enum.all?(constraints_list, fn {key, value} ->
          Map.has_key?(fields, key) and fields[key] == value
        end)
      end)

    if profile_name == nil, do: raise("Cannot read the profile name based on SPS's fields.")
    profile_name
  end
end