kvm/vendor/github.com/pion/rtp/codecs/av1_depacketizer.go

189 lines
5.3 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
import (
"fmt"
"github.com/pion/rtp/codecs/av1/obu"
)
// AV1Depacketizer is a AV1 RTP Packet depacketizer.
// Reads AV1 packets from a RTP stream and outputs AV1 low overhead bitstream.
type AV1Depacketizer struct {
// holds the fragmented OBU from the previous packet.
buffer []byte
// Z, Y, N are flags from the AV1 Aggregation Header.
Z, Y, N bool
videoDepacketizer
}
// Unmarshal parses an AV1 RTP payload into its constituent OBUs stream with obu_size_field,
// It assumes that the payload is in order (e.g. the caller is responsible for reordering RTP packets).
// If the last OBU in the payload is fragmented, it will be stored in the buffer until the
// it is completed.
//
//nolint:gocognit,cyclop
func (d *AV1Depacketizer) Unmarshal(payload []byte) (buff []byte, err error) {
buff = make([]byte, 0)
if len(payload) <= 1 {
return nil, errShortPacket
}
// |Z|Y| W |N|-|-|-|
obuZ := (av1ZMask & payload[0]) != 0 // Z
obuY := (av1YMask & payload[0]) != 0 // Y
obuCount := (av1WMask & payload[0]) >> 4 // W
obuN := (av1NMask & payload[0]) != 0 // N
d.Z = obuZ
d.Y = obuY
d.N = obuN
if obuN {
d.buffer = nil
}
// Make sure we clear the buffer if Z is not 0.
if !obuZ && len(d.buffer) > 0 {
d.buffer = nil
}
obuOffset := 0
for offset := 1; offset < len(payload); obuOffset++ {
isFirst := obuOffset == 0
isLast := obuCount != 0 && obuOffset == int(obuCount)-1
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
// W: two bit field that describes the number of OBU elements in the packet.
// This field MUST be set equal to 0 or equal to the number of OBU elements contained in the packet.
// If set to 0, each OBU element MUST be preceded by a length field. If not set to 0
// (i.e., W = 1, 2 or 3) the last OBU element MUST NOT be preceded by a length field.
var lengthField, n int
if obuCount == 0 || !isLast {
obuSizeVal, nVal, err := obu.ReadLeb128(payload[offset:])
lengthField = int(obuSizeVal) //nolint:gosec // G115 false positive
n = int(nVal) //nolint:gosec // G115 false positive
if err != nil {
return nil, err
}
offset += n
if obuCount == 0 && offset+lengthField == len(payload) {
isLast = true
}
} else {
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
// Length of the last OBU element =
// length of the RTP payload
// - length of aggregation header
// - length of previous OBU elements including length fields
lengthField = len(payload) - offset
}
if offset+lengthField > len(payload) {
return nil, fmt.Errorf(
"%w: OBU size %d + %d offset exceeds payload length %d",
errShortPacket, lengthField, offset, len(payload),
)
}
var obuBuffer []byte
if isFirst && obuZ {
// We lost the first fragment of the OBU
// We drop the buffer and continue
if len(d.buffer) == 0 {
if isLast {
break
}
offset += lengthField
continue
}
obuBuffer = make([]byte, len(d.buffer)+lengthField)
copy(obuBuffer, d.buffer)
copy(obuBuffer[len(d.buffer):], payload[offset:offset+lengthField])
d.buffer = nil
} else {
obuBuffer = payload[offset : offset+lengthField]
}
offset += lengthField
if isLast && obuY {
d.buffer = obuBuffer
break
}
if len(obuBuffer) == 0 {
continue
}
obuHeader, err := obu.ParseOBUHeader(obuBuffer)
if err != nil {
return nil, err
}
// The temporal delimiter OBU, if present, SHOULD be removed when transmitting,
// and MUST be ignored by receivers. Tile list OBUs are not supported.
// They SHOULD be removed when transmitted, and MUST be ignored by receivers.
// https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules
if obuHeader.Type == obu.OBUTemporalDelimiter || obuHeader.Type == obu.OBUTileList {
continue
}
// obu_has_size_field should be set to 0 for AV1 RTP packets.
// But we still check it to be sure, if we get obu size we just use it, instead of calculating it.
if obuHeader.HasSizeField {
obuSize, n, err := obu.ReadLeb128(obuBuffer[obuHeader.Size():])
if err != nil {
return nil, err
}
// We validate the obu_size_field if it is present.
sizeFromOBUSize := obuHeader.Size() + int(obuSize) + int(n) //nolint:gosec
if lengthField != sizeFromOBUSize {
return nil, fmt.Errorf(
"%w: OBU size %d does not match calculated size %d",
errShortPacket, obuSize, sizeFromOBUSize,
)
}
buff = append(buff, obuBuffer...)
} else {
obuHeader.HasSizeField = true
buff = append(buff, obuHeader.Marshal()...)
size := len(obuBuffer) - obuHeader.Size()
buff = append(buff, obu.WriteToLeb128(uint(size))...) // nolint: gosec // G104
buff = append(buff, obuBuffer[obuHeader.Size():]...)
}
if isLast {
break
}
}
if obuCount != 0 && obuOffset != int(obuCount-1) {
return nil, fmt.Errorf(
"%w: OBU count %d does not match number of OBUs %d",
errShortPacket, obuCount, obuOffset,
)
}
return buff, nil
}
// IsPartitionHead returns true if Z in the AV1 Aggregation Header
// is set to 0.
func (d *AV1Depacketizer) IsPartitionHead(payload []byte) bool {
if len(payload) == 0 {
return false
}
return (payload[0] & av1ZMask) == 0
}