Skip to content

Commit 74a4302

Browse files
committed
Add AV1 OBU header parser and OBU types utilities
Implement AV1 bitstream format OBU header parser and utility functions for OBU types.
1 parent a78a4a6 commit 74a4302

File tree

3 files changed

+598
-0
lines changed

3 files changed

+598
-0
lines changed

codecs/av1/obu/errors.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package obu
5+
6+
import "errors"
7+
8+
var (
9+
// ErrInvalidOBUHeader is returned when an OBU header has forbidden bits set.
10+
ErrInvalidOBUHeader = errors.New("invalid OBU header")
11+
// ErrShortHeader is returned when an OBU header is not large enough.
12+
// This can happen when an extension header is expected but not present.
13+
ErrShortHeader = errors.New("OBU header is not large enough")
14+
)

codecs/av1/obu/obu.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package obu
5+
6+
import (
7+
"fmt"
8+
)
9+
10+
// Type represents the type of an AV1 OBU.
11+
type Type uint8
12+
13+
// OBU types as defined in the AV1 specification.
14+
// 5.3.1: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=39
15+
const (
16+
// OBUSequenceHeader av1 sequence_header_obu.
17+
OBUSequenceHeader = Type(1)
18+
// OBUTemporalDelimiter av1 temporal_delimiter_obu.
19+
OBUTemporalDelimiter = Type(2)
20+
// OBUFrameHeader av1 frame_header_obu.
21+
OBUFrameHeader = Type(3)
22+
// OBUTileGroup av1 tile_group_obu.
23+
OBUTileGroup = Type(4)
24+
// OBUMetadata av1 metadata_obu.
25+
OBUMetadata = Type(5)
26+
// OBUFrame av1 frame_obu.
27+
OBUFrame = Type(6)
28+
// OBURedundantFrameHeader av1 redundant_frame_header_obu.
29+
OBURedundantFrameHeader = Type(7)
30+
// OBUTileList av1 tile_list_obu.
31+
OBUTileList = Type(8)
32+
// OBUPadding av1 padding_obu.
33+
OBUPadding = Type(15)
34+
)
35+
36+
//nolint:cyclop
37+
func (o Type) String() string {
38+
switch o {
39+
case OBUSequenceHeader:
40+
return "OBU_SEQUENCE_HEADER"
41+
case OBUTemporalDelimiter:
42+
return "OBU_TEMPORAL_DELIMITER"
43+
case OBUFrameHeader:
44+
return "OBU_FRAME_HEADER"
45+
case OBUTileGroup:
46+
return "OBU_TILE_GROUP"
47+
case OBUMetadata:
48+
return "OBU_METADATA"
49+
case OBUFrame:
50+
return "OBU_FRAME"
51+
case OBURedundantFrameHeader:
52+
return "OBU_REDUNDANT_FRAME_HEADER"
53+
case OBUTileList:
54+
return "OBU_TILE_LIST"
55+
case OBUPadding:
56+
return "OBU_PADDING"
57+
default:
58+
return "OBU_RESERVED"
59+
}
60+
}
61+
62+
// Header represents the header of an OBU obu_header().
63+
// 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
64+
type Header struct {
65+
Type Type
66+
ExtensionHeader *ExtensionHeader
67+
HasSizeField bool
68+
Reserved1Bit bool
69+
}
70+
71+
// ParseOBUHeader parses an OBU header from the given data.
72+
// 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
73+
/*
74+
obu_header() { Type
75+
obu_forbidden_bit f(1)
76+
obu_type f(4)
77+
obu_extension_flag f(1)
78+
obu_has_size_field f(1)
79+
obu_reserved_1bit f(1)
80+
if ( obu_extension_flag == 1 )
81+
obu_extension_header()
82+
}
83+
}
84+
*/
85+
func ParseOBUHeader(data []byte) (*Header, error) {
86+
if len(data) < 1 {
87+
return nil, fmt.Errorf("%w: data is too short", ErrShortHeader)
88+
}
89+
90+
forbiddenBit := data[0] & 0x80
91+
if forbiddenBit != 0 {
92+
return nil, fmt.Errorf("%w: forbidden bit is set", ErrInvalidOBUHeader)
93+
}
94+
95+
obuType := Type((data[0] & 0x78) >> 3)
96+
obuExtensionFlag := (data[0] & 0x04) != 0
97+
obuHasSizeField := (data[0] & 0x02) != 0
98+
obuReserved1Bit := (data[0] & 0x01) != 0
99+
100+
header := &Header{
101+
Type: obuType,
102+
HasSizeField: obuHasSizeField,
103+
Reserved1Bit: obuReserved1Bit,
104+
}
105+
106+
if obuExtensionFlag {
107+
if len(data) < 2 {
108+
return nil, fmt.Errorf("%w: Unexpected end of data, expected extension header", ErrShortHeader)
109+
}
110+
111+
extensionHeader := ParseOBUExtensionHeader(data[1])
112+
header.ExtensionHeader = &extensionHeader
113+
}
114+
115+
return header, nil
116+
}
117+
118+
// Marshal serializes the OBU header to a byte slice.
119+
// If the OBU has an extension header, the extension header is serialized as well.
120+
// 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
121+
/*
122+
obu_header() { Type
123+
obu_forbidden_bit f(1)
124+
obu_type f(4)
125+
obu_extension_flag f(1)
126+
obu_has_size_field f(1)
127+
obu_reserved_1bit f(1)
128+
if ( obu_extension_flag == 1 )
129+
obu_extension_header()
130+
}
131+
}
132+
*/
133+
func (o *Header) Marshal() []byte {
134+
header := make([]byte, o.Size())
135+
136+
header[0] = (byte(o.Type) & 0x0f) << 3
137+
138+
if o.ExtensionHeader != nil {
139+
header[0] |= 0x04
140+
header[1] = o.ExtensionHeader.Marshal()
141+
}
142+
143+
if o.HasSizeField {
144+
header[0] |= 0x02
145+
}
146+
147+
if o.Reserved1Bit {
148+
header[0] |= 0x01
149+
}
150+
151+
return header
152+
}
153+
154+
// Size returns the size of the OBU header in bytes.
155+
func (o *Header) Size() int {
156+
size := 1
157+
if o.ExtensionHeader != nil {
158+
size++
159+
}
160+
161+
return size
162+
}
163+
164+
// ExtensionHeader represents an OBU extension header obu_extension_header().
165+
// 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
166+
type ExtensionHeader struct {
167+
TemporalID uint8
168+
SpatialID uint8
169+
Reserved3Bits uint8
170+
}
171+
172+
// ParseOBUExtensionHeader parses an OBU extension header from the given data.
173+
// 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
174+
/*
175+
obu_extension_header() { Type
176+
temporal_id f(3)
177+
spatial_id f(2)
178+
extension_header_reserved_3bits f(3)
179+
}
180+
*/
181+
func ParseOBUExtensionHeader(headerData byte) ExtensionHeader {
182+
return ExtensionHeader{
183+
TemporalID: headerData >> 5,
184+
SpatialID: (headerData >> 3) & 0x03,
185+
Reserved3Bits: headerData & 0x07,
186+
}
187+
}
188+
189+
// Marshal serializes the OBU extension header to a byte slice.
190+
// 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
191+
/*
192+
obu_extension_header() { Type
193+
temporal_id f(3)
194+
spatial_id f(2)
195+
extension_header_reserved_3bits f(3)
196+
}
197+
*/
198+
func (o *ExtensionHeader) Marshal() byte {
199+
return (o.TemporalID << 5) | ((o.SpatialID & 0x3) << 3) | (o.Reserved3Bits & 0x07)
200+
}
201+
202+
// OBU represents an AV1 OBU.
203+
// 5.1 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=39
204+
type OBU struct {
205+
Header Header
206+
Payload []byte
207+
}
208+
209+
// Marshal serializes the OBU to low-overhead bitstream format.
210+
// 5.2 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40
211+
func (o *OBU) Marshal() []byte {
212+
buffer := o.Header.Marshal()
213+
214+
if o.Header.HasSizeField {
215+
buffer = append(buffer, WriteToLeb128(uint(len(o.Payload)))...)
216+
}
217+
218+
return append(buffer, o.Payload...)
219+
}

0 commit comments

Comments
 (0)