|
| 1 | +defmodule ExWebRTC.RTP.Depayloader.H264 do |
| 2 | + @moduledoc false |
| 3 | + # Extracts H264 NAL Units from RTP packets. |
| 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 | + @annexb_prefix <<1::32>> |
| 16 | + |
| 17 | + @type t() :: %__MODULE__{ |
| 18 | + current_timestamp: non_neg_integer() | nil, |
| 19 | + fu_parser_acc: [binary()] |
| 20 | + } |
| 21 | + |
| 22 | + defstruct current_timestamp: nil, fu_parser_acc: [] |
| 23 | + |
| 24 | + @impl true |
| 25 | + def new() do |
| 26 | + %__MODULE__{} |
| 27 | + end |
| 28 | + |
| 29 | + @impl true |
| 30 | + def depayload(depayloader, %ExRTP.Packet{payload: <<>>, padding: true}), do: {nil, depayloader} |
| 31 | + |
| 32 | + def depayload(depayloader, packet) do |
| 33 | + with {:ok, {header, _payload} = nal} <- NAL.Header.parse(packet.payload), |
| 34 | + unit_type = NAL.Header.decode_type(header), |
| 35 | + {:ok, {nal, depayloader}} <- |
| 36 | + do_depayload(unit_type, depayloader, packet, nal) do |
| 37 | + {nal, depayloader} |
| 38 | + else |
| 39 | + {:error, reason} -> |
| 40 | + Logger.debug(""" |
| 41 | + Couldn't parse payload, reason: #{reason}. \ |
| 42 | + Resetting depayloader state. Payload: #{inspect(packet.payload)}.\ |
| 43 | + """) |
| 44 | + |
| 45 | + {nil, %{depayloader | current_timestamp: nil, fu_parser_acc: []}} |
| 46 | + end |
| 47 | + end |
| 48 | + |
| 49 | + defp do_depayload(:single_nalu, depayloader, packet, {_header, payload}) do |
| 50 | + {:ok, |
| 51 | + {prefix_annexb(payload), %__MODULE__{depayloader | current_timestamp: packet.timestamp}}} |
| 52 | + end |
| 53 | + |
| 54 | + defp do_depayload( |
| 55 | + :fu_a, |
| 56 | + %{current_timestamp: current_timestamp, fu_parser_acc: fu_parser_acc}, |
| 57 | + packet, |
| 58 | + {_header, _payload} |
| 59 | + ) |
| 60 | + when fu_parser_acc != [] and current_timestamp != packet.timestamp do |
| 61 | + Logger.warning(""" |
| 62 | + received packet with fu-a type payload that is not a start of fragmentation unit with timestamp \ |
| 63 | + different than last start and without finishing the previous fu. dropping fu.\ |
| 64 | + """) |
| 65 | + |
| 66 | + {:error, :invalid_timestamp} |
| 67 | + end |
| 68 | + |
| 69 | + defp do_depayload( |
| 70 | + :fu_a, |
| 71 | + %{fu_parser_acc: fu_parser_acc}, |
| 72 | + packet, |
| 73 | + {header, payload} |
| 74 | + ) do |
| 75 | + case FU.parse(payload, fu_parser_acc || []) do |
| 76 | + {:ok, {data, type}} -> |
| 77 | + data = NAL.Header.add(data, 0, header.nal_ref_idc, type) |
| 78 | + |
| 79 | + {:ok, |
| 80 | + {prefix_annexb(data), |
| 81 | + %__MODULE__{current_timestamp: packet.timestamp, fu_parser_acc: []}}} |
| 82 | + |
| 83 | + {:incomplete, fu} -> |
| 84 | + {:ok, {nil, %__MODULE__{fu_parser_acc: fu, current_timestamp: packet.timestamp}}} |
| 85 | + |
| 86 | + {:error, _reason} = error -> |
| 87 | + error |
| 88 | + end |
| 89 | + end |
| 90 | + |
| 91 | + defp do_depayload(:stap_a, depayloader, packet, {_header, payload}) do |
| 92 | + with {:ok, result} <- StapA.parse(payload) do |
| 93 | + nals = result |> Enum.map_join(&prefix_annexb/1) |
| 94 | + {:ok, {nals, %__MODULE__{depayloader | current_timestamp: packet.timestamp}}} |
| 95 | + end |
| 96 | + end |
| 97 | + |
| 98 | + defp do_depayload(unsupported_type, _depayloader, _packet, _nal) do |
| 99 | + Logger.warning(""" |
| 100 | + Received packet with unsupported NAL type: #{unsupported_type}. Supported types are: Single NALU, STAP-A, FU-A. Dropping packet. |
| 101 | + """) |
| 102 | + |
| 103 | + {:error, :unsupported_nal_type} |
| 104 | + end |
| 105 | + |
| 106 | + defp prefix_annexb(nal) do |
| 107 | + @annexb_prefix <> nal |
| 108 | + end |
| 109 | +end |
0 commit comments