|
| 1 | +defmodule ExWebRTC.RTP.AV1.OBU do |
| 2 | + @moduledoc false |
| 3 | + # Defines the Open Bitstream Unit, the base packetization unit of all structures present in the AV1 bitstream. |
| 4 | + # |
| 5 | + # Based on [the AV1 spec](https://aomediacodec.github.io/av1-spec/av1-spec.pdf). |
| 6 | + # |
| 7 | + # OBU syntax: |
| 8 | + # 0 1 2 3 4 5 6 7 |
| 9 | + # +-+-+-+-+-+-+-+-+ |
| 10 | + # |0| type |X|S|-| (REQUIRED) |
| 11 | + # +-+-+-+-+-+-+-+-+ |
| 12 | + # X: | TID |SID|-|-|-| (OPTIONAL) |
| 13 | + # +-+-+-+-+-+-+-+-+ |
| 14 | + # |1| | |
| 15 | + # +-+ OBU payload | |
| 16 | + # S: |1| | (OPTIONAL, variable length leb128 encoded) |
| 17 | + # +-+ size | |
| 18 | + # |0| | |
| 19 | + # +-+-+-+-+-+-+-+-+ |
| 20 | + # | OBU payload | |
| 21 | + # | ... | |
| 22 | + |
| 23 | + alias ExWebRTC.RTP.AV1.LEB128 |
| 24 | + |
| 25 | + @obu_sequence_header 1 |
| 26 | + @obu_temporal_delimiter 2 |
| 27 | + @obu_padding 15 |
| 28 | + |
| 29 | + @type t :: %__MODULE__{ |
| 30 | + type: 0..15, |
| 31 | + x: 0 | 1, |
| 32 | + s: 0 | 1, |
| 33 | + tid: 0..7 | nil, |
| 34 | + sid: 0..3 | nil, |
| 35 | + payload: binary() |
| 36 | + } |
| 37 | + |
| 38 | + @enforce_keys [:type, :x, :s, :payload] |
| 39 | + defstruct @enforce_keys ++ [:tid, :sid] |
| 40 | + |
| 41 | + @doc """ |
| 42 | + Parses the low overhead bitstream format defined in AV1 spec section 5.2. |
| 43 | + On success, returns the parsed OBU as well as the remainder of the AV1 bitstream. |
| 44 | + """ |
| 45 | + @spec parse(binary()) :: {:ok, t(), binary()} | {:error, :invalid_av1_bitstream} |
| 46 | + def parse(av1_bitstream_binary) |
| 47 | + |
| 48 | + def parse(<<0::1, type::4, x::1, s::1, 0::1, rest::binary>>) do |
| 49 | + with {:ok, tid, sid, rest} <- parse_extension_header(x, rest), |
| 50 | + {:ok, payload, rest} <- parse_payload(s, rest), |
| 51 | + :ok <- validate_payload(type, payload) do |
| 52 | + {:ok, |
| 53 | + %__MODULE__{ |
| 54 | + type: type, |
| 55 | + x: x, |
| 56 | + s: s, |
| 57 | + tid: tid, |
| 58 | + sid: sid, |
| 59 | + payload: payload |
| 60 | + }, rest} |
| 61 | + else |
| 62 | + {:error, _} = err -> err |
| 63 | + end |
| 64 | + end |
| 65 | + |
| 66 | + def parse(_), do: {:error, :invalid_av1_bitstream} |
| 67 | + |
| 68 | + defp parse_extension_header(0, rest), do: {:ok, nil, nil, rest} |
| 69 | + |
| 70 | + defp parse_extension_header(1, <<tid::3, sid::2, 0::3, rest::binary>>), |
| 71 | + do: {:ok, tid, sid, rest} |
| 72 | + |
| 73 | + defp parse_extension_header(_, _), do: {:error, :invalid_av1_bitstream} |
| 74 | + |
| 75 | + defp parse_payload(0, rest), do: {:ok, rest, <<>>} |
| 76 | + |
| 77 | + defp parse_payload(1, rest) do |
| 78 | + with {:ok, leb128_size, payload_size} <- LEB128.read(rest), |
| 79 | + <<_::binary-size(leb128_size), payload::binary-size(payload_size), rest::binary>> <- rest do |
| 80 | + {:ok, payload, rest} |
| 81 | + else |
| 82 | + _ -> {:error, :invalid_av1_bitstream} |
| 83 | + end |
| 84 | + end |
| 85 | + |
| 86 | + defp validate_payload(@obu_padding, _), do: :ok |
| 87 | + defp validate_payload(@obu_temporal_delimiter, <<>>), do: :ok |
| 88 | + defp validate_payload(type, data) when type != @obu_temporal_delimiter and data != <<>>, do: :ok |
| 89 | + defp validate_payload(_, _), do: {:error, :invalid_av1_bitstream} |
| 90 | + |
| 91 | + @spec serialize(t()) :: binary() |
| 92 | + def serialize(%__MODULE__{type: type, x: x, s: s, payload: payload} = obu) do |
| 93 | + obu_binary = |
| 94 | + <<0::1, type::4, x::1, s::1, 0::1>> |
| 95 | + |> add_extension_header(obu) |
| 96 | + |> add_payload_size(obu) |
| 97 | + |
| 98 | + <<obu_binary::binary, payload::binary>> |
| 99 | + end |
| 100 | + |
| 101 | + defp add_extension_header(obu_binary, %__MODULE__{x: 0, tid: nil, sid: nil}), do: obu_binary |
| 102 | + |
| 103 | + defp add_extension_header(obu_binary, %__MODULE__{x: 1, tid: tid, sid: sid}) |
| 104 | + when tid != nil and sid != nil do |
| 105 | + <<obu_binary::binary, tid::3, sid::2, 0::3>> |
| 106 | + end |
| 107 | + |
| 108 | + defp add_extension_header(_obu_binary, _invalid_obu), |
| 109 | + do: raise("AV1 TID and SID must be set if, and only if X bit is set") |
| 110 | + |
| 111 | + defp add_payload_size(obu_binary, %__MODULE__{s: 0}), do: obu_binary |
| 112 | + |
| 113 | + defp add_payload_size(obu_binary, %__MODULE__{s: 1, payload: payload}) do |
| 114 | + payload_size = payload |> byte_size() |> LEB128.encode() |
| 115 | + <<obu_binary::binary, payload_size::binary>> |
| 116 | + end |
| 117 | + |
| 118 | + @doc """ |
| 119 | + Rewrites a specific case of the sequence header OBU to disable OBU dropping in the AV1 decoder |
| 120 | + in accordance with av1-rtp-spec sec. 5. Leaves other OBUs unchanged. |
| 121 | + """ |
| 122 | + @spec disable_dropping_in_decoder_if_applicable(t()) :: t() |
| 123 | + def disable_dropping_in_decoder_if_applicable(obu) |
| 124 | + |
| 125 | + # We're handling the following case: |
| 126 | + # - still_picture = 0 |
| 127 | + # - reduced_still_picture_header = 0 |
| 128 | + # - timing_info_present_flag = 0 |
| 129 | + # - operating_points_cnt_minus_1 = 0 |
| 130 | + # - seq_level_idx[0] = 0 |
| 131 | + # and setting operating_point_idc[0] = 0xFFF |
| 132 | + # |
| 133 | + # For the sequence header OBU syntax, refer to the AV1 spec sec. 5.5. |
| 134 | + def disable_dropping_in_decoder_if_applicable( |
| 135 | + %__MODULE__{ |
| 136 | + type: @obu_sequence_header, |
| 137 | + payload: <<seq_profile::3, 0::3, iddpf::1, 0::5, _op_idc_0::12, 0::5, rest::bitstring>> |
| 138 | + } = obu |
| 139 | + ) do |
| 140 | + %{obu | payload: <<seq_profile::3, 0::3, iddpf::1, 0::5, 0xFFF::12, 0::5, rest::bitstring>>} |
| 141 | + end |
| 142 | + |
| 143 | + def disable_dropping_in_decoder_if_applicable(obu), do: obu |
| 144 | +end |
0 commit comments