Skip to content

Commit

Permalink
Create a module for every codec in ExWebRTC.RTP (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala authored Jun 27, 2024
1 parent 8b0af9e commit 838af94
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExWebRTC.RTP.OpusDepayloader do
defmodule ExWebRTC.RTP.Opus.Depayloader do
@moduledoc """
Decapsualtes Opus audio out of RTP packet.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExWebRTC.RTP.OpusPayloader do
defmodule ExWebRTC.RTP.Opus.Payloader do
@moduledoc """
Encapsulates Opus audio packet into an RTP packet.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
defmodule ExWebRTC.RTP.VP8Depayloader do
defmodule ExWebRTC.RTP.VP8.Depayloader do
@moduledoc """
Reassembles VP8 frames from RTP packets.
Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741).
"""
require Logger

alias ExWebRTC.RTP.VP8Payload
alias ExWebRTC.RTP.VP8.Payload

@opaque t() :: %__MODULE__{
current_frame: nil,
Expand All @@ -26,10 +26,10 @@ defmodule ExWebRTC.RTP.VP8Depayloader do
def write(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {:ok, depayloader}

def write(depayloader, packet) do
with {:ok, vp8_payload} <- VP8Payload.parse(packet.payload) do
with {:ok, vp8_payload} <- Payload.parse(packet.payload) do
depayloader =
case {depayloader.current_frame, vp8_payload} do
{nil, %VP8Payload{s: 1, pid: 0}} ->
{nil, %Payload{s: 1, pid: 0}} ->
%{
depayloader
| current_frame: vp8_payload.payload,
Expand All @@ -40,7 +40,7 @@ defmodule ExWebRTC.RTP.VP8Depayloader do
Logger.debug("Dropping vp8 payload as it doesn't start a new frame")
depayloader

{_current_frame, %VP8Payload{s: 1, pid: 0}} ->
{_current_frame, %Payload{s: 1, pid: 0}} ->
Logger.debug("""
Received packet that starts a new frame without finishing the previous frame. \
Dropping previous frame.\
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExWebRTC.RTP.VP8Payload do
defmodule ExWebRTC.RTP.VP8.Payload do
@moduledoc """
Defines VP8 payload structure stored in RTP packet payload.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExWebRTC.RTP.VP8Payloader do
defmodule ExWebRTC.RTP.VP8.Payloader do
@moduledoc """
Encapsulates VP8 video frames into RTP packets.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
defmodule ExWebRTC.RTP.VP8DepayloaderTest do
defmodule ExWebRTC.RTP.VP8.DepayloaderTest do
use ExUnit.Case, async: true

alias ExWebRTC.RTP.{VP8Payload, VP8Depayloader}
alias ExWebRTC.RTP.VP8.{Payload, Depayloader}

test "write/2" do
depayloader = VP8Depayloader.new()
depayloader = Depayloader.new()
# random vp8 data, not necessarily correct
data = <<0, 1, 2, 3>>

# packet with entire frame
vp8_payload = %VP8Payload{n: 0, s: 1, pid: 0, payload: data}
vp8_payload = VP8Payload.serialize(vp8_payload)
vp8_payload = %Payload{n: 0, s: 1, pid: 0, payload: data}
vp8_payload = Payload.serialize(vp8_payload)

packet = ExRTP.Packet.new(vp8_payload, marker: true)

assert {:ok, ^data, %{current_frame: nil, current_timestamp: nil} = depayloader} =
VP8Depayloader.write(depayloader, packet)
Depayloader.write(depayloader, packet)

# packet that doesn't start a new frame
vp8_payload = %VP8Payload{n: 0, s: 0, pid: 0, payload: data}
vp8_payload = VP8Payload.serialize(vp8_payload)
vp8_payload = %Payload{n: 0, s: 0, pid: 0, payload: data}
vp8_payload = Payload.serialize(vp8_payload)

packet = ExRTP.Packet.new(vp8_payload)

assert {:ok, %{current_frame: nil, current_timestamp: nil} = depayloader} =
VP8Depayloader.write(depayloader, packet)
Depayloader.write(depayloader, packet)

# packet that starts a new frame without finishing the previous one
vp8_payload = %VP8Payload{n: 0, s: 1, pid: 0, payload: data}
vp8_payload = VP8Payload.serialize(vp8_payload)
vp8_payload = %Payload{n: 0, s: 1, pid: 0, payload: data}
vp8_payload = Payload.serialize(vp8_payload)

packet = ExRTP.Packet.new(vp8_payload)

assert {:ok, %{current_frame: ^data, current_timestamp: 0} = depayloader} =
VP8Depayloader.write(depayloader, packet)
Depayloader.write(depayloader, packet)

data2 = data <> <<0>>
vp8_payload = %VP8Payload{n: 0, s: 1, pid: 0, payload: data2}
vp8_payload = VP8Payload.serialize(vp8_payload)
vp8_payload = %Payload{n: 0, s: 1, pid: 0, payload: data2}
vp8_payload = Payload.serialize(vp8_payload)

packet = ExRTP.Packet.new(vp8_payload, timestamp: 3000)

assert {:ok, %{current_frame: ^data2, current_timestamp: 3000} = depayloader} =
VP8Depayloader.write(depayloader, packet)
Depayloader.write(depayloader, packet)

# packet with timestamp from a new frame that is not a beginning of this frame
data2 = data
vp8_payload = %VP8Payload{n: 0, s: 0, pid: 0, payload: data2}
vp8_payload = VP8Payload.serialize(vp8_payload)
vp8_payload = %Payload{n: 0, s: 0, pid: 0, payload: data2}
vp8_payload = Payload.serialize(vp8_payload)

packet = ExRTP.Packet.new(vp8_payload, timestamp: 6000)

assert {:ok, %{current_frame: nil, current_timestamp: nil}} =
VP8Depayloader.write(depayloader, packet)
Depayloader.write(depayloader, packet)
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ExWebrtc.Rtp.Vp8PayloadTest do
defmodule ExWebRTC.Rtp.VP8.PayloadTest do
use ExUnit.Case, async: true

alias ExWebRTC.RTP.VP8Payload
alias ExWebRTC.RTP.VP8.Payload

test "parse/1 and serialize/1" do
# test vectors are based on RFC 7741, sec. 4.6
Expand All @@ -14,7 +14,7 @@ defmodule ExWebrtc.Rtp.Vp8PayloadTest do
<<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 1::1, 0::7, 0::1, 17::7, vp8_payload::binary>>

parsed_frame =
%VP8Payload{
%Payload{
n: 0,
s: 1,
pid: 0,
Expand All @@ -26,13 +26,13 @@ defmodule ExWebrtc.Rtp.Vp8PayloadTest do
payload: vp8_payload
}

assert {:ok, parsed_frame} == VP8Payload.parse(frame)
assert frame == VP8Payload.serialize(parsed_frame)
assert {:ok, parsed_frame} == Payload.parse(frame)
assert frame == Payload.serialize(parsed_frame)

# X=0, S=1, PID=0
frame = <<0::1, 0::1, 0::1, 1::1, 0::1, 0::3, vp8_payload::binary>>

parsed_frame = %VP8Payload{
parsed_frame = %Payload{
n: 0,
s: 1,
pid: 0,
Expand All @@ -44,15 +44,15 @@ defmodule ExWebrtc.Rtp.Vp8PayloadTest do
payload: vp8_payload
}

assert {:ok, parsed_frame} == VP8Payload.parse(frame)
assert frame == VP8Payload.serialize(parsed_frame)
assert {:ok, parsed_frame} == Payload.parse(frame)
assert frame == Payload.serialize(parsed_frame)

# X=1, S=1, I=1, L=1, T=1, K=1, M=1, picture_id=4711
frame =
<<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 1::1, 1::1, 1::1, 1::1, 0::4, 1::1, 4711::15, 1::8,
1::2, 1::1, 1::5, vp8_payload::binary>>

parsed_frame = %VP8Payload{
parsed_frame = %Payload{
n: 0,
s: 1,
pid: 0,
Expand All @@ -64,33 +64,33 @@ defmodule ExWebrtc.Rtp.Vp8PayloadTest do
payload: vp8_payload
}

assert {:ok, parsed_frame} == VP8Payload.parse(frame)
assert frame == VP8Payload.serialize(parsed_frame)
assert {:ok, parsed_frame} == Payload.parse(frame)
assert frame == Payload.serialize(parsed_frame)

assert {:error, :invalid_packet} = VP8Payload.parse(<<>>)
assert {:error, :invalid_packet} = Payload.parse(<<>>)

# X=0 and no vp8_payload
assert {:error, :invalid_packet} =
VP8Payload.parse(<<0::1, 0::1, 0::1, 1::1, 0::1, 0::3>>)
Payload.parse(<<0::1, 0::1, 0::1, 1::1, 0::1, 0::3>>)

# X=1, I=1 picture_id=1 and no vp8_payload
frame = <<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 1::1, 0::7, 0::1, 1::7>>
assert {:error, :invalid_packet} = VP8Payload.parse(frame)
assert {:error, :invalid_packet} = Payload.parse(frame)

# invalid reserved bit
assert {:error, :invalid_packet} =
VP8Payload.parse(<<0::1, 1::1, 0::1, 1::1, 1::1, 0::3>>)
Payload.parse(<<0::1, 1::1, 0::1, 1::1, 1::1, 0::3>>)

# missing picture id
missing_picture_id = <<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 1::1, 0::7>>
assert {:error, :invalid_packet} = VP8Payload.parse(missing_picture_id)
assert {:error, :invalid_packet} = Payload.parse(missing_picture_id)

# missing tl0picidx
missing_tl0picidx = <<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 0::1, 1::1, 0::6>>
assert {:error, :invalid_packet} = VP8Payload.parse(missing_tl0picidx)
assert {:error, :invalid_packet} = Payload.parse(missing_tl0picidx)

# missing tidykeyidx
missing_tidykeyidx = <<1::1, 0::1, 0::1, 1::1, 0::1, 0::3, 0::2, 1::1, 0::1, 0::4>>
assert {:error, :invalid_packet} = VP8Payload.parse(missing_tidykeyidx)
assert {:error, :invalid_packet} = Payload.parse(missing_tidykeyidx)
end
end
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
defmodule ExWebRTC.RTP.VP8PayloaderTest do
defmodule ExWebRTC.RTP.VP8.PayloaderTest do
use ExUnit.Case, async: true

alias ExWebRTC.Media.IVF.Reader
alias ExWebRTC.RTP.VP8Payloader
alias ExWebRTC.RTP.VP8.Payloader

test "payload vp8 video" do
# video frames in the fixture are mostly 500+ bytes
vp8_payloader = VP8Payloader.new(200)
vp8_payloader = Payloader.new(200)
{:ok, _header, ivf_reader} = Reader.open("test/fixtures/ivf/vp8_correct.ivf")

for _i <- 0..28, reduce: vp8_payloader do
vp8_payloader ->
{:ok, frame} = Reader.next_frame(ivf_reader)
{rtp_packets, vp8_payloader} = VP8Payloader.payload(vp8_payloader, frame.data)
{rtp_packets, vp8_payloader} = Payloader.payload(vp8_payloader, frame.data)

# assert all packets but last are 200 bytes
rtp_packets
Expand Down

0 comments on commit 838af94

Please sign in to comment.