defmodule Membrane.RTP.PayloadFormat do @moduledoc """ This module contains utilities for resolving RTP default payload types, encoding names, clock rates and (de)payloaders. """ require Membrane.RTP alias Membrane.RTP @app :membrane_rtp_format @format_env :__membrane_format @pt_env :__membrane_payload_type_mapping @payload_types_specs %{ 0 => %{encoding_name: :PCMU, clock_rate: 8_000}, 3 => %{encoding_name: :GSM, clock_rate: 8_000}, 4 => %{encoding_name: :G732, clock_rate: 8_000}, 5 => %{encoding_name: :DVI4, clock_rate: 8_000}, 6 => %{encoding_name: :DVI4, clock_rate: 16_000}, 7 => %{encoding_name: :LPC, clock_rate: 8_000}, 8 => %{encoding_name: :PCMA, clock_rate: 8_000}, 9 => %{encoding_name: :G722, clock_rate: 8_000}, 10 => %{encoding_name: :L16, clock_rate: 44_100}, 11 => %{encoding_name: :L16, clock_rate: 44_100}, 12 => %{encoding_name: :QCELP, clock_rate: 8_000}, 13 => %{encoding_name: :CN, clock_rate: 8_000}, 14 => %{encoding_name: :MPA, clock_rate: 90_000}, 15 => %{encoding_name: :G728, clock_rate: 8_000}, 16 => %{encoding_name: :DVI4, clock_rate: 11_025}, 17 => %{encoding_name: :DVI4, clock_rate: 22_050}, 18 => %{encoding_name: :G729, clock_rate: 8_000}, 25 => %{encoding_name: :CELB, clock_rate: 90_000}, 26 => %{encoding_name: :JPEG, clock_rate: 90_000}, 28 => %{encoding_name: :NV, clock_rate: 90_000}, 31 => %{encoding_name: :H261, clock_rate: 90_000}, 32 => %{encoding_name: :MPV, clock_rate: 90_000}, 33 => %{encoding_name: :MP2T, clock_rate: 90_000}, 34 => %{encoding_name: :H263, clock_rate: 90_000} } @enforce_keys [:encoding_name] defstruct @enforce_keys ++ [ payload_type: nil, payloader: nil, depayloader: nil, keyframe_detector: nil, frame_detector: nil ] @type t :: %__MODULE__{ encoding_name: RTP.encoding_name(), payload_type: RTP.payload_type() | nil, payloader: module | nil, depayloader: module | nil, keyframe_detector: (binary() -> boolean()) | nil, frame_detector: (binary() -> boolean()) | nil } @doc false @spec register_static_formats() :: :ok def register_static_formats() do @payload_types_specs |> Enum.group_by(fn {_pt, specs} -> specs.encoding_name end, fn {pt, _specs} -> pt end) |> Enum.each(fn {name, [pt]} -> register(%__MODULE__{encoding_name: name, payload_type: pt}) _ambiguous -> :ok end) end @doc """ Returns encoding name and clock rate for given payload type, if registered. """ @spec get_payload_type_mapping(RTP.payload_type()) :: %{ optional(:encoding_name) => RTP.encoding_name(), optional(:clock_rate) => RTP.clock_rate() } def get_payload_type_mapping(payload_type) when RTP.is_payload_type_static(payload_type) do Map.fetch!(@payload_types_specs, payload_type) end def get_payload_type_mapping(payload_type) when RTP.is_payload_type_dynamic(payload_type) do get_env(@pt_env, payload_type, %{}) end @doc """ Registers default encoding name and clock rate for a dynamic payload_type """ @spec register_payload_type_mapping( RTP.dynamic_payload_type(), RTP.encoding_name(), RTP.clock_rate() ) :: :ok | no_return() def register_payload_type_mapping(payload_type, encoding_name, clock_rate) when RTP.is_payload_type_dynamic(payload_type) do case fetch_env(@pt_env, payload_type) do {:ok, payload_format} -> raise "RTP payload type #{payload_type} already registered: #{inspect(payload_format)}" :error -> put_env(@pt_env, payload_type, %{encoding_name: encoding_name, clock_rate: clock_rate}) end end @doc """ Returns payload format registered for given encoding name. """ @spec get(RTP.encoding_name()) :: t def get(encoding_name) do get_env(@format_env, encoding_name, %__MODULE__{encoding_name: encoding_name}) end @doc """ Registers payload format. Raises if some payload format field was already registered and set to different value. """ @spec register(t) :: :ok | no_return def register(%__MODULE__{encoding_name: encoding_name} = payload_format) do payload_format = get_env(@format_env, encoding_name, %{}) |> Map.merge(payload_format, &merge_format(encoding_name, &1, &2, &3)) put_env(@format_env, encoding_name, payload_format) end defp merge_format(_name, _k, nil, v), do: v defp merge_format(_name, _k, v, nil), do: v defp merge_format(_name, _k, v, v), do: v defp merge_format(name, k, v1, v2) do raise "Cannot register RTP payload format #{name} field #{k} to #{inspect(v2)}, " <> "already registered to #{inspect(v1)}." end defp put_env(env_key, key, value) do env = Application.get_env(@app, env_key, %{}) Application.put_env(@app, env_key, Map.put(env, key, value)) end defp fetch_env(env_key, key) do Application.get_env(@app, env_key, %{}) |> Map.fetch(key) end defp get_env(env_key, key, default) do case fetch_env(env_key, key) do {:ok, value} -> value :error -> default end end end