Skip to content

Commit 56225d1

Browse files
committed
Implement av1 depacketizer
Adds AV1Depacketizer which implements the Depacketizer interface for AV1
1 parent 3e1f46a commit 56225d1

File tree

2 files changed

+635
-0
lines changed

2 files changed

+635
-0
lines changed

codecs/av1_depacketizer.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
const (
13+
av1OBUTemporalDelimiter = 2
14+
av1OBUTileList = 8
15+
)
16+
17+
// AV1Depacketizer is a AV1 RTP Packet depacketizer.
18+
type AV1Depacketizer struct {
19+
// holds the fragmented OBU from the previous packet.
20+
buffer []byte
21+
}
22+
23+
// Unmarshal parses an AV1 RTP payload into its constituent OBU elements.
24+
// It assumes that the payload is in order (e.g. the caller is responsible for reordering RTP packets).
25+
// If the last OBU in the payload is fragmented, it will be stored in the buffer until the
26+
// it is completed.
27+
//
28+
//nolint:gocognit,cyclop
29+
func (d *AV1Depacketizer) Unmarshal(payload []byte) (buff []byte, err error) {
30+
if len(payload) <= 1 {
31+
return nil, errShortPacket
32+
}
33+
34+
// |Z|Y| W |N|-|-|-|
35+
obuZ := (0b10000000 & payload[0]) != 0 // Z
36+
obuY := (0b01000000 & payload[0]) != 0 // Y
37+
obuCount := (0b00110000 & payload[0]) >> 4 // W
38+
buff = make([]byte, 0)
39+
40+
// Make sure we clear the buffer if Z is not 0.
41+
if !obuZ && len(d.buffer) > 0 {
42+
buff = nil
43+
}
44+
45+
obuOffset := 0
46+
for offset := 1; offset < len(payload); obuOffset++ {
47+
isFirst := obuOffset == 0
48+
isLast := obuCount != 0 && obuOffset == int(obuCount)-1
49+
50+
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
51+
// W: two bit field that describes the number of OBU elements in the packet.
52+
// This field MUST be set equal to 0 or equal to the number of OBU elements contained in the packet.
53+
// If set to 0, each OBU element MUST be preceded by a length field. If not set to 0
54+
// (i.e., W = 1, 2 or 3) the last OBU element MUST NOT be preceded by a length field.
55+
var obuSize, n int
56+
if obuCount == 0 || !isLast {
57+
obuSizeVal, nVal, err := obu.ReadLeb128(payload[offset:])
58+
obuSize = int(obuSizeVal) //nolint:gosec // G115 false positive
59+
n = int(nVal) //nolint:gosec // G115 false positive
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
offset += n
65+
if obuCount == 0 && offset+obuSize == len(payload) {
66+
isLast = true
67+
}
68+
} else {
69+
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
70+
// Length of the last OBU element =
71+
// length of the RTP payload
72+
// - length of aggregation header
73+
// - length of previous OBU elements including length fields
74+
obuSize = len(payload) - offset
75+
}
76+
77+
if offset+obuSize > len(payload) {
78+
return nil, fmt.Errorf(
79+
"%w: OBU size %d + %d offset exceeds payload length %d",
80+
errShortPacket, obuSize, offset, len(payload),
81+
)
82+
}
83+
84+
var obuBuffer []byte
85+
if isFirst && obuZ {
86+
// We lost the first fragment of the OBU
87+
// We drop the buffer and continue
88+
if len(d.buffer) == 0 {
89+
if isLast {
90+
break
91+
}
92+
93+
offset += obuSize
94+
95+
continue
96+
}
97+
98+
obuBuffer = make([]byte, len(d.buffer)+obuSize)
99+
100+
copy(obuBuffer, d.buffer)
101+
copy(obuBuffer[len(d.buffer):], payload[offset:offset+obuSize])
102+
d.buffer = nil
103+
} else {
104+
obuBuffer = payload[offset : offset+obuSize]
105+
}
106+
offset += obuSize
107+
108+
if isLast && obuY {
109+
d.buffer = obuBuffer
110+
} else {
111+
if len(obuBuffer) == 0 {
112+
return nil, fmt.Errorf(
113+
"%w: OBU size %d is 0",
114+
errShortPacket, obuSize,
115+
)
116+
}
117+
118+
// The temporal delimiter OBU, if present, SHOULD be removed when transmitting,
119+
// and MUST be ignored by receivers. Tile list OBUs are not supported.
120+
// They SHOULD be removed when transmitted, and MUST be ignored by receivers.
121+
// https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules
122+
123+
obuType := (obuBuffer[0] & obuFrameTypeMask) >> obuFrameTypeBitshift
124+
125+
if obuType != av1OBUTemporalDelimiter && obuType != av1OBUTileList {
126+
buff = append(buff, obuBuffer...)
127+
}
128+
}
129+
130+
if isLast {
131+
break
132+
}
133+
}
134+
135+
if obuCount != 0 && obuOffset != int(obuCount-1) {
136+
return nil, fmt.Errorf(
137+
"%w: OBU count %d does not match number of OBUs %d",
138+
errShortPacket, obuCount, obuOffset,
139+
)
140+
}
141+
142+
return buff, nil
143+
}
144+
145+
// IsPartitionTail returns true if RTP packet marker is set.
146+
// Clear the buffer if we are at the end of the partition.
147+
func (d *AV1Depacketizer) IsPartitionTail(marker bool, _ []byte) bool {
148+
if marker {
149+
// We make sure we clear the buffer if we are at the end of the partition.
150+
d.buffer = nil
151+
152+
return true
153+
}
154+
155+
return false
156+
}
157+
158+
// IsPartitionHead returns true if Z in the AV1 Aggregation Header
159+
// is set to 0.
160+
func (d *AV1Depacketizer) IsPartitionHead(payload []byte) bool {
161+
if len(payload) == 0 {
162+
return false
163+
}
164+
165+
return (payload[0] & 0b11000000) == 0
166+
}

0 commit comments

Comments
 (0)