From 8f7866e60959bf3b05425ee895c51dd562ea8216 Mon Sep 17 00:00:00 2001 From: Jakub Pisarek <99591440+sgfn@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:57:09 +0200 Subject: [PATCH] Add behaviour and dynamic dispatch for (de)payloaders (#147) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Śledź --- .../lib/save_to_file/peer_handler.ex | 26 +++++++---- .../lib/send_from_file/peer_handler.ex | 11 +++-- lib/ex_webrtc/rtp/depayloader.ex | 42 +++++++++++++++++ lib/ex_webrtc/rtp/depayloader_behaviour.ex | 19 ++++++++ lib/ex_webrtc/rtp/opus/depayloader.ex | 32 ++++++++----- lib/ex_webrtc/rtp/opus/payloader.ex | 30 +++++++----- lib/ex_webrtc/rtp/payloader.ex | 46 +++++++++++++++++++ lib/ex_webrtc/rtp/payloader_behaviour.ex | 17 +++++++ lib/ex_webrtc/rtp/vp8/depayloader.ex | 38 +++++++-------- lib/ex_webrtc/rtp/vp8/payload.ex | 9 ++-- lib/ex_webrtc/rtp/vp8/payloader.ex | 39 ++++++++-------- test/ex_webrtc/rtp/depayloader_test.exs | 43 +++++++++++++++++ test/ex_webrtc/rtp/payloader_test.exs | 40 ++++++++++++++++ test/ex_webrtc/rtp/vp8/depayloader_test.exs | 25 +++++----- test/ex_webrtc/rtp/vp8/payloader_test.exs | 6 +-- 15 files changed, 329 insertions(+), 94 deletions(-) create mode 100644 lib/ex_webrtc/rtp/depayloader.ex create mode 100644 lib/ex_webrtc/rtp/depayloader_behaviour.ex create mode 100644 lib/ex_webrtc/rtp/payloader.ex create mode 100644 lib/ex_webrtc/rtp/payloader_behaviour.ex create mode 100644 test/ex_webrtc/rtp/depayloader_test.exs create mode 100644 test/ex_webrtc/rtp/payloader_test.exs diff --git a/examples/save_to_file/lib/save_to_file/peer_handler.ex b/examples/save_to_file/lib/save_to_file/peer_handler.ex index b28373b..ca32a73 100644 --- a/examples/save_to_file/lib/save_to_file/peer_handler.ex +++ b/examples/save_to_file/lib/save_to_file/peer_handler.ex @@ -10,7 +10,7 @@ defmodule SaveToFile.PeerHandler do } alias ExWebRTC.Media.{IVF, Ogg} - alias ExWebRTC.RTP.{Opus, VP8} + alias ExWebRTC.RTP.Depayloader @behaviour WebSock @@ -54,6 +54,7 @@ defmodule SaveToFile.PeerHandler do video_writer: nil, video_depayloader: nil, audio_writer: nil, + audio_depayloader: nil, frames_cnt: 0 } @@ -140,9 +141,11 @@ defmodule SaveToFile.PeerHandler do timebase_num: 1 ) + {:ok, video_depayloader} = @video_codecs |> hd() |> Depayloader.new() + state = %{ state - | video_depayloader: VP8.Depayloader.new(), + | video_depayloader: video_depayloader, video_writer: video_writer, video_track_id: id } @@ -153,8 +156,15 @@ defmodule SaveToFile.PeerHandler do defp handle_webrtc_msg({:track, %MediaStreamTrack{kind: :audio, id: id}}, state) do # by default uses 1 mono channel and 48k clock rate {:ok, audio_writer} = Ogg.Writer.open(@audio_file) + {:ok, audio_depayloader} = @audio_codecs |> hd() |> Depayloader.new() + + state = %{ + state + | audio_depayloader: audio_depayloader, + audio_writer: audio_writer, + audio_track_id: id + } - state = %{state | audio_writer: audio_writer, audio_track_id: id} {:ok, state} end @@ -166,11 +176,11 @@ defmodule SaveToFile.PeerHandler do defp handle_webrtc_msg({:rtp, id, nil, packet}, %{video_track_id: id} = state) do state = - case VP8.Depayloader.write(state.video_depayloader, packet) do - {:ok, video_depayloader} -> + case Depayloader.depayload(state.video_depayloader, packet) do + {nil, video_depayloader} -> %{state | video_depayloader: video_depayloader} - {:ok, vp8_frame, video_depayloader} -> + {vp8_frame, video_depayloader} -> frame = %IVF.Frame{timestamp: state.frames_cnt, data: vp8_frame} {:ok, video_writer} = IVF.Writer.write_frame(state.video_writer, frame) @@ -186,10 +196,10 @@ defmodule SaveToFile.PeerHandler do end defp handle_webrtc_msg({:rtp, id, nil, packet}, %{audio_track_id: id} = state) do - opus_packet = Opus.Depayloader.depayload(packet) + {opus_packet, depayloader} = Depayloader.depayload(state.audio_depayloader, packet) {:ok, audio_writer} = Ogg.Writer.write_packet(state.audio_writer, opus_packet) - {:ok, %{state | audio_writer: audio_writer}} + {:ok, %{state | audio_depayloader: depayloader, audio_writer: audio_writer}} end defp handle_webrtc_msg(_msg, state), do: {:ok, state} diff --git a/examples/send_from_file/lib/send_from_file/peer_handler.ex b/examples/send_from_file/lib/send_from_file/peer_handler.ex index eeb37c2..02dbb37 100644 --- a/examples/send_from_file/lib/send_from_file/peer_handler.ex +++ b/examples/send_from_file/lib/send_from_file/peer_handler.ex @@ -12,7 +12,7 @@ defmodule SendFromFile.PeerHandler do } alias ExWebRTC.Media.{IVF, Ogg} - alias ExWebRTC.RTP.{Opus, VP8} + alias ExWebRTC.RTP.Payloader @behaviour WebSock @@ -60,9 +60,10 @@ defmodule SendFromFile.PeerHandler do {:ok, _sender} = PeerConnection.add_track(pc, audio_track) {:ok, _header, video_reader} = IVF.Reader.open(@video_file) - video_payloader = VP8.Payloader.new(800) + {:ok, video_payloader} = @video_codecs |> hd() |> Payloader.new(max_payload_size: 800) {:ok, audio_reader} = Ogg.Reader.open(@audio_file) + {:ok, audio_payloader} = @audio_codecs |> hd() |> Payloader.new() state = %{ peer_connection: pc, @@ -71,6 +72,7 @@ defmodule SendFromFile.PeerHandler do video_reader: video_reader, video_payloader: video_payloader, audio_reader: audio_reader, + audio_payloader: audio_payloader, next_video_timestamp: Enum.random(0..@max_rtp_timestamp), next_audio_timestamp: Enum.random(0..@max_rtp_timestamp), next_video_sequence_number: Enum.random(0..@max_rtp_seq_no), @@ -112,7 +114,7 @@ defmodule SendFromFile.PeerHandler do case IVF.Reader.next_frame(state.video_reader) do {:ok, frame} -> - {rtp_packets, payloader} = VP8.Payloader.payload(state.video_payloader, frame.data) + {rtp_packets, payloader} = Payloader.payload(state.video_payloader, frame.data) # 3_000 = 90_000 (VP8 clock rate) / 30 FPS next_sequence_number = @@ -158,7 +160,7 @@ defmodule SendFromFile.PeerHandler do # and time spent on reading and parsing the file Process.send_after(self(), :send_audio, duration) - rtp_packet = Opus.Payloader.payload(packet) + {[rtp_packet], payloader} = Payloader.payload(state.audio_payloader, packet) rtp_packet = %{ rtp_packet @@ -177,6 +179,7 @@ defmodule SendFromFile.PeerHandler do state = %{ state | audio_reader: reader, + audio_payloader: payloader, next_audio_timestamp: next_timestamp, next_audio_sequence_number: next_sequence_number } diff --git a/lib/ex_webrtc/rtp/depayloader.ex b/lib/ex_webrtc/rtp/depayloader.ex new file mode 100644 index 0000000..33f0cce --- /dev/null +++ b/lib/ex_webrtc/rtp/depayloader.ex @@ -0,0 +1,42 @@ +defmodule ExWebRTC.RTP.Depayloader do + @moduledoc """ + RTP depayloader. + + It unpacks RTP pakcets into audio/video frames. + """ + + alias ExWebRTC.RTPCodecParameters + + @opaque depayloader :: struct() + + @doc """ + Creates a new depayloader that matches the passed codec parameters. + """ + @spec new(RTPCodecParameters.t()) :: + {:ok, depayloader()} | {:error, :no_depayloader_for_codec} + def new(codec_params) do + with {:ok, module} <- to_depayloader_module(codec_params.mime_type) do + depayloader = module.new() + {:ok, depayloader} + end + end + + @doc """ + Processes binary data from a single RTP packet, and outputs a frame if assembled. + + Returns the frame (or `nil` if a frame could not be depayloaded yet) + together with the updated depayloader. + """ + @spec depayload(depayloader(), ExRTP.Packet.t()) :: {binary() | nil, depayloader()} + def depayload(%module{} = depayloader, packet) do + module.depayload(depayloader, packet) + end + + defp to_depayloader_module(mime_type) do + case String.downcase(mime_type) do + "video/vp8" -> {:ok, ExWebRTC.RTP.Depayloader.VP8} + "audio/opus" -> {:ok, ExWebRTC.RTP.Depayloader.Opus} + _other -> {:error, :no_depayloader_for_codec} + end + end +end diff --git a/lib/ex_webrtc/rtp/depayloader_behaviour.ex b/lib/ex_webrtc/rtp/depayloader_behaviour.ex new file mode 100644 index 0000000..3bae5cf --- /dev/null +++ b/lib/ex_webrtc/rtp/depayloader_behaviour.ex @@ -0,0 +1,19 @@ +defmodule ExWebRTC.RTP.Depayloader.Behaviour do + @moduledoc false + + @type depayloader :: struct() + + @doc """ + Creates a new depayloader struct. + """ + @callback new() :: depayloader() + + @doc """ + Processes binary data from a single RTP packet, and outputs a frame if assembled. + + Returns the frame (or `nil` if a frame could not be depayloaded yet) + together with the updated depayloader struct. + """ + @callback depayload(depayloader(), packet :: ExRTP.Packet.t()) :: + {binary() | nil, depayloader()} +end diff --git a/lib/ex_webrtc/rtp/opus/depayloader.ex b/lib/ex_webrtc/rtp/opus/depayloader.ex index ad89c5b..38b75c8 100644 --- a/lib/ex_webrtc/rtp/opus/depayloader.ex +++ b/lib/ex_webrtc/rtp/opus/depayloader.ex @@ -1,15 +1,25 @@ -defmodule ExWebRTC.RTP.Opus.Depayloader do - @moduledoc """ - Decapsualtes Opus audio out of RTP packet. - - Based on [RFC 7587: RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587). - """ +defmodule ExWebRTC.RTP.Depayloader.Opus do + @moduledoc false + # Decapsualtes Opus audio out of RTP packet. + # + # Based on [RFC 7587: RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587). alias ExRTP.Packet - @doc """ - Takes Opus packet out of an RTP packet. - """ - @spec depayload(Packet.t()) :: binary() - def depayload(%Packet{payload: payload}), do: payload + @behaviour ExWebRTC.RTP.Depayloader.Behaviour + + @type t :: %__MODULE__{} + + defstruct [] + + @impl true + @spec new() :: t() + def new() do + %__MODULE__{} + end + + @impl true + @spec depayload(t(), Packet.t()) :: {binary(), t()} + def depayload(%__MODULE__{} = depayloader, %Packet{payload: payload}), + do: {payload, depayloader} end diff --git a/lib/ex_webrtc/rtp/opus/payloader.ex b/lib/ex_webrtc/rtp/opus/payloader.ex index ef1f3f7..a0e8215 100644 --- a/lib/ex_webrtc/rtp/opus/payloader.ex +++ b/lib/ex_webrtc/rtp/opus/payloader.ex @@ -1,17 +1,23 @@ -defmodule ExWebRTC.RTP.Opus.Payloader do - @moduledoc """ - Encapsulates Opus audio packet into an RTP packet. +defmodule ExWebRTC.RTP.Payloader.Opus do + @moduledoc false + # Encapsulates Opus audio packet into an RTP packet. + # + # Based on [RFC 7587: RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587). - Based on [RFC 7587: RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587). - """ + @behaviour ExWebRTC.RTP.Payloader.Behaviour - @doc """ - Packs Opus packet into an RTP packet. + @type t :: %__MODULE__{} - Fields from RTP header like ssrc, timestamp etc. are set to 0. - """ - @spec payload(binary()) :: ExRTP.Packet.t() - def payload(packet) when packet != <<>> do - ExRTP.Packet.new(packet) + defstruct [] + + @impl true + def new(_max_payload_size) do + %__MODULE__{} + end + + @impl true + @spec payload(t(), binary()) :: {[ExRTP.Packet.t()], t()} + def payload(%__MODULE__{} = payloader, packet) when packet != <<>> do + {[ExRTP.Packet.new(packet)], payloader} end end diff --git a/lib/ex_webrtc/rtp/payloader.ex b/lib/ex_webrtc/rtp/payloader.ex new file mode 100644 index 0000000..08ac1e3 --- /dev/null +++ b/lib/ex_webrtc/rtp/payloader.ex @@ -0,0 +1,46 @@ +defmodule ExWebRTC.RTP.Payloader do + @moduledoc """ + RTP payloader. + + It packs audio/video frames into one or more RTP packets. + """ + + alias ExWebRTC.RTPCodecParameters + + @opaque payloader :: struct() + + @doc """ + Creates a new payloader that matches the passed codec parameters. + + Opts: + * max_payload_size - determines the maximum size of a single RTP packet outputted by the payloader. + It must be greater than `100`, and is set to `1000` by default. + """ + @spec new(RTPCodecParameters.t(), max_payload_size: integer()) :: + {:ok, payloader()} | {:error, :no_payloader_for_codec} + def new(codec_params, opts \\ []) do + with {:ok, module} <- to_payloader_module(codec_params.mime_type) do + max_payload_size = opts[:max_payload_size] || 1000 + payloader = module.new(max_payload_size) + {:ok, payloader} + end + end + + @doc """ + Packs a frame into one or more RTP packets. + + Returns the packets together with the updated payloader. + """ + @spec payload(payloader(), binary()) :: {[ExRTP.Packet.t()], payloader()} + def payload(%module{} = payloader, frame) do + module.payload(payloader, frame) + end + + defp to_payloader_module(mime_type) do + case String.downcase(mime_type) do + "video/vp8" -> {:ok, ExWebRTC.RTP.Payloader.VP8} + "audio/opus" -> {:ok, ExWebRTC.RTP.Payloader.Opus} + _other -> {:error, :no_payloader_for_codec} + end + end +end diff --git a/lib/ex_webrtc/rtp/payloader_behaviour.ex b/lib/ex_webrtc/rtp/payloader_behaviour.ex new file mode 100644 index 0000000..81c3df4 --- /dev/null +++ b/lib/ex_webrtc/rtp/payloader_behaviour.ex @@ -0,0 +1,17 @@ +defmodule ExWebRTC.RTP.Payloader.Behaviour do + @moduledoc false + + @type payloader :: struct() + + @doc """ + Creates a new payloader struct. + """ + @callback new(max_payload_size :: integer()) :: payloader() + + @doc """ + Packs a frame into one or more RTP packets. + + Returns the packets together with the updated payloader struct. + """ + @callback payload(payloader(), frame :: binary()) :: {[ExRTP.Packet.t()], payloader()} +end diff --git a/lib/ex_webrtc/rtp/vp8/depayloader.ex b/lib/ex_webrtc/rtp/vp8/depayloader.ex index c56cf15..42b6b2c 100644 --- a/lib/ex_webrtc/rtp/vp8/depayloader.ex +++ b/lib/ex_webrtc/rtp/vp8/depayloader.ex @@ -1,34 +1,36 @@ -defmodule ExWebRTC.RTP.VP8.Depayloader do - @moduledoc """ - Reassembles VP8 frames from RTP packets. +defmodule ExWebRTC.RTP.Depayloader.VP8 do + @moduledoc false + # Reassembles VP8 frames from RTP packets. + # + # Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). + + @behaviour ExWebRTC.RTP.Depayloader.Behaviour - Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). - """ require Logger alias ExWebRTC.RTP.VP8.Payload - @opaque t() :: %__MODULE__{ - current_frame: nil, - current_timestamp: nil - } + @type t() :: %__MODULE__{ + current_frame: nil, + current_timestamp: nil + } defstruct [:current_frame, :current_timestamp] - @spec new() :: t() + @impl true def new() do %__MODULE__{} end - @spec write(t(), ExRTP.Packet.t()) :: {:ok, t()} | {:ok, binary(), t()} - def write(depayloader, packet) + @impl true + def depayload(depayloader, packet) - def write(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {:ok, depayloader} + def depayload(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {nil, depayloader} - def write(depayloader, packet) do + def depayload(depayloader, packet) do case Payload.parse(packet.payload) do {:ok, vp8_payload} -> - do_write(depayloader, packet, vp8_payload) + do_depayload(depayloader, packet, vp8_payload) {:error, reason} -> Logger.warning(""" @@ -40,7 +42,7 @@ defmodule ExWebRTC.RTP.VP8.Depayloader do end end - defp do_write(depayloader, packet, vp8_payload) do + defp do_depayload(depayloader, packet, vp8_payload) do depayloader = case {depayloader.current_frame, vp8_payload} do {nil, %Payload{s: 1, pid: 0}} -> @@ -80,10 +82,10 @@ defmodule ExWebRTC.RTP.VP8.Depayloader do case {depayloader.current_frame, packet.marker} do {current_frame, true} when current_frame != nil -> - {:ok, current_frame, %{depayloader | current_frame: nil, current_timestamp: nil}} + {current_frame, %{depayloader | current_frame: nil, current_timestamp: nil}} _ -> - {:ok, depayloader} + {nil, depayloader} end end end diff --git a/lib/ex_webrtc/rtp/vp8/payload.ex b/lib/ex_webrtc/rtp/vp8/payload.ex index 48338f0..eb74684 100644 --- a/lib/ex_webrtc/rtp/vp8/payload.ex +++ b/lib/ex_webrtc/rtp/vp8/payload.ex @@ -1,9 +1,8 @@ defmodule ExWebRTC.RTP.VP8.Payload do - @moduledoc """ - Defines VP8 payload structure stored in RTP packet payload. - - Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). - """ + @moduledoc false + # Defines VP8 payload structure stored in RTP packet payload. + # + # Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). @type t() :: %__MODULE__{ n: 0 | 1, diff --git a/lib/ex_webrtc/rtp/vp8/payloader.ex b/lib/ex_webrtc/rtp/vp8/payloader.ex index 27b24d1..bcf374e 100644 --- a/lib/ex_webrtc/rtp/vp8/payloader.ex +++ b/lib/ex_webrtc/rtp/vp8/payloader.ex @@ -1,12 +1,13 @@ -defmodule ExWebRTC.RTP.VP8.Payloader do - @moduledoc """ - Encapsulates VP8 video frames into RTP packets. +defmodule ExWebRTC.RTP.Payloader.VP8 do + @moduledoc false + # Encapsulates VP8 video frames into RTP packets. + # + # Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). + # + # It does not support `X` bit right now, in particular it + # does not pay attention to VP8 partition boundaries (see RFC 7741 sec. 4.4). - Based on [RFC 7741: RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741). - - It does not support `X` bit right now, in particular it - does not pay attention to VP8 partition boundaries (see RFC 7741 sec. 4.4). - """ + @behaviour ExWebRTC.RTP.Payloader.Behaviour @first_chunk_descriptor <<0::1, 0::1, 0::1, 1::1, 0::1, 0::3>> @@ -14,24 +15,20 @@ defmodule ExWebRTC.RTP.VP8.Payloader do @desc_size_bytes 1 - @opaque t() :: %__MODULE__{ - max_payload_size: non_neg_integer() - } + @type t() :: %__MODULE__{ + max_payload_size: non_neg_integer() + } - defstruct [:max_payload_size] + @enforce_keys [:max_payload_size] + defstruct @enforce_keys - @spec new(non_neg_integer()) :: t() - def new(max_payload_size \\ 1000) when max_payload_size > 100 do + @impl true + def new(max_payload_size) when max_payload_size > 100 do %__MODULE__{max_payload_size: max_payload_size} end - @doc """ - Packs VP8 frame into one or more RTP packets. - - Fields from RTP header like ssrc, timestamp etc. are set to 0. - """ - @spec payload(t(), frame :: binary()) :: {[ExRTP.Packet.t()], t()} - def payload(payloader, frame) when frame != <<>> do + @impl true + def payload(%__MODULE__{} = payloader, frame) when frame != <<>> do rtp_payloads = chunk(frame, payloader.max_payload_size - @desc_size_bytes) [first_rtp_payload | next_rtp_payloads] = rtp_payloads diff --git a/test/ex_webrtc/rtp/depayloader_test.exs b/test/ex_webrtc/rtp/depayloader_test.exs new file mode 100644 index 0000000..01c35be --- /dev/null +++ b/test/ex_webrtc/rtp/depayloader_test.exs @@ -0,0 +1,43 @@ +defmodule ExWebRTC.RTP.DepayloaderTest do + use ExUnit.Case, async: true + + alias ExWebRTC.RTPCodecParameters + alias ExWebRTC.RTP.Depayloader + + @packet %ExRTP.Packet{ + payload_type: 96, + sequence_number: 0, + timestamp: 0, + ssrc: 0, + payload: <<0, 1, 2, 3>> + } + + test "creates a VP8 depayloader and dispatches calls to its module" do + assert {:ok, depayloader} = + %RTPCodecParameters{payload_type: 96, mime_type: "video/VP8", clock_rate: 90_000} + |> Depayloader.new() + + assert Depayloader.depayload(depayloader, @packet) == + Depayloader.VP8.depayload(depayloader, @packet) + end + + test "creates an Opus depayloader and dispatches calls to its module" do + assert {:ok, depayloader} = + %RTPCodecParameters{ + payload_type: 96, + mime_type: "audio/opus", + clock_rate: 48_000, + channels: 2 + } + |> Depayloader.new() + + assert Depayloader.depayload(depayloader, @packet) == + Depayloader.Opus.depayload(depayloader, @packet) + end + + test "returns error if no depayloader exists for given codec" do + assert {:error, :no_depayloader_for_codec} = + %RTPCodecParameters{payload_type: 97, mime_type: "video/H264", clock_rate: 90_000} + |> Depayloader.new() + end +end diff --git a/test/ex_webrtc/rtp/payloader_test.exs b/test/ex_webrtc/rtp/payloader_test.exs new file mode 100644 index 0000000..ea7fd76 --- /dev/null +++ b/test/ex_webrtc/rtp/payloader_test.exs @@ -0,0 +1,40 @@ +defmodule ExWebRTC.RTP.PayloaderTest do + use ExUnit.Case, async: true + + alias ExWebRTC.RTPCodecParameters + alias ExWebRTC.RTP.Payloader + + @frame <<0, 1, 2, 3>> + + test "creates a VP8 payloader and dispatches calls to its module" do + assert {:ok, _payloader} = + %RTPCodecParameters{payload_type: 96, mime_type: "video/VP8", clock_rate: 90_000} + |> Payloader.new() + + # with options + assert {:ok, payloader} = + %RTPCodecParameters{payload_type: 96, mime_type: "video/VP8", clock_rate: 90_000} + |> Payloader.new(max_payload_size: 800) + + assert Payloader.payload(payloader, @frame) == Payloader.VP8.payload(payloader, @frame) + end + + test "creates an Opus payloader and dispatches calls to its module" do + assert {:ok, payloader} = + %RTPCodecParameters{ + payload_type: 111, + mime_type: "audio/opus", + clock_rate: 48_000, + channels: 2 + } + |> Payloader.new() + + assert Payloader.payload(payloader, @frame) == Payloader.Opus.payload(payloader, @frame) + end + + test "returns error if no payloader exists for given codec" do + assert {:error, :no_payloader_for_codec} = + %RTPCodecParameters{payload_type: 97, mime_type: "video/H264", clock_rate: 90_000} + |> Payloader.new() + end +end diff --git a/test/ex_webrtc/rtp/vp8/depayloader_test.exs b/test/ex_webrtc/rtp/vp8/depayloader_test.exs index 7ff1c46..ff65619 100644 --- a/test/ex_webrtc/rtp/vp8/depayloader_test.exs +++ b/test/ex_webrtc/rtp/vp8/depayloader_test.exs @@ -1,10 +1,11 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do use ExUnit.Case, async: true - alias ExWebRTC.RTP.VP8.{Payload, Depayloader} + alias ExWebRTC.RTP.Depayloader + alias ExWebRTC.RTP.VP8.Payload test "write/2" do - depayloader = Depayloader.new() + depayloader = Depayloader.VP8.new() # random vp8 data, not necessarily correct data = <<0, 1, 2, 3>> @@ -14,8 +15,8 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do packet = ExRTP.Packet.new(vp8_payload, marker: true) - assert {:ok, ^data, %{current_frame: nil, current_timestamp: nil} = depayloader} = - Depayloader.write(depayloader, packet) + assert {^data, %{current_frame: nil, current_timestamp: nil} = depayloader} = + Depayloader.VP8.depayload(depayloader, packet) # packet that doesn't start a new frame vp8_payload = %Payload{n: 0, s: 0, pid: 0, payload: data} @@ -23,8 +24,8 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do packet = ExRTP.Packet.new(vp8_payload) - assert {:ok, %{current_frame: nil, current_timestamp: nil} = depayloader} = - Depayloader.write(depayloader, packet) + assert {nil, %{current_frame: nil, current_timestamp: nil} = depayloader} = + Depayloader.VP8.depayload(depayloader, packet) # packet that starts a new frame without finishing the previous one vp8_payload = %Payload{n: 0, s: 1, pid: 0, payload: data} @@ -32,8 +33,8 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do packet = ExRTP.Packet.new(vp8_payload) - assert {:ok, %{current_frame: ^data, current_timestamp: 0} = depayloader} = - Depayloader.write(depayloader, packet) + assert {nil, %{current_frame: ^data, current_timestamp: 0} = depayloader} = + Depayloader.VP8.depayload(depayloader, packet) data2 = data <> <<0>> vp8_payload = %Payload{n: 0, s: 1, pid: 0, payload: data2} @@ -41,8 +42,8 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do packet = ExRTP.Packet.new(vp8_payload, timestamp: 3000) - assert {:ok, %{current_frame: ^data2, current_timestamp: 3000} = depayloader} = - Depayloader.write(depayloader, packet) + assert {nil, %{current_frame: ^data2, current_timestamp: 3000} = depayloader} = + Depayloader.VP8.depayload(depayloader, packet) # packet with timestamp from a new frame that is not a beginning of this frame data2 = data @@ -51,7 +52,7 @@ defmodule ExWebRTC.RTP.VP8.DepayloaderTest do packet = ExRTP.Packet.new(vp8_payload, timestamp: 6000) - assert {:ok, %{current_frame: nil, current_timestamp: nil}} = - Depayloader.write(depayloader, packet) + assert {nil, %{current_frame: nil, current_timestamp: nil}} = + Depayloader.VP8.depayload(depayloader, packet) end end diff --git a/test/ex_webrtc/rtp/vp8/payloader_test.exs b/test/ex_webrtc/rtp/vp8/payloader_test.exs index cd64260..3721715 100644 --- a/test/ex_webrtc/rtp/vp8/payloader_test.exs +++ b/test/ex_webrtc/rtp/vp8/payloader_test.exs @@ -2,17 +2,17 @@ defmodule ExWebRTC.RTP.VP8.PayloaderTest do use ExUnit.Case, async: true alias ExWebRTC.Media.IVF.Reader - alias ExWebRTC.RTP.VP8.Payloader + alias ExWebRTC.RTP.Payloader test "payload vp8 video" do # video frames in the fixture are mostly 500+ bytes - vp8_payloader = Payloader.new(200) + vp8_payloader = Payloader.VP8.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} = Payloader.payload(vp8_payloader, frame.data) + {rtp_packets, vp8_payloader} = Payloader.VP8.payload(vp8_payloader, frame.data) # assert all packets but last are 200 bytes rtp_packets