Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the structs in the API, add docs #104

Merged
merged 5 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
461 changes: 336 additions & 125 deletions lib/ex_webrtc/peer_connection.ex

Large diffs are not rendered by default.

72 changes: 50 additions & 22 deletions lib/ex_webrtc/rtp_receiver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ defmodule ExWebRTC.RTPReceiver do
alias ExWebRTC.{MediaStreamTrack, Utils, RTPCodecParameters}
alias __MODULE__.{NACKGenerator, ReportRecorder}

@type t() :: %__MODULE__{
@type id() :: integer()

@typedoc false
@type receiver() :: %{
id: id(),
track: MediaStreamTrack.t(),
codec: RTPCodecParameters.t() | nil,
ssrc: non_neg_integer() | nil,
Expand All @@ -19,42 +23,64 @@ defmodule ExWebRTC.RTPReceiver do
nack_generator: NACKGenerator.t()
}

@enforce_keys [:track, :codec, :report_recorder]
defstruct [
ssrc: nil,
bytes_received: 0,
packets_received: 0,
markers_received: 0,
nack_generator: %NACKGenerator{}
] ++ @enforce_keys
@typedoc """
Struct representing a receiver.

The fields mostly match these of [RTCRtpReceiver](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver),
except for:
* `id` - to uniquely identify the receiver.
* `codec` - codec this receiver is expected to receive.
"""
@type t() :: %__MODULE__{
id: id(),
track: MediaStreamTrack.t(),
codec: RTPCodecParameters.t() | nil
}

@enforce_keys [:id, :track, :codec]
defstruct @enforce_keys

@doc false
@spec to_struct(receiver()) :: t()
def to_struct(receiver) do
receiver
|> Map.take([:id, :track, :codec])
|> then(&struct!(__MODULE__, &1))
end

@doc false
@spec new(MediaStreamTrack.t(), RTPCodecParameters.t() | nil) :: t()
@spec new(MediaStreamTrack.t(), RTPCodecParameters.t() | nil) :: receiver()
def new(track, codec) do
report_recorder = %ReportRecorder{
clock_rate: codec && codec.clock_rate
}

%__MODULE__{
%{
id: Utils.generate_id(),
track: track,
codec: codec,
report_recorder: report_recorder
ssrc: nil,
bytes_received: 0,
packets_received: 0,
markers_received: 0,
report_recorder: report_recorder,
nack_generator: %NACKGenerator{}
}
end

@doc false
@spec update(t(), RTPCodecParameters.t() | nil) :: t()
@spec update(receiver(), RTPCodecParameters.t() | nil) :: receiver()
def update(receiver, codec) do
report_recorder = %ReportRecorder{
receiver.report_recorder
| clock_rate: codec && codec.clock_rate
}

%__MODULE__{receiver | codec: codec, report_recorder: report_recorder}
%{receiver | codec: codec, report_recorder: report_recorder}
end

@doc false
@spec receive_packet(t(), ExRTP.Packet.t(), non_neg_integer()) :: t()
@spec receive_packet(receiver(), ExRTP.Packet.t(), non_neg_integer()) :: receiver()
def receive_packet(receiver, packet, size) do
if packet.payload_type != receiver.codec.payload_type do
Logger.warning("Received packet with unexpected payload_type \
Expand All @@ -65,7 +91,7 @@ defmodule ExWebRTC.RTPReceiver do
nack_generator = NACKGenerator.record_packet(receiver.nack_generator, packet)

# TODO assign ssrc when applying local/remote description.
%__MODULE__{
%{
receiver
| ssrc: packet.ssrc,
bytes_received: receiver.bytes_received + size,
Expand All @@ -76,7 +102,8 @@ defmodule ExWebRTC.RTPReceiver do
}
end

@spec receive_rtx(t(), ExRTP.Packet.t(), non_neg_integer()) :: {:ok, ExRTP.Packet.t()} | :error
@spec receive_rtx(receiver(), ExRTP.Packet.t(), non_neg_integer()) ::
{:ok, ExRTP.Packet.t()} | :error
def receive_rtx(receiver, rtx_packet, apt) do
with <<seq_no::16, rest::binary>> <- rtx_packet.payload,
ssrc when ssrc != nil <- receiver.ssrc do
Expand All @@ -94,23 +121,24 @@ defmodule ExWebRTC.RTPReceiver do
end
end

@spec receive_report(t(), ExRTCP.Packet.SenderReport.t()) :: t()
@spec receive_report(receiver(), ExRTCP.Packet.SenderReport.t()) :: receiver()
def receive_report(receiver, report) do
report_recorder = ReportRecorder.record_report(receiver.report_recorder, report)

%__MODULE__{receiver | report_recorder: report_recorder}
%{receiver | report_recorder: report_recorder}
end

@doc false
@spec update_sender_ssrc(t(), non_neg_integer()) :: t()
@spec update_sender_ssrc(receiver(), non_neg_integer()) :: receiver()
def update_sender_ssrc(receiver, ssrc) do
report_recorder = %ReportRecorder{receiver.report_recorder | sender_ssrc: ssrc}
nack_generator = %NACKGenerator{receiver.nack_generator | sender_ssrc: ssrc}
%__MODULE__{receiver | report_recorder: report_recorder, nack_generator: nack_generator}

%{receiver | report_recorder: report_recorder, nack_generator: nack_generator}
end

@doc false
@spec get_stats(t(), non_neg_integer()) :: map()
@spec get_stats(receiver(), non_neg_integer()) :: map()
def get_stats(receiver, timestamp) do
%{
id: receiver.track.id,
Expand Down
5 changes: 1 addition & 4 deletions lib/ex_webrtc/rtp_receiver/nack_generator.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExWebRTC.RTPReceiver.NACKGenerator do
@moduledoc nil
@moduledoc false
# for now, it mimics the Pion implementation, but there's some issues and remarks
# 1) NACKs are send at constant interval
# 2) no timing rules (like rtt) are taken into account
Expand All @@ -25,9 +25,6 @@ defmodule ExWebRTC.RTPReceiver.NACKGenerator do
last_sn: nil,
max_nack: @max_nack

@doc """
Records incoming RTP Packet.
"""
@spec record_packet(t(), ExRTP.Packet.t()) :: t()
def record_packet(generator, packet)

Expand Down
9 changes: 6 additions & 3 deletions lib/ex_webrtc/rtp_receiver/report_recorder.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExWebRTC.RTPReceiver.ReportRecorder do
@moduledoc nil
@moduledoc false
# based on https://datatracker.ietf.org/doc/html/rfc3550#section-6.4.1

import Bitwise
Expand Down Expand Up @@ -40,7 +40,8 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do
total_lost: 0

@doc """
Records incoming RTP Packet.
Records incoming RTP packet.

`time` parameter accepts output of `System.monotonic_time()` as a value.
"""
@spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t()
Expand Down Expand Up @@ -68,6 +69,7 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do

@doc """
Records incoming RTCP Sender Report.

`time` parameter accepts output of `System.monotonic_time()` as a value.
"""
@spec record_report(t(), ExRTCP.Packet.SenderReport.t(), integer()) :: t()
Expand All @@ -79,7 +81,8 @@ defmodule ExWebRTC.RTPReceiver.ReportRecorder do
end

@doc """
Creates an RTCP Receiver Report.
Generates RTCP Receiver Report.

`time` parameter accepts output of `System.monotonic_time()` as a value.
"""
@spec get_report(t(), integer()) :: {:ok, ReceiverReport.t(), t()} | {:error, term()}
Expand Down
65 changes: 38 additions & 27 deletions lib/ex_webrtc/rtp_sender.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ defmodule ExWebRTC.RTPSender do

@type id() :: integer()

@type t() :: %__MODULE__{
@typedoc false
@type sender() :: %{
id: id(),
track: MediaStreamTrack.t() | nil,
codec: RTPCodecParameters.t() | nil,
Expand All @@ -28,21 +29,30 @@ defmodule ExWebRTC.RTPSender do
nack_responder: NACKResponder.t()
}

@enforce_keys [:id, :report_recorder, :nack_responder]
defstruct @enforce_keys ++
[
:track,
:codec,
:mid,
:pt,
:rtx_pt,
:ssrc,
:rtx_ssrc,
rtp_hdr_exts: %{},
packets_sent: 0,
bytes_sent: 0,
markers_sent: 0
]
@typedoc """
Struct representing a sender.

The fields mostly match these of [RTCRtpSender](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender),
except for:
* `id` - to uniquely identify the sender.
* `codec` - codec this sender is going to send.
"""
@type t() :: %__MODULE__{
id: id(),
track: MediaStreamTrack.t() | nil,
codec: RTPCodecParameters.t() | nil
}

@enforce_keys [:id, :track, :codec]
defstruct @enforce_keys

@doc false
@spec to_struct(sender()) :: t()
def to_struct(sender) do
sender
|> Map.take([:id, :track, :codec])
|> then(&struct!(__MODULE__, &1))
end

@doc false
@spec new(
Expand All @@ -53,15 +63,15 @@ defmodule ExWebRTC.RTPSender do
String.t() | nil,
non_neg_integer() | nil,
non_neg_integer() | nil
) :: t()
) :: sender()
def new(track, codec, rtx_codec, rtp_hdr_exts, mid \\ nil, ssrc, rtx_ssrc) do
# convert to a map to be able to find extension id using extension uri
rtp_hdr_exts = Map.new(rtp_hdr_exts, fn extmap -> {extmap.uri, extmap} end)
# TODO: handle cases when codec == nil (no valid codecs after negotiation)
pt = if codec != nil, do: codec.payload_type, else: nil
rtx_pt = if rtx_codec != nil, do: rtx_codec.payload_type, else: nil

%__MODULE__{
%{
id: Utils.generate_id(),
track: track,
codec: codec,
Expand All @@ -71,15 +81,18 @@ defmodule ExWebRTC.RTPSender do
ssrc: ssrc,
rtx_ssrc: rtx_ssrc,
mid: mid,
packets_sent: 0,
bytes_sent: 0,
markers_sent: 0,
report_recorder: %ReportRecorder{clock_rate: codec && codec.clock_rate},
nack_responder: %NACKResponder{}
}
end

@doc false
@spec update(t(), String.t(), RTPCodecParameters.t() | nil, RTPCodecParameters.t() | nil, [
@spec update(sender(), String.t(), RTPCodecParameters.t() | nil, RTPCodecParameters.t() | nil, [
Extmap.t()
]) :: t()
]) :: sender()
def update(sender, mid, codec, rtx_codec, rtp_hdr_exts) do
if sender.mid != nil and mid != sender.mid, do: raise(ArgumentError)
# convert to a map to be able to find extension id using extension uri
Expand All @@ -93,7 +106,7 @@ defmodule ExWebRTC.RTPSender do
| clock_rate: codec && codec.clock_rate
}

%__MODULE__{
%{
sender
| mid: mid,
codec: codec,
Expand All @@ -104,11 +117,8 @@ defmodule ExWebRTC.RTPSender do
}
end

# Prepares packet for sending i.e.:
# * assigns SSRC, pt, mid
# * serializes to binary
@doc false
@spec send_packet(t(), ExRTP.Packet.t(), boolean()) :: {binary(), t()}
@spec send_packet(sender(), ExRTP.Packet.t(), boolean()) :: {binary(), sender()}
def send_packet(sender, packet, rtx?) do
%Extmap{} = mid_extmap = Map.fetch!(sender.rtp_hdr_exts, @mid_uri)

Expand Down Expand Up @@ -146,7 +156,8 @@ defmodule ExWebRTC.RTPSender do
end

@doc false
@spec receive_nack(t(), ExRTCP.Packet.TransportFeedback.NACK.t()) :: {[ExRTP.Packet.t()], t()}
@spec receive_nack(sender(), ExRTCP.Packet.TransportFeedback.NACK.t()) ::
{[ExRTP.Packet.t()], sender()}
def receive_nack(sender, nack) do
{packets, nack_responder} = NACKResponder.get_rtx(sender.nack_responder, nack)
sender = %{sender | nack_responder: nack_responder}
Expand All @@ -155,7 +166,7 @@ defmodule ExWebRTC.RTPSender do
end

@doc false
@spec get_stats(t(), non_neg_integer()) :: map()
@spec get_stats(sender(), non_neg_integer()) :: map()
def get_stats(sender, timestamp) do
%{
timestamp: timestamp,
Expand Down
4 changes: 1 addition & 3 deletions lib/ex_webrtc/rtp_sender/nack_responder.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExWebRTC.RTPSender.NACKResponder do
@moduledoc nil
@moduledoc false

alias ExRTP.Packet
alias ExRTCP.Packet.TransportFeedback.NACK
Expand All @@ -14,7 +14,6 @@ defmodule ExWebRTC.RTPSender.NACKResponder do
defstruct packets: %{},
seq_no: Enum.random(0..0xFFFF)

@doc false
@spec record_packet(t(), Packet.t()) :: t()
def record_packet(responder, packet) do
key = rem(packet.sequence_number, @max_packets)
Expand All @@ -23,7 +22,6 @@ defmodule ExWebRTC.RTPSender.NACKResponder do
%__MODULE__{responder | packets: packets}
end

@doc false
@spec get_rtx(t(), NACK.t()) :: {[ExRTP.Packet.t()], t()}
def get_rtx(responder, nack) do
seq_nos = NACK.to_sequence_numbers(nack)
Expand Down
9 changes: 5 additions & 4 deletions lib/ex_webrtc/rtp_sender/report_recorder.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExWebRTC.RTPSender.ReportRecorder do
@moduledoc nil
@moduledoc false

import Bitwise

Expand Down Expand Up @@ -30,7 +30,8 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do
octet_count: 0

@doc """
Records outgoing RTP Packet.
Records incoming RTP packet.

`time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units).
"""
@spec record_packet(t(), ExRTP.Packet.t(), integer()) :: t()
Expand Down Expand Up @@ -81,9 +82,9 @@ defmodule ExWebRTC.RTPSender.ReportRecorder do
end

@doc """
Creates an RTCP Sender Report.
`time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units).
Generates a RTCP Sender Report.

`time` parameter accepts output of `System.os_time(:native)` as a value (UNIX timestamp in :native units).
This function can be called only if at least one packet has been recorded,
otherwise it will raise.
"""
Expand Down
Loading