|
| 1 | +defmodule ExRTMP.Chunk do |
| 2 | + @moduledoc """ |
| 3 | + RTMP Chunk structure |
| 4 | + """ |
| 5 | + |
| 6 | + @type payload_size :: non_neg_integer() |
| 7 | + @type stream_id :: non_neg_integer() |
| 8 | + |
| 9 | + @type t :: %__MODULE__{ |
| 10 | + fmt: 0..3, |
| 11 | + stream_id: stream_id(), |
| 12 | + timestamp: non_neg_integer() | nil, |
| 13 | + message_length: non_neg_integer() | nil, |
| 14 | + message_type_id: non_neg_integer() | nil, |
| 15 | + message_stream_id: non_neg_integer() | nil, |
| 16 | + payload: binary() | nil |
| 17 | + } |
| 18 | + |
| 19 | + defstruct [ |
| 20 | + :fmt, |
| 21 | + :stream_id, |
| 22 | + :timestamp, |
| 23 | + :message_length, |
| 24 | + :message_type_id, |
| 25 | + :message_stream_id, |
| 26 | + :payload |
| 27 | + ] |
| 28 | + |
| 29 | + @spec parse_header(binary()) :: {:ok, t(), binary()} | :more |
| 30 | + def parse_header(data) do |
| 31 | + with {:ok, chunk, rest} <- parse_stream_id(data), |
| 32 | + {:ok, chunk, rest} <- parse_message_header(chunk, rest), |
| 33 | + {:ok, chunk, rest} <- parse_extended_timestamp(chunk, rest) do |
| 34 | + {:ok, chunk, rest} |
| 35 | + end |
| 36 | + end |
| 37 | + |
| 38 | + @spec serialize(t()) :: iodata() |
| 39 | + def serialize(%__MODULE__{} = chunk) do |
| 40 | + timestamp = if chunk.timestamp, do: <<chunk.timestamp::24>>, else: <<>> |
| 41 | + length = if chunk.message_length, do: <<chunk.message_length::24>>, else: <<>> |
| 42 | + type_id = if chunk.message_type_id, do: <<chunk.message_type_id::8>>, else: <<>> |
| 43 | + |
| 44 | + msg_stream_id = |
| 45 | + if chunk.message_stream_id, do: <<chunk.message_stream_id::32-little>>, else: <<>> |
| 46 | + |
| 47 | + [ |
| 48 | + <<chunk.fmt::2, encode_stream_id(chunk.stream_id)::bitstring>>, |
| 49 | + timestamp, |
| 50 | + length, |
| 51 | + type_id, |
| 52 | + msg_stream_id, |
| 53 | + chunk.payload |
| 54 | + ] |
| 55 | + end |
| 56 | + |
| 57 | + defp parse_stream_id(data) do |
| 58 | + case data do |
| 59 | + <<fmt::2, 0::6, cs_id::8, rest::binary>> -> |
| 60 | + {:ok, %__MODULE__{fmt: fmt, stream_id: cs_id + 64}, rest} |
| 61 | + |
| 62 | + <<fmt::2, 1::6, cs_id::16, rest::binary>> -> |
| 63 | + {:ok, %__MODULE__{fmt: fmt, stream_id: cs_id + 64}, rest} |
| 64 | + |
| 65 | + <<fmt::2, cs_id::6, rest::binary>> -> |
| 66 | + {:ok, %__MODULE__{fmt: fmt, stream_id: cs_id}, rest} |
| 67 | + |
| 68 | + _ -> |
| 69 | + :more |
| 70 | + end |
| 71 | + end |
| 72 | + |
| 73 | + defp parse_message_header(chunk, data) do |
| 74 | + case {chunk.fmt, data} do |
| 75 | + {0, |
| 76 | + <<timestamp::24, length::24, type_id::8, msg_stream_id::integer-32-little, rest::binary>>} -> |
| 77 | + chunk = %{ |
| 78 | + chunk |
| 79 | + | timestamp: timestamp, |
| 80 | + message_length: length, |
| 81 | + message_type_id: type_id, |
| 82 | + message_stream_id: msg_stream_id |
| 83 | + } |
| 84 | + |
| 85 | + {:ok, chunk, rest} |
| 86 | + |
| 87 | + {1, <<timestamp::24, length::24, type_id::8, rest::binary>>} -> |
| 88 | + chunk = %{chunk | timestamp: timestamp, message_length: length, message_type_id: type_id} |
| 89 | + {:ok, chunk, rest} |
| 90 | + |
| 91 | + {2, <<timestamp::24, rest::binary>>} -> |
| 92 | + {:ok, %{chunk | timestamp: timestamp}, rest} |
| 93 | + |
| 94 | + {3, rest} -> |
| 95 | + {:ok, chunk, rest} |
| 96 | + |
| 97 | + _ -> |
| 98 | + :more |
| 99 | + end |
| 100 | + end |
| 101 | + |
| 102 | + defp parse_extended_timestamp(%{timestamp: 0xFFFFFF} = chunk, <<ts::32, rest::binary>>) do |
| 103 | + {:ok, %{chunk | timestamp: ts}, rest} |
| 104 | + end |
| 105 | + |
| 106 | + defp parse_extended_timestamp(%{timestamp: 0xFFFFFF}, _rest), do: :more |
| 107 | + defp parse_extended_timestamp(chunk, data), do: {:ok, chunk, data} |
| 108 | + |
| 109 | + defp encode_stream_id(stream_id) when stream_id in 2..63, do: <<stream_id::6>> |
| 110 | + defp encode_stream_id(stream_id) when stream_id in 64..319, do: <<0::6, stream_id - 64::8>> |
| 111 | + defp encode_stream_id(stream_id) when stream_id >= 320, do: <<1::6, stream_id - 64::16>> |
| 112 | +end |
0 commit comments