Skip to content

Commit

Permalink
Add AV1 Support
Browse files Browse the repository at this point in the history
Implements Marshal and Unmarshal support. Marshal doesn't pack multiple
OBUs into one packet and could be improved.
  • Loading branch information
Sean-Der committed Apr 11, 2022
1 parent b08333e commit 62d6782
Show file tree
Hide file tree
Showing 8 changed files with 635 additions and 2 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ jobs:
- uses: codecov/codecov-action@v2
with:
file: ./cover.out
name: codecov-umbrella
fail_ci_if_error: true
flags: go
Expand Down Expand Up @@ -153,7 +152,6 @@ jobs:
- uses: codecov/codecov-action@v2
with:
file: ./cover.out
name: codecov-umbrella
fail_ci_if_error: true
flags: wasm
41 changes: 41 additions & 0 deletions codecs/av1_frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package codecs

// AV1Frame represents a collection of OBUs given a stream of AV1 Packets.
// Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one.
// AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements. This structure
// contains an internal cache and should be used for the entire RTP Stream.
type AV1Frame struct {
// Buffer for fragmented OBU. If ReadFrames is called on a RTP Packet
// that doesn't contain a fully formed OBU
obuBuffer []byte
}

func (f *AV1Frame) pushOBUElement(isFirstOBUFragment *bool, obuElement []byte, obuList [][]byte) [][]byte {
if *isFirstOBUFragment {
*isFirstOBUFragment = false
// Discard pushed because we don't have a fragment to combine it with
if f.obuBuffer == nil {
return obuList
}
obuElement = append(f.obuBuffer, obuElement...)
f.obuBuffer = nil
}
return append(obuList, obuElement)
}

// ReadFrames processes the AV1Packet and returns fully constructed frames
func (f *AV1Frame) ReadFrames(pkt *AV1Packet) ([][]byte, error) {
OBUs := [][]byte{}
isFirstOBUFragment := pkt.Z

for i := range pkt.OBUElements {
OBUs = f.pushOBUElement(&isFirstOBUFragment, pkt.OBUElements[i], OBUs)
}

if pkt.Y {
// Take copy of OBUElement that is being cached
f.obuBuffer = append(f.obuBuffer, append([]byte{}, OBUs[len(OBUs)-1]...)...)
OBUs = OBUs[:len(OBUs)-1]
}
return OBUs, nil
}
81 changes: 81 additions & 0 deletions codecs/av1_frame_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package codecs

import (
"reflect"
"testing"
)

// First is Fragment (and no buffer)
// Self contained OBU
// OBU spread across 3 packets
func TestAV1_ReadFrames(t *testing.T) {
// First is Fragment of OBU, but no OBU Elements is cached
f := &AV1Frame{}
frames, err := f.ReadFrames(&AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}})
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(frames, [][]byte{}) {
t.Fatalf("No frames should be generated, %v", frames)
}

f = &AV1Frame{}
frames, err = f.ReadFrames(&AV1Packet{OBUElements: [][]byte{{0x01}}})
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(frames, [][]byte{{0x01}}) {
t.Fatalf("One frame should be generated, %v", frames)
}

f = &AV1Frame{}
frames, err = f.ReadFrames(&AV1Packet{Y: true, OBUElements: [][]byte{{0x00}}})
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(frames, [][]byte{}) {
t.Fatalf("No frames should be generated, %v", frames)
}

frames, err = f.ReadFrames(&AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}})
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(frames, [][]byte{{0x00, 0x01}}) {
t.Fatalf("One frame should be generated, %v", frames)
}
}

// Marshal some AV1 Frames to RTP, assert that AV1Frame can get them back in the original format
func TestAV1_ReadFrames_E2E(t *testing.T) {
const mtu = 1500
frames := [][]byte{
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
{0x00, 0x01},
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
{0x00, 0x01},
}

frames = append(frames, []byte{})
for i := 0; i <= 5; i++ {
frames[len(frames)-1] = append(frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}...)
}

frames = append(frames, []byte{})
for i := 0; i <= 500; i++ {
frames[len(frames)-1] = append(frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}...)
}

payloader := &AV1Payloader{}
f := &AV1Frame{}
for _, originalFrame := range frames {
for _, payload := range payloader.Payload(mtu, originalFrame) {
rtpPacket := &AV1Packet{}
if _, err := rtpPacket.Unmarshal(payload); err != nil {
t.Fatal(err)
}
decodedFrame, err := f.ReadFrames(rtpPacket)
if err != nil {
t.Fatal(err)
} else if len(decodedFrame) != 0 && !reflect.DeepEqual(originalFrame, decodedFrame[0]) {
t.Fatalf("Decode(%02x) and Original(%02x) are not equal", decodedFrame[0], originalFrame)
}
}
}
}
158 changes: 158 additions & 0 deletions codecs/av1_packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package codecs

import (
"github.com/pion/rtp/v2/pkg/obu"
)

const (
zMask = byte(0b10000000)
zBitshift = 7

yMask = byte(0b01000000)
yBitshift = 6

wMask = byte(0b00110000)
wBitshift = 4

nMask = byte(0b00001000)
nBitshift = 3

av1PayloaderHeadersize = 1
)

// AV1Payloader payloads AV1 packets
type AV1Payloader struct{}

// Payload fragments a AV1 packet across one or more byte arrays
// See AV1Packet for description of AV1 Payload Header
func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) {
maxFragmentSize := int(mtu) - av1PayloaderHeadersize - 2
payloadDataRemaining := len(payload)
payloadDataIndex := 0

// Make sure the fragment/payload size is correct
if min(maxFragmentSize, payloadDataRemaining) <= 0 {
return payloads
}

for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
leb128Size := 1
if currentFragmentSize >= 127 {
leb128Size = 2
}

out := make([]byte, av1PayloaderHeadersize+leb128Size+currentFragmentSize)
leb128Value := obu.EncodeLEB128(uint(currentFragmentSize))
if leb128Size == 1 {
out[1] = byte(leb128Value)
} else {
out[1] = byte(leb128Value >> 8)
out[2] = byte(leb128Value)
}

copy(out[av1PayloaderHeadersize+leb128Size:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)

payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize

if len(payloads) > 1 {
out[0] ^= zMask
}
if payloadDataRemaining != 0 {
out[0] ^= yMask
}
}

return payloads
}

// AV1Packet represents a depacketized AV1 RTP Packet
//
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// |Z|Y| W |N|-|-|-|
// +-+-+-+-+-+-+-+-+
//
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
type AV1Packet struct {
// Z: MUST be set to 1 if the first OBU element is an
// OBU fragment that is a continuation of an OBU fragment
// from the previous packet, and MUST be set to 0 otherwise.
Z bool

// Y: MUST be set to 1 if the last OBU element is an OBU fragment
// that will continue in the next packet, and MUST be set to 0 otherwise.
Y bool

// 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. Instead, the length of the last OBU
// element contained in the packet can be calculated as follows:
// Length of the last OBU element =
// length of the RTP payload
// - length of aggregation header
// - length of previous OBU elements including length fields
W byte

// N: MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set to 0 otherwise.
N bool

// Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one.
// AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements
OBUElements [][]byte
}

// Unmarshal parses the passed byte slice and stores the result in the AV1Packet this method is called upon
func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) {
if payload == nil {
return nil, errNilPacket
} else if len(payload) < 2 {
return nil, errShortPacket
}

p.Z = ((payload[0] & zMask) >> zBitshift) != 0
p.Y = ((payload[0] & yMask) >> yBitshift) != 0
p.N = ((payload[0] & nMask) >> nBitshift) != 0
p.W = (payload[0] & wMask) >> wBitshift

if p.Z && p.N {
return nil, errIsKeyframeAndFragment
}

currentIndex := uint(1)
p.OBUElements = [][]byte{}

var (
obuElementLength, bytesRead uint
err error
)
for i := 1; ; i++ {
if currentIndex == uint(len(payload)) {
break
}

// If W bit is set the last OBU Element will have no length header
if byte(i) == p.W {
bytesRead = 0
obuElementLength = uint(len(payload)) - currentIndex
} else {
obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:])
if err != nil {
return nil, err
}
}

currentIndex += bytesRead
if uint(len(payload)) < currentIndex+obuElementLength {
return nil, errShortPacket
}
p.OBUElements = append(p.OBUElements, payload[currentIndex:currentIndex+obuElementLength])
currentIndex += obuElementLength
}

return payload[1:], nil
}
Loading

0 comments on commit 62d6782

Please sign in to comment.