lib/membrane/rtp/sequence_number_tracker.ex

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