-
Notifications
You must be signed in to change notification settings - Fork 21
Add H264 RTP depayloader #224
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
Open
brzep
wants to merge
11
commits into
master
Choose a base branch
from
brzep/h264-depayloader
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
b98c2fa
wip, baseline of depayloader
brzep b8b7d16
first working
brzep 3976b90
cleanup
brzep b6b74b0
add tests, fix depayloader
brzep de0ac2a
fix tests
brzep 7353165
add FU-A start constraint
brzep 43407a3
review fixes, unused code removal
brzep ecc81f3
fix warning
brzep f068bb6
dialyzer fixes
brzep 5e64f05
test coverage
brzep 5ce9157
drop rtp padding test
brzep File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
defmodule ExWebRTC.RTP.Depayloader.H264 do | ||
@moduledoc false | ||
# Extracts H264 NAL Units from RTP packets. | ||
# | ||
# Based on [RFC 6184](https://tools.ietf.org/html/rfc6184). | ||
# | ||
# Supported types: Single NALU, FU-A, STAP-A. | ||
|
||
@behaviour ExWebRTC.RTP.Depayloader.Behaviour | ||
|
||
require Logger | ||
|
||
alias ExWebRTC.RTP.H264.{FU, NAL, StapA} | ||
|
||
@annexb_prefix <<1::32>> | ||
|
||
@type t() :: %__MODULE__{ | ||
current_timestamp: non_neg_integer() | nil, | ||
fu_parser_acc: [binary()] | ||
} | ||
|
||
defstruct current_timestamp: nil, fu_parser_acc: [] | ||
|
||
@impl true | ||
def new() do | ||
%__MODULE__{} | ||
end | ||
|
||
@impl true | ||
def depayload(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {nil, depayloader} | ||
|
||
def depayload(depayloader, packet) do | ||
with {:ok, {header, _payload} = nal} <- NAL.Header.parse_unit_header(packet.payload), | ||
unit_type = NAL.Header.decode_type(header), | ||
{:ok, {nal, depayloader}} <- | ||
do_depayload(unit_type, depayloader, packet, nal) do | ||
{nal, depayloader} | ||
else | ||
{:error, reason} -> | ||
Logger.warning(""" | ||
Couldn't parse payload, reason: #{reason}. \ | ||
Resetting depayloader state. Payload: #{inspect(packet.payload)}.\ | ||
""") | ||
|
||
{nil, %{depayloader | current_timestamp: nil, fu_parser_acc: []}} | ||
end | ||
end | ||
|
||
defp do_depayload(:single_nalu, depayloader, packet, {_header, payload}) do | ||
{:ok, | ||
{prefix_annexb(payload), %__MODULE__{depayloader | current_timestamp: packet.timestamp}}} | ||
end | ||
|
||
defp do_depayload( | ||
:fu_a, | ||
%{current_timestamp: current_timestamp, fu_parser_acc: fu_parser_acc}, | ||
packet, | ||
{_header, _payload} | ||
) | ||
when fu_parser_acc != [] and current_timestamp != packet.timestamp do | ||
Logger.warning(""" | ||
received packet with fu-a type payload that is not a start of fragmentation unit with timestamp \ | ||
different than last start and without finishing the previous fu. dropping fu.\ | ||
""") | ||
|
||
{:error, "invalid timestamp inside fu-a"} | ||
end | ||
|
||
defp do_depayload( | ||
:fu_a, | ||
%{fu_parser_acc: fu_parser_acc}, | ||
packet, | ||
{header, payload} | ||
) do | ||
case FU.parse(payload, fu_parser_acc || []) do | ||
{:ok, {data, type}} -> | ||
data = NAL.Header.add_header(data, 0, header.nal_ref_idc, type) | ||
|
||
{:ok, | ||
{prefix_annexb(data), | ||
%__MODULE__{current_timestamp: packet.timestamp, fu_parser_acc: []}}} | ||
|
||
{:incomplete, fu} -> | ||
{:ok, {nil, %__MODULE__{fu_parser_acc: fu, current_timestamp: packet.timestamp}}} | ||
|
||
{:error, _reason} = error -> | ||
error | ||
end | ||
end | ||
|
||
defp do_depayload(:stap_a, depayloader, packet, {_header, payload}) do | ||
with {:ok, result} <- StapA.parse(payload) do | ||
nals = result |> Stream.map(&prefix_annexb/1) |> Enum.join() | ||
{:ok, {nals, %__MODULE__{depayloader | current_timestamp: packet.timestamp}}} | ||
end | ||
end | ||
|
||
defp do_depayload(unsupported_type, _depayloader, _packet, _nal) do | ||
Logger.warning(""" | ||
Received packet with unsupported NAL type: #{unsupported_type}. Supported types are: Single NALU, STAP-A, FU-A. Dropping packet. | ||
""") | ||
|
||
{:error, "Unsupported nal type #{unsupported_type}"} | ||
end | ||
|
||
defp prefix_annexb(nal) do | ||
@annexb_prefix <> nal | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
defmodule ExWebRTC.RTP.H264.FU do | ||
@moduledoc """ | ||
Module responsible for parsing H264 Fragmentation Unit. | ||
""" | ||
Comment on lines
+2
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's change the other moduledocs to comments as well |
||
alias __MODULE__ | ||
alias ExWebRTC.RTP.H264.NAL | ||
|
||
@doc """ | ||
Parses H264 Fragmentation Unit | ||
|
||
If a packet that is being parsed is not considered last then a `{:incomplete, t()}` | ||
tuple will be returned. | ||
In case of last packet `{:ok, {type, data}}` tuple will be returned, where data | ||
is `NAL Unit` created by concatenating subsequent Fragmentation Units. | ||
""" | ||
@spec parse(binary(), [binary()]) :: | ||
{:ok, {binary(), NAL.Header.rbsp_type()}} | ||
| {:error, :packet_malformed | :invalid_first_packet} | ||
| {:incomplete, [binary()]} | ||
def parse(packet, acc) do | ||
with {:ok, {header, value}} <- FU.Header.parse(packet) do | ||
do_parse(header, value, acc) | ||
end | ||
end | ||
|
||
defp do_parse(header, packet, acc) | ||
|
||
defp do_parse(%FU.Header{start_bit: true}, data, []), | ||
do: {:incomplete, [data]} | ||
|
||
defp do_parse(%FU.Header{start_bit: true}, _data, _acc), | ||
do: {:error, :last_fu_not_finished} | ||
|
||
defp do_parse(%FU.Header{start_bit: false}, _data, []), | ||
do: {:error, :invalid_first_packet} | ||
|
||
defp do_parse(%FU.Header{end_bit: true, type: type}, data, acc_data) do | ||
result = | ||
[data | acc_data] | ||
|> Enum.reverse() | ||
|> Enum.join() | ||
|
||
{:ok, {result, type}} | ||
end | ||
|
||
defp do_parse(_header, data, acc_data), | ||
do: {:incomplete, [data | acc_data]} | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
defmodule ExWebRTC.RTP.H264.FU.Header do | ||
@moduledoc """ | ||
Defines a structure representing Fragmentation Unit (FU) header | ||
which is defined in [RFC6184](https://tools.ietf.org/html/rfc6184#page-31) | ||
|
||
``` | ||
+---------------+ | ||
|0|1|2|3|4|5|6|7| | ||
+-+-+-+-+-+-+-+-+ | ||
|S|E|R| Type | | ||
+---------------+ | ||
``` | ||
""" | ||
|
||
alias ExWebRTC.RTP.H264.NAL | ||
|
||
@typedoc """ | ||
MUST be set to true only in the first packet in a sequence. | ||
""" | ||
@type start_flag :: boolean() | ||
|
||
@typedoc """ | ||
MUST be set to true only in the last packet in a sequence. | ||
""" | ||
@type end_flag :: boolean() | ||
|
||
@enforce_keys [:type] | ||
defstruct start_bit: false, end_bit: false, type: 0 | ||
|
||
@type t :: %__MODULE__{ | ||
start_bit: start_flag(), | ||
end_bit: end_flag(), | ||
type: NAL.Header.rbsp_type() | ||
} | ||
|
||
defguardp valid_frame_boundary(start, finish) when start != 1 or finish != 1 | ||
|
||
@doc """ | ||
Parses Fragmentation Unit Header | ||
|
||
It will fail if the Start bit and End bit are both set to one in the | ||
same Fragmentation Unit Header, because a fragmented NAL unit | ||
MUST NOT be transmitted in one FU. | ||
""" | ||
@spec parse(data :: binary()) :: {:error, :packet_malformed} | {:ok, {t(), nal :: binary()}} | ||
def parse(<<start::1, finish::1, 0::1, nal_type::5, rest::binary>>) | ||
when nal_type in 1..23 and valid_frame_boundary(start, finish) do | ||
header = %__MODULE__{ | ||
start_bit: start == 1, | ||
end_bit: finish == 1, | ||
type: nal_type | ||
} | ||
|
||
{:ok, {header, rest}} | ||
end | ||
|
||
def parse(_binary), do: {:error, :packet_malformed} | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
defmodule ExWebRTC.RTP.H264.StapA do | ||
@moduledoc """ | ||
Module responsible for parsing Single Time Agregation Packets type A. | ||
|
||
Documented in [RFC6184](https://tools.ietf.org/html/rfc6184#page-22) | ||
|
||
``` | ||
0 1 2 3 | ||
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 | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| RTP Header | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| NALU 1 Data | | ||
: : | ||
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| | NALU 2 Size | NALU 2 HDR | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| NALU 2 Data | | ||
: : | ||
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| :...OPTIONAL RTP padding | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
``` | ||
""" | ||
|
||
@spec parse(binary()) :: {:ok, [binary()]} | {:error, :packet_malformed} | ||
def parse(data) do | ||
do_parse(data, []) | ||
end | ||
|
||
defp do_parse(<<>>, acc), do: {:ok, Enum.reverse(acc)} | ||
|
||
defp do_parse(<<size::16, nalu::binary-size(size), rest::binary>>, acc), | ||
do: do_parse(rest, [nalu | acc]) | ||
|
||
defp do_parse(_data, _acc), do: {:error, :packet_malformed} | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
defmodule ExWebRTC.RTP.H264.NAL.Header do | ||
@moduledoc """ | ||
Defines a structure representing Network Abstraction Layer Unit Header | ||
|
||
Defined in [RFC 6184](https://tools.ietf.org/html/rfc6184#section-5.3) | ||
|
||
``` | ||
+---------------+ | ||
|0|1|2|3|4|5|6|7| | ||
+-+-+-+-+-+-+-+-+ | ||
|F|NRI| Type | | ||
+---------------+ | ||
``` | ||
""" | ||
|
||
@typedoc """ | ||
NRI stands for nal_ref_idc. This value represents importance of | ||
frame that is being parsed. | ||
|
||
The higher the value the more important frame is (for example key | ||
frames have nri value of 3) and a value of 00 indicates that the | ||
content of the NAL unit is not used to reconstruct reference pictures | ||
for inter picture prediction. NAL units with NRI equal 0 can be discarded | ||
without risking the integrity of the reference pictures, although these | ||
payloads might contain metadata. | ||
""" | ||
@type nri :: 0..3 | ||
|
||
@typedoc """ | ||
Specifies the type of RBSP (Raw Byte Sequence Payload) data structure contained in the NAL unit. | ||
|
||
Types are defined as follows. | ||
|
||
| ID | RBSP Type | | ||
|----------|----------------| | ||
| 0 | Unspecified | | ||
| 1-23 | NAL unit types | | ||
| 24 | STAP-A | | ||
| 25 | STAP-B | | ||
| 26 | MTAP-16 | | ||
| 27 | MTAP-24 | | ||
| 28 | FU-A | | ||
| 29 | FU-B | | ||
| Reserved | 30-31 | | ||
|
||
""" | ||
@type rbsp_type :: 1..31 | ||
@type supported_types :: :stap_a | :fu_a | :single_nalu | ||
@type unsupported_types :: :stap_b | :mtap_16 | :mtap_24 | :fu_b | ||
@type types :: supported_types | unsupported_types | :reserved | ||
|
||
defstruct [:nal_ref_idc, :type] | ||
|
||
@type t :: %__MODULE__{ | ||
nal_ref_idc: nri(), | ||
type: rbsp_type() | ||
} | ||
|
||
@spec parse_unit_header(binary()) :: {:error, :malformed_data} | {:ok, {t(), binary()}} | ||
def parse_unit_header(raw_nal) | ||
|
||
def parse_unit_header(<<0::1, nri::2, type::5, rest::binary>>) do | ||
nal = %__MODULE__{ | ||
nal_ref_idc: nri, | ||
type: type | ||
} | ||
|
||
{:ok, {nal, rest}} | ||
end | ||
|
||
# If first bit is not set to 0 packet is flagged as malformed | ||
def parse_unit_header(_binary), do: {:error, :malformed_data} | ||
|
||
@doc """ | ||
Adds NAL header to payload | ||
""" | ||
@spec add_header(binary(), 0 | 1, nri(), rbsp_type()) :: binary() | ||
def add_header(payload, f, nri, type), | ||
do: <<f::1, nri::2, type::5>> <> payload | ||
|
||
@doc """ | ||
Parses type stored in NAL Header | ||
""" | ||
@spec decode_type(t) :: types() | ||
def decode_type(%__MODULE__{type: type}), do: do_decode_type(type) | ||
|
||
defp do_decode_type(number) when number in 1..21, do: :single_nalu | ||
defp do_decode_type(number) when number in [22, 23], do: :reserved | ||
defp do_decode_type(24), do: :stap_a | ||
defp do_decode_type(25), do: :stap_b | ||
defp do_decode_type(26), do: :mtap_16 | ||
defp do_decode_type(27), do: :mtap_24 | ||
defp do_decode_type(28), do: :fu_a | ||
defp do_decode_type(29), do: :fu_b | ||
defp do_decode_type(number) when number in [30, 31], do: :reserved | ||
defp do_decode_type(_), do: :invalid | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, completely forgot about the existence of
Enum.map_join/2
xddwe can use that instead