Skip to content

Commit 48e175c

Browse files
committed
Implement av1 depacketizer
Adds AV1Depacketizer which implements the Depacketizer interface for AV1
1 parent 74a4302 commit 48e175c

File tree

2 files changed

+865
-0
lines changed

2 files changed

+865
-0
lines changed

codecs/av1_depacketizer.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package codecs
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/pion/rtp/codecs/av1/obu"
10+
)
11+
12+
// AV1Depacketizer is a AV1 RTP Packet depacketizer.
13+
// Reads AV1 packets from a RTP stream and outputs AV1 low overhead bitstream.
14+
type AV1Depacketizer struct {
15+
// holds the fragmented OBU from the previous packet.
16+
buffer []byte
17+
18+
// Z, Y, N are flags from the AV1 Aggregation Header.
19+
Z, Y, N bool
20+
21+
videoDepacketizer
22+
}
23+
24+
// Unmarshal parses an AV1 RTP payload into its constituent OBUs stream with obu_size_field,
25+
// It assumes that the payload is in order (e.g. the caller is responsible for reordering RTP packets).
26+
// If the last OBU in the payload is fragmented, it will be stored in the buffer until the
27+
// it is completed.
28+
//
29+
//nolint:gocognit,cyclop
30+
func (d *AV1Depacketizer) Unmarshal(payload []byte) (buff []byte, err error) {
31+
buff = make([]byte, 0)
32+
33+
if len(payload) <= 1 {
34+
return nil, errShortPacket
35+
}
36+
37+
// |Z|Y| W |N|-|-|-|
38+
obuZ := (0b10000000 & payload[0]) != 0 // Z
39+
obuY := (0b01000000 & payload[0]) != 0 // Y
40+
obuCount := (0b00110000 & payload[0]) >> 4 // W
41+
obuN := (0b00001000 & payload[0]) != 0 // N
42+
d.Z = obuZ
43+
d.Y = obuY
44+
d.N = obuN
45+
if obuN {
46+
d.buffer = nil
47+
}
48+
49+
// Make sure we clear the buffer if Z is not 0.
50+
if !obuZ && len(d.buffer) > 0 {
51+
d.buffer = nil
52+
}
53+
54+
obuOffset := 0
55+
for offset := 1; offset < len(payload); obuOffset++ {
56+
isFirst := obuOffset == 0
57+
isLast := obuCount != 0 && obuOffset == int(obuCount)-1
58+
59+
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
60+
// W: two bit field that describes the number of OBU elements in the packet.
61+
// This field MUST be set equal to 0 or equal to the number of OBU elements contained in the packet.
62+
// If set to 0, each OBU element MUST be preceded by a length field. If not set to 0
63+
// (i.e., W = 1, 2 or 3) the last OBU element MUST NOT be preceded by a length field.
64+
var lengthField, n int
65+
if obuCount == 0 || !isLast {
66+
obuSizeVal, nVal, err := obu.ReadLeb128(payload[offset:])
67+
lengthField = int(obuSizeVal) //nolint:gosec // G115 false positive
68+
n = int(nVal) //nolint:gosec // G115 false positive
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
offset += n
74+
if obuCount == 0 && offset+lengthField == len(payload) {
75+
isLast = true
76+
}
77+
} else {
78+
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
79+
// Length of the last OBU element =
80+
// length of the RTP payload
81+
// - length of aggregation header
82+
// - length of previous OBU elements including length fields
83+
lengthField = len(payload) - offset
84+
}
85+
86+
if offset+lengthField > len(payload) {
87+
return nil, fmt.Errorf(
88+
"%w: OBU size %d + %d offset exceeds payload length %d",
89+
errShortPacket, lengthField, offset, len(payload),
90+
)
91+
}
92+
93+
var obuBuffer []byte
94+
if isFirst && obuZ {
95+
// We lost the first fragment of the OBU
96+
// We drop the buffer and continue
97+
if len(d.buffer) == 0 {
98+
if isLast {
99+
break
100+
}
101+
offset += lengthField
102+
103+
continue
104+
}
105+
106+
obuBuffer = make([]byte, len(d.buffer)+lengthField)
107+
108+
copy(obuBuffer, d.buffer)
109+
copy(obuBuffer[len(d.buffer):], payload[offset:offset+lengthField])
110+
d.buffer = nil
111+
} else {
112+
obuBuffer = payload[offset : offset+lengthField]
113+
}
114+
offset += lengthField
115+
116+
if isLast && obuY {
117+
d.buffer = obuBuffer
118+
119+
break
120+
}
121+
122+
if len(obuBuffer) == 0 {
123+
return nil, fmt.Errorf(
124+
"%w: OBU size %d is 0",
125+
errShortPacket, lengthField,
126+
)
127+
}
128+
129+
obuHeader, err := obu.ParseOBUHeader(obuBuffer)
130+
if err != nil {
131+
return nil, err
132+
}
133+
134+
// The temporal delimiter OBU, if present, SHOULD be removed when transmitting,
135+
// and MUST be ignored by receivers. Tile list OBUs are not supported.
136+
// They SHOULD be removed when transmitted, and MUST be ignored by receivers.
137+
// https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules
138+
if obuHeader.Type == obu.OBUTemporalDelimiter || obuHeader.Type == obu.OBUTileList {
139+
continue
140+
}
141+
142+
// obu_has_size_field should be set to 0 for AV1 RTP packets.
143+
// But we still check it to be sure, if we get obu size we just use it, instead of calculating it.
144+
if obuHeader.HasSizeField {
145+
obuSize, n, err := obu.ReadLeb128(obuBuffer[obuHeader.Size():])
146+
if err != nil {
147+
return nil, err
148+
}
149+
150+
// We validate the obu_size_field if it is present.
151+
sizeFromOBUSize := obuHeader.Size() + int(obuSize) + int(n) //nolint:gosec
152+
if lengthField != sizeFromOBUSize {
153+
return nil, fmt.Errorf(
154+
"%w: OBU size %d does not match calculated size %d",
155+
errShortPacket, obuSize, sizeFromOBUSize,
156+
)
157+
}
158+
159+
buff = append(buff, obuBuffer...)
160+
} else {
161+
obuHeader.HasSizeField = true
162+
buff = append(buff, obuHeader.Marshal()...)
163+
size := len(obuBuffer) - obuHeader.Size()
164+
buff = append(buff, obu.WriteToLeb128(uint(size))...) // nolint: gosec // G104
165+
buff = append(buff, obuBuffer[obuHeader.Size():]...)
166+
}
167+
168+
if isLast {
169+
break
170+
}
171+
}
172+
173+
if obuCount != 0 && obuOffset != int(obuCount-1) {
174+
return nil, fmt.Errorf(
175+
"%w: OBU count %d does not match number of OBUs %d",
176+
errShortPacket, obuCount, obuOffset,
177+
)
178+
}
179+
180+
return buff, nil
181+
}
182+
183+
// IsPartitionHead returns true if Z in the AV1 Aggregation Header
184+
// is set to 0.
185+
func (d *AV1Depacketizer) IsPartitionHead(payload []byte) bool {
186+
if len(payload) == 0 {
187+
return false
188+
}
189+
190+
return (payload[0] & 0b10000000) == 0
191+
}

0 commit comments

Comments
 (0)