Skip to content

Commit 564fc30

Browse files
committed
wip, baseline of depayloader
1 parent 9cdd430 commit 564fc30

File tree

6 files changed

+567
-0
lines changed

6 files changed

+567
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
defmodule ExWebRTC.RTP.Depayloader.H264 do
2+
@moduledoc """
3+
Depayloads H264 RTP payloads into H264 NAL Units.
4+
5+
Based on [RFC 6184](https://tools.ietf.org/html/rfc6184).
6+
7+
Supported types: Single NALU, FU-A, STAP-A.
8+
"""
9+
@behaviour ExWebRTC.RTP.Depayloader.Behaviour
10+
11+
require Logger
12+
13+
alias ExWebRTC.RTP.H264.{FU, NAL, StapA}
14+
15+
@frame_prefix <<1::32>>
16+
@annexb_prefix <<1::4>>
17+
18+
defmodule State do
19+
@moduledoc false
20+
defstruct parser_acc: nil
21+
end
22+
23+
@type t() :: %__MODULE__{
24+
current_nal: nil,
25+
current_timestamp: nil
26+
}
27+
28+
defstruct [:current_nal, :current_timestamp]
29+
30+
@impl true
31+
def new() do
32+
%__MODULE__{}
33+
end
34+
35+
# TODO: handle timestamps
36+
@impl true
37+
def depayload(depayloader, packet)
38+
39+
def depayload(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {nil, depayloader}
40+
41+
def depayload(depayloader, packet) do
42+
with {:ok, {header, _payload} = nal} <- NAL.Header.parse_unit_header(packet.payload),
43+
unit_type = NAL.Header.decode_type(header),
44+
{:ok, {nalu, depayloader}} <- handle_unit_type(unit_type, depayloader, packet, nal) do
45+
{nalu, depayloader}
46+
else
47+
{:error, reason} ->
48+
Logger.warning("""
49+
Couldn't parse payload, reason: #{reason}. \
50+
Resetting depayloader state. Payload: #{inspect(packet.payload)}.\
51+
""")
52+
53+
{:ok, %{depayloader | current_nal: nil, current_timestamp: nil}}
54+
end
55+
end
56+
57+
defp handle_unit_type(:single_nalu, _depayloader, _packet, nal) do
58+
{header, payload} = nal
59+
{:ok, {prefix_annexb(payload), depayloader}}
60+
end
61+
62+
defp handle_unit_type(
63+
:fu_a,
64+
{current_nal, current_timestamp} = depayloader,
65+
packet,
66+
{header, payload} = nal
67+
) do
68+
if current_nal != nil and current_timestamp != packet.timestamp do
69+
{:error, "fu-a colliding rtp timestamps"}
70+
71+
Logger.debug("""
72+
Received packet with timestamp from a new frame that is not a beginning of this frame \
73+
and without finishing the previous frame. Dropping both.\
74+
""")
75+
end
76+
77+
case FU.parse(payload, current_nal) do
78+
{:ok, {payload, type}} ->
79+
{:ok, result}
80+
81+
{:incomplete, tmp} ->
82+
{:ok, {nil, %{depayloader | current_nal: curent_nal <> tmp}}}
83+
84+
{:error, _reason} = error ->
85+
error
86+
end
87+
end
88+
89+
defp handle_unit_type(:stap_a, depayloader, {_header, data}, buffer, state) do
90+
with {:ok, result} <- StapA.parse(data) do
91+
nals = Enum.reduce(result, <<>>, fn nal, acc -> acc <> prefix_annexb(nal) end)
92+
{:ok, {nals, depayloader}}
93+
end
94+
end
95+
96+
defp prefix_annexb(nal) do
97+
@annexb_prefix <> nal
98+
end
99+
end
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
defmodule ExWebRTC.RTP.H264.FU do
2+
@moduledoc """
3+
Module responsible for parsing H264 Fragmentation Unit.
4+
"""
5+
use Bunch
6+
alias __MODULE__
7+
alias Membrane.RTP.H264.NAL
8+
9+
defstruct data: []
10+
11+
@type t :: %__MODULE__{data: [binary()]}
12+
13+
@doc """
14+
Parses H264 Fragmentation Unit
15+
16+
If a packet that is being parsed is not considered last then a `{:incomplete, t()}`
17+
tuple will be returned.
18+
In case of last packet `{:ok, {type, data}}` tuple will be returned, where data
19+
is `NAL Unit` created by concatenating subsequent Fragmentation Units.
20+
"""
21+
@spec parse(binary(), t) ::
22+
{:ok, {binary(), NAL.Header.type()}}
23+
| {:error, :packet_malformed | :invalid_first_packet}
24+
| {:incomplete, t()}
25+
def parse(packet, acc) do
26+
with {:ok, {header, value}} <- FU.Header.parse(packet) do
27+
do_parse(header, value, acc)
28+
end
29+
end
30+
31+
@doc """
32+
Serialize H264 unit into list of FU-A payloads
33+
"""
34+
@spec serialize(binary(), pos_integer()) :: list(binary()) | {:error, :unit_too_small}
35+
def serialize(data, preferred_size) do
36+
case data do
37+
<<header::1-binary, head::binary-size(preferred_size - 1), rest::binary>> ->
38+
<<r::1, nri::2, type::5>> = header
39+
40+
payload =
41+
head
42+
|> FU.Header.add_header(1, 0, type)
43+
|> NAL.Header.add_header(r, nri, NAL.Header.encode_type(:fu_a))
44+
45+
[payload | do_serialize(rest, r, nri, type, preferred_size)]
46+
47+
_data ->
48+
{:error, :unit_too_small}
49+
end
50+
end
51+
52+
defp do_serialize(data, r, nri, type, preferred_size) do
53+
case data do
54+
<<head::binary-size(preferred_size - 2), rest::binary>> when byte_size(rest) > 0 ->
55+
payload =
56+
head
57+
|> FU.Header.add_header(0, 0, type)
58+
|> NAL.Header.add_header(r, nri, NAL.Header.encode_type(:fu_a))
59+
60+
[payload] ++ do_serialize(rest, r, nri, type, preferred_size)
61+
62+
rest ->
63+
[
64+
rest
65+
|> FU.Header.add_header(0, 1, type)
66+
|> NAL.Header.add_header(r, nri, NAL.Header.encode_type(:fu_a))
67+
]
68+
end
69+
end
70+
71+
defp do_parse(header, packet, acc)
72+
73+
defp do_parse(%FU.Header{start_bit: true}, packet, acc),
74+
do: {:incomplete, %__MODULE__{acc | data: [packet]}}
75+
76+
defp do_parse(%FU.Header{start_bit: false}, _data, %__MODULE__{data: []}),
77+
do: {:error, :invalid_first_packet}
78+
79+
defp do_parse(%FU.Header{end_bit: true, type: type}, packet, %__MODULE__{data: acc_data}) do
80+
result =
81+
[packet | acc_data]
82+
|> Enum.reverse()
83+
|> Enum.join()
84+
85+
{:ok, {result, type}}
86+
end
87+
88+
defp do_parse(_header, packet, %__MODULE__{data: acc_data} = fu),
89+
do: {:incomplete, %__MODULE__{fu | data: [packet | acc_data]}}
90+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
defmodule ExWebRTC.RTP.H264.FU.Header do
2+
@moduledoc """
3+
Defines a structure representing Fragmentation Unit (FU) header
4+
which is defined in [RFC6184](https://tools.ietf.org/html/rfc6184#page-31)
5+
6+
```
7+
+---------------+
8+
|0|1|2|3|4|5|6|7|
9+
+-+-+-+-+-+-+-+-+
10+
|S|E|R| Type |
11+
+---------------+
12+
```
13+
"""
14+
15+
alias Membrane.RTP.H264.NAL
16+
17+
@typedoc """
18+
MUST be set to true only in the first packet in a sequence.
19+
"""
20+
@type start_flag :: boolean()
21+
22+
@typedoc """
23+
MUST be set to true only in the last packet in a sequence.
24+
"""
25+
@type end_flag :: boolean()
26+
27+
@enforce_keys [:type]
28+
defstruct start_bit: false, end_bit: false, type: 0
29+
30+
@type t :: %__MODULE__{
31+
start_bit: start_flag(),
32+
end_bit: end_flag(),
33+
type: NAL.Header.type()
34+
}
35+
36+
defguardp valid_frame_boundary(start, finish) when start != 1 or finish != 1
37+
38+
@doc """
39+
Parses Fragmentation Unit Header
40+
41+
It will fail if the Start bit and End bit are both set to one in the
42+
same Fragmentation Unit Header, because a fragmented NAL unit
43+
MUST NOT be transmitted in one FU.
44+
"""
45+
@spec parse(data :: binary()) :: {:error, :packet_malformed} | {:ok, {t(), nal :: binary()}}
46+
def parse(<<start::1, finish::1, 0::1, nal_type::5, rest::binary>>)
47+
when nal_type in 1..23 and valid_frame_boundary(start, finish) do
48+
header = %__MODULE__{
49+
start_bit: start == 1,
50+
end_bit: finish == 1,
51+
type: nal_type
52+
}
53+
54+
{:ok, {header, rest}}
55+
end
56+
57+
def parse(_binary), do: {:error, :packet_malformed}
58+
59+
@doc """
60+
Adds FU header
61+
"""
62+
@spec add_header(binary(), 0 | 1, 0 | 1, NAL.Header.type()) :: binary()
63+
def add_header(payload, start_bit, end_bit, type),
64+
do: <<start_bit::1, end_bit::1, 0::1, type::5>> <> payload
65+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defmodule ExWebRTC.RTP.H264.StapA do
2+
@moduledoc """
3+
Module responsible for parsing Single Time Agregation Packets type A.
4+
5+
Documented in [RFC6184](https://tools.ietf.org/html/rfc6184#page-22)
6+
7+
```
8+
0 1 2 3
9+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
10+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
11+
| RTP Header |
12+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
14+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15+
| NALU 1 Data |
16+
: :
17+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18+
| | NALU 2 Size | NALU 2 HDR |
19+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20+
| NALU 2 Data |
21+
: :
22+
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23+
| :...OPTIONAL RTP padding |
24+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25+
```
26+
"""
27+
use Bunch
28+
29+
alias ExWebRTC.RTP.H264.NAL
30+
31+
@spec parse(binary()) :: {:ok, [binary()]} | {:error, :packet_malformed}
32+
def parse(data) do
33+
do_parse(data, [])
34+
end
35+
36+
defp do_parse(<<>>, acc), do: {:ok, Enum.reverse(acc)}
37+
38+
defp do_parse(<<size::16, nalu::binary-size(size), rest::binary>>, acc),
39+
do: do_parse(rest, [nalu | acc])
40+
41+
defp do_parse(_data, _acc), do: {:error, :packet_malformed}
42+
43+
@spec aggregation_unit_size(binary()) :: pos_integer()
44+
def aggregation_unit_size(nalu), do: byte_size(nalu) + 2
45+
46+
@spec serialize([binary], 0..1, 0..3) :: binary
47+
def serialize(payloads, f, nri) do
48+
payloads
49+
|> Enum.reverse()
50+
|> Enum.map(&<<byte_size(&1)::16, &1::binary>>)
51+
|> IO.iodata_to_binary()
52+
|> NAL.Header.add_header(f, nri, NAL.Header.encode_type(:stap_a))
53+
end
54+
end

0 commit comments

Comments
 (0)