Packages
membrane_rtp_plugin
0.5.1
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.0
0.29.1
0.29.0
0.28.0
0.27.1
0.27.0
0.26.0
0.25.0
0.24.1
0.24.0
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.1
0.17.0
0.16.0
0.15.0
0.15.0-rc.1
0.14.0
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1-alpha.3
0.7.1-alpha.2
0.7.0-alpha.2
0.7.0-alpha.1
0.7.0-alpha
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0-alpha
Membrane Multimedia Framework plugin for RTP
Current section
Files
Jump to
Current section
Files
lib/membrane/rtp/serializer.ex
defmodule Membrane.RTP.Serializer do
@moduledoc """
Serializes RTP payload to RTP packets by adding the RTP header to each of them.
Accepts the following metadata under `:rtp` key: `:marker`, `:csrcs`, `:extension`.
See `Membrane.RTP.Header` for their meaning and specifications.
"""
use Membrane.Filter
alias Membrane.{Buffer, RTP, RemoteStream, Payload, Time}
@max_seq_num 65535
@max_timestamp 0xFFFFFFFF
def_input_pad :input, caps: RTP, demand_unit: :buffers
def_output_pad :output, caps: {RemoteStream, type: :packetized, content_format: RTP}
def_options ssrc: [spec: RTP.ssrc_t()],
payload_type: [spec: RTP.payload_type_t()],
clock_rate: [spec: RTP.clock_rate_t()],
alignment: [
default: 1,
spec: pos_integer(),
description: """
Number of bytes that each packet should be aligned to.
Alignment is achieved by adding RTP padding.
"""
]
defmodule State do
@moduledoc false
use Bunch.Access
defstruct sequence_number: 0,
init_timestamp: 0,
any_buffer_sent?: false,
stats_acc: %{
clock_rate: 0,
timestamp: 0,
rtp_timestamp: 0,
sender_packet_count: 0,
sender_octet_count: 0
}
@type t :: %__MODULE__{
sequence_number: non_neg_integer(),
init_timestamp: non_neg_integer(),
any_buffer_sent?: boolean(),
stats_acc: %{}
}
end
@impl true
def handle_init(options) do
state = %State{
sequence_number: Enum.random(0..@max_seq_num),
init_timestamp: Enum.random(0..@max_timestamp)
}
state = state |> put_in([:stats_acc, :clock_rate], options.clock_rate)
{:ok, Map.merge(Map.from_struct(options), state)}
end
@impl true
def handle_caps(:input, _caps, _ctx, state) do
caps = %RemoteStream{type: :packetized, content_format: RTP}
{{:ok, caps: {:output, caps}}, state}
end
@impl true
def handle_demand(:output, size, :buffers, _ctx, state) do
{{:ok, demand: {:input, size}}, state}
end
@impl true
def handle_process(:input, %Buffer{payload: payload, metadata: metadata} = buffer, _ctx, state) do
state = update_counters(buffer, state)
{rtp_metadata, metadata} = Map.pop(metadata, :rtp, %{})
%{timestamp: timestamp} = metadata
rtp_offset = timestamp |> Ratio.mult(state.clock_rate) |> Membrane.Time.to_seconds()
rtp_timestamp = rem(state.init_timestamp + rtp_offset, @max_timestamp + 1)
header = %RTP.Header{
ssrc: state.ssrc,
marker: Map.get(rtp_metadata, :marker, false),
payload_type: state.payload_type,
timestamp: rtp_timestamp,
sequence_number: state.sequence_number,
csrcs: Map.get(rtp_metadata, :csrcs, []),
extension: Map.get(rtp_metadata, :extension)
}
packet = %RTP.Packet{header: header, payload: payload}
payload = RTP.Packet.serialize(packet, align_to: state.alignment)
buffer = %Buffer{payload: payload, metadata: metadata}
state = Map.update!(state, :sequence_number, &rem(&1 + 1, @max_seq_num + 1))
state = %{
state
| any_buffer_sent?: true,
stats_acc: %{state.stats_acc | timestamp: Time.vm_time(), rtp_timestamp: rtp_timestamp}
}
{{:ok, buffer: {:output, buffer}}, state}
end
@impl true
def handle_other(:send_stats, _ctx, state) do
stats = get_stats(state)
state = %{state | any_buffer_sent?: false}
{{:ok, notify: {:serializer_stats, stats}}, state}
end
@spec get_stats(State.t()) :: %{} | :no_stats
defp get_stats(%State{any_buffer_sent?: false}), do: :no_stats
defp get_stats(%State{stats_acc: stats}), do: stats
defp update_counters(%Buffer{payload: payload}, state) do
state
|> update_in(
[:stats_acc, :sender_octet_count],
&(&1 + Payload.size(payload))
)
|> update_in([:stats_acc, :sender_packet_count], &(&1 + 1))
end
end