defmodule Membrane.RTP.SequenceNumberTracker do
@moduledoc """
A module used to map 16-bit sequence number to a continuous index without rollovers.
It also detectes repeated and lost packets
"""
require Bitwise
alias Membrane.RTP.Utils
@seq_num_bits 16
@rollover_modulus Bitwise.bsl(1, @seq_num_bits)
@type t() :: %__MODULE__{
highest_seen_index: nil | non_neg_integer()
}
defstruct highest_seen_index: nil
@doc """
Initializes new SequenceNumberTracker
"""
@spec new() :: t()
def new(), do: %__MODULE__{}
@doc """
Main function of the Tracker that returns a difference to the newest package and packet's monotonic index
"""
@spec track(t(), Membrane.RTP.Header.sequence_number_t()) :: {integer(), non_neg_integer(), t()}
def track(%__MODULE__{highest_seen_index: nil} = tracker, 0) do
# Start from roc = 1 to allow late sequence numbers
{1, @rollover_modulus, %__MODULE__{tracker | highest_seen_index: @rollover_modulus}}
end
def track(%__MODULE__{highest_seen_index: nil} = tracker, seq_num) do
{1, seq_num, %__MODULE__{tracker | highest_seen_index: seq_num}}
end
def track(%__MODULE__{highest_seen_index: reference_index} = tracker, seq_num) do
reference_seq_num = rem(reference_index, @rollover_modulus)
reference_roc = div(reference_index, @rollover_modulus)
incoming_roc =
case Utils.from_which_rollover(reference_seq_num, seq_num, @rollover_modulus) do
:current -> reference_roc
:previous -> reference_roc - 1
:next -> reference_roc + 1
end
incoming_index = seq_num + Bitwise.bsl(incoming_roc, @seq_num_bits)
diff = incoming_index - reference_index
tracker =
if diff > 0 do
%__MODULE__{tracker | highest_seen_index: incoming_index}
else
tracker
end
{diff, incoming_index, tracker}
end
end