Current section
Files
Jump to
Current section
Files
lib/rtp/payload_format.ex
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}
}
@type payload_type_mapping :: %{
RTP.payload_type() => %{
encoding_name: RTP.encoding_name(),
clock_rate: RTP.clock_rate()
}
}
@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
@doc """
Quality of life function that makes best effort resolution of `PayloadFormat`, payload type and clock rate based
on provided arguments. Prefers provided values over registered defaults.
"""
@spec resolve(
encoding_name: RTP.encoding_name() | nil,
payload_type: RTP.payload_type() | nil,
clock_rate: RTP.clock_rate() | nil,
payload_type_mapping: payload_type_mapping() | nil
) :: %{
payload_format: t() | nil,
payload_type: RTP.payload_type() | nil,
clock_rate: RTP.clock_rate() | nil
}
def resolve(args) do
{encoding_name, payload_type} =
case {args[:encoding_name], args[:payload_type]} do
{nil, nil} ->
{nil, nil}
{nil, payload_type} ->
{resolve_encoding_name(payload_type, args[:payload_type_mapping]), payload_type}
{encoding_name, nil} ->
{encoding_name, resolve_payload_type(encoding_name, args[:payload_type_mapping])}
{encoding_name, payload_type} ->
{encoding_name, payload_type}
end
payload_format = resolve_payload_format(encoding_name, payload_type)
clock_rate = resolve_clock_rate(args[:clock_rate], payload_type, args[:payload_type_mapping])
%{payload_format: payload_format, payload_type: payload_type, clock_rate: clock_rate}
end
@spec resolve_encoding_name(RTP.payload_type(), payload_type_mapping() | nil) ::
RTP.encoding_name() | nil
defp resolve_encoding_name(payload_type, nil) do
case get_payload_type_mapping(payload_type) do
%{encoding_name: encoding_name} -> encoding_name
%{} -> nil
end
end
defp resolve_encoding_name(payload_type, payload_type_mapping) do
case Map.get(payload_type_mapping, payload_type) do
%{encoding_name: encoding_name} -> encoding_name
nil -> resolve_encoding_name(payload_type, nil)
end
end
@spec resolve_payload_type(RTP.encoding_name(), payload_type_mapping() | nil) ::
RTP.payload_type() | nil
defp resolve_payload_type(encoding_name, nil) do
get(encoding_name).payload_type
end
defp resolve_payload_type(encoding_name, payload_type_mapping) do
Enum.find(payload_type_mapping, fn {_payload_type, payload_type_specs} ->
payload_type_specs.encoding_name == encoding_name
end)
|> case do
{payload_type, _payload_type_specs} -> payload_type
nil -> resolve_payload_type(encoding_name, nil)
end
end
@spec resolve_payload_format(RTP.encoding_name() | nil, RTP.payload_type() | nil) :: t() | nil
defp resolve_payload_format(nil, _payload_type) do
nil
end
defp resolve_payload_format(encoding_name, payload_type) do
payload_format = get(encoding_name)
case payload_type do
nil -> payload_format
payload_type -> %{payload_format | payload_type: payload_type}
end
end
@spec resolve_clock_rate(
RTP.clock_rate() | nil,
RTP.payload_type() | nil,
payload_type_mapping() | nil
) :: RTP.clock_rate() | nil
defp resolve_clock_rate(nil, nil, _payload_type_mapping) do
nil
end
defp resolve_clock_rate(nil, payload_type, nil) do
case get_payload_type_mapping(payload_type) do
%{clock_rate: clock_rate} -> clock_rate
%{} -> nil
end
end
defp resolve_clock_rate(nil, payload_type, payload_type_mapping) do
case Map.get(payload_type_mapping, payload_type) do
%{clock_rate: clock_rate} -> clock_rate
nil -> resolve_clock_rate(nil, payload_type, nil)
end
end
defp resolve_clock_rate(clock_rate, _payload_type, _payload_type_mapping) do
clock_rate
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