Repository: pion/rtp
Pull request #172 (2022-04-07 03:50:40)

Start AV1 implementation
WIP



codecov[bot]: # [Codecov](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) Report
> Merging [#172](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (42c0905) into [master](https://codecov.io/gh/pion/rtp/commit/b08333e6c3a2db0ec7c388c81541d91d78a9ae70?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (b08333e) will **decrease** coverage by `3.08%`.
> The diff coverage is `0.00%`.

> :exclamation: Current head 42c0905 differs from pull request most recent head 501da28. Consider uploading reports for the commit 501da28 to get more accurate results

```diff
@@            Coverage Diff             @@
##           master     #172      +/-   ##
==========================================
- Coverage   86.31%   83.23%   -3.09%     
==========================================
  Files          15       16       +1     
  Lines        1637     1688      +51     
==========================================
- Hits         1413     1405       -8     
- Misses        195      252      +57     
- Partials       29       31       +2     
```

| Flag | Coverage Δ | |
|---|---|---|
| go | `?` | |
| wasm | `83.23% <0.00%> (-2.60%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) | Coverage Δ | |
|---|---|---|
| [codecs/av1\_packet.go](https://codecov.io/gh/pion/rtp/pull/172/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-Y29kZWNzL2F2MV9wYWNrZXQuZ28=) | `0.00% <0.00%> (ø)` | |
| [packet.go](https://codecov.io/gh/pion/rtp/pull/172/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGFja2V0Lmdv) | `85.58% <0.00%> (-2.36%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Last update [b08333e...501da28](https://codecov.io/gh/pion/rtp/pull/172?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).

Sean-Der: Here is an example of a IVFWriter that is when passed AV1 RTP packets saves to disk successfully!
Sean-Der: @enobufs I also need to think/design how the OBU Cache works and take sequence numbers into account. 

We should associate the cache with a sequence number. If we have gaps we should just evict the cache. It is a harder problem, but I am sure we can come up with a good design.
enobufs: > We should associate the cache with a sequence number.

@Sean-Der I think so too because packet loss could occur over the network and NACK may not be able restore them or applicable, but this is done outside the av1_packet.go.  We could update the IVFWriter example to tolerate the case of the discontinuity.

Patch:
diff --git a/codecs/av1_frame.go b/codecs/av1_frame.go
new file mode 100644
index 0000000..6f0db4c
--- /dev/null
+++ b/codecs/av1_frame.go
@@ -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
+}
diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go
new file mode 100644
index 0000000..7aa3a55
--- /dev/null
+++ b/codecs/av1_packet.go
@@ -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
+}
diff --git a/codecs/error.go b/codecs/error.go
index 38ee907..7f72e7b 100644
--- a/codecs/error.go
+++ b/codecs/error.go
@@ -8,4 +8,7 @@ var (
 	errTooManyPDiff         = errors.New("too many PDiff")
 	errTooManySpatialLayers = errors.New("too many spatial layers")
 	errUnhandledNALUType    = errors.New("NALU Type is unhandled")
+
+	// AV1 Errors
+	errIsKeyframeAndFragment = errors.New("bits Z and N are set. Not possible to have OBU be tail fragment and be keyframe")
 )
diff --git a/pkg/obu/leb128.go b/pkg/obu/leb128.go
new file mode 100644
index 0000000..988a8f4
--- /dev/null
+++ b/pkg/obu/leb128.go
@@ -0,0 +1,66 @@
+// Package obu implements tools for working with the "Open Bitstream Unit"
+package obu
+
+import "errors"
+
+const (
+	sevenLsbBitmask = uint(0b01111111)
+	msbBitmask      = uint(0b10000000)
+)
+
+// ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read
+var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished")
+
+// EncodeLEB128 encodes a uint as LEB128
+func EncodeLEB128(in uint) (out uint) {
+	for {
+		// Copy seven bits from in and discard
+		// what we have copied from in
+		out |= (in & sevenLsbBitmask)
+		in >>= 7
+
+		// If we have more bits to encode set MSB
+		// otherwise we are done
+		if in != 0 {
+			out |= msbBitmask
+			out <<= 8
+		} else {
+			return out
+		}
+	}
+}
+
+func decodeLEB128(in uint) (out uint) {
+	for {
+		// Take 7 LSB from in
+		out |= (in & sevenLsbBitmask)
+
+		// Discard the MSB
+		in >>= 8
+		if in == 0 {
+			return out
+		}
+
+		out <<= 7
+	}
+}
+
+// ReadLeb128 scans an buffer and decodes a Leb128 value.
+// If the end of the buffer is reached and all MSB are set
+// an error is returned
+func ReadLeb128(in []byte) (uint, uint, error) {
+	var encodedLength uint
+
+	for i := range in {
+		encodedLength |= uint(in[i])
+
+		if in[i]&byte(msbBitmask) == 0 {
+			return decodeLEB128(encodedLength), uint(i + 1), nil
+		}
+
+		// Make more room for next read
+		encodedLength <<= 8
+	}
+
+	return 0, 0, ErrFailedToReadLEB128
+}


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 0d01db5..95e97fd 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -64,7 +64,6 @@ jobs:
 
       - uses: codecov/codecov-action@v2
         with:
-          file: ./cover.out
           name: codecov-umbrella
           fail_ci_if_error: true
           flags: go
@@ -153,7 +152,6 @@ jobs:
 
       - uses: codecov/codecov-action@v2
         with:
-          file: ./cover.out
           name: codecov-umbrella
           fail_ci_if_error: true
           flags: wasm
diff --git a/codecs/av1_frame_test.go b/codecs/av1_frame_test.go
new file mode 100644
index 0000000..544ba7d
--- /dev/null
+++ b/codecs/av1_frame_test.go
@@ -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)
+			}
+		}
+	}
+}
diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go
new file mode 100644
index 0000000..27eeb47
--- /dev/null
+++ b/codecs/av1_packet_test.go
@@ -0,0 +1,247 @@
+package codecs
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/pion/rtp/v2/pkg/obu"
+)
+
+func TestAV1_Marshal(t *testing.T) {
+	const mtu = 5
+
+	for _, test := range []struct {
+		input  []byte
+		output [][]byte
+	}{
+		{[]byte{0x01}, [][]byte{{0x00, 0x01, 0x01}}},
+		{[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05}, [][]byte{{0x40, 0x02, 0x00, 0x01}, {0xc0, 0x02, 0x02, 0x03}, {0xc0, 0x02, 0x04, 0x04}, {0x80, 0x01, 0x05}}},
+	} {
+		test := test
+
+		p := &AV1Payloader{}
+		if payloads := p.Payload(mtu, test.input); !reflect.DeepEqual(payloads, test.output) {
+			t.Fatalf("Expected(%02x) did not equal actual(%02x)", test.output, payloads)
+		}
+	}
+
+	p := &AV1Payloader{}
+	zeroMtuPayload := p.Payload(0, []byte{0x0A, 0x0B, 0x0C})
+	if zeroMtuPayload != nil {
+		t.Fatal("Unexpected output from zero MTU AV1 Payloader")
+	}
+}
+
+func TestAV1_Unmarshal_Error(t *testing.T) {
+	for _, test := range []struct {
+		expectedError error
+		input         []byte
+	}{
+		{errNilPacket, nil},
+		{errShortPacket, []byte{0x00}},
+		{errIsKeyframeAndFragment, []byte{byte(0b10001000), 0x00}},
+		{obu.ErrFailedToReadLEB128, []byte{byte(0b10000000), 0xFF, 0xFF}},
+		{errShortPacket, []byte{byte(0b10000000), 0xFF, 0x0F, 0x00, 0x00}},
+	} {
+		test := test
+		av1Pkt := &AV1Packet{}
+
+		if _, err := av1Pkt.Unmarshal(test.input); !errors.Is(err, test.expectedError) {
+			t.Fatal(fmt.Sprintf("Expected error(%s) but got (%s)", test.expectedError, err))
+		}
+	}
+}
+
+func TestAV1_Unmarshal(t *testing.T) {
+	av1Payload := []byte{
+		0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c,
+		0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80,
+		0x30, 0x10, 0xc3, 0xc0, 0x07, 0xff, 0xff,
+		0xf8, 0xb7, 0x30, 0xc0, 0x00, 0x00, 0x88,
+		0x17, 0xf9, 0x0c, 0xcf, 0xc6, 0x7b, 0x9c,
+		0x0d, 0xda, 0x55, 0x82, 0x82, 0x67, 0x2f,
+		0xf0, 0x07, 0x26, 0x5d, 0xf6, 0xc6, 0xe3,
+		0x12, 0xdd, 0xf9, 0x71, 0x77, 0x43, 0xe6,
+		0xba, 0xf2, 0xce, 0x36, 0x08, 0x63, 0x92,
+		0xac, 0xbb, 0xbd, 0x26, 0x4c, 0x05, 0x52,
+		0x91, 0x09, 0xf5, 0x37, 0xb5, 0x18, 0xbe,
+		0x5c, 0x95, 0xb1, 0x2c, 0x13, 0x27, 0x81,
+		0xc2, 0x52, 0x8c, 0xaf, 0x27, 0xca, 0xf2,
+		0x93, 0xd6, 0x2e, 0x46, 0x32, 0xed, 0x71,
+		0x87, 0x90, 0x1d, 0x0b, 0x84, 0x46, 0x7f,
+		0xd1, 0x57, 0xc1, 0x0d, 0xc7, 0x5b, 0x41,
+		0xbb, 0x8a, 0x7d, 0xe9, 0x2c, 0xae, 0x36,
+		0x98, 0x13, 0x39, 0xb9, 0x0c, 0x66, 0x47,
+		0x05, 0xa2, 0xdf, 0x55, 0xc4, 0x09, 0xab,
+		0xe4, 0xfb, 0x11, 0x52, 0x36, 0x27, 0x88,
+		0x86, 0xf3, 0x4a, 0xbb, 0xef, 0x40, 0xa7,
+		0x85, 0x2a, 0xfe, 0x92, 0x28, 0xe4, 0xce,
+		0xce, 0xdc, 0x4b, 0xd0, 0xaa, 0x3c, 0xd5,
+		0x16, 0x76, 0x74, 0xe2, 0xfa, 0x34, 0x91,
+		0x4f, 0xdc, 0x2b, 0xea, 0xae, 0x71, 0x36,
+		0x74, 0xe1, 0x2a, 0xf3, 0xd3, 0x53, 0xe8,
+		0xec, 0xd6, 0x63, 0xf6, 0x6a, 0x75, 0x95,
+		0x68, 0xcc, 0x99, 0xbe, 0x17, 0xd8, 0x3b,
+		0x87, 0x5b, 0x94, 0xdc, 0xec, 0x32, 0x09,
+		0x18, 0x4b, 0x37, 0x58, 0xb5, 0x67, 0xfb,
+		0xdf, 0x66, 0x6c, 0x16, 0x9e, 0xba, 0x72,
+		0xc6, 0x21, 0xac, 0x02, 0x6d, 0x6b, 0x17,
+		0xf9, 0x68, 0x22, 0x2e, 0x10, 0xd7, 0xdf,
+		0xfb, 0x24, 0x69, 0x7c, 0xaf, 0x11, 0x64,
+		0x80, 0x7a, 0x9d, 0x09, 0xc4, 0x1f, 0xf1,
+		0xd7, 0x3c, 0x5a, 0xc2, 0x2c, 0x8e, 0xf5,
+		0xff, 0xee, 0xc2, 0x7c, 0xa1, 0xe4, 0xcb,
+		0x1c, 0x6d, 0xd8, 0x15, 0x0e, 0x40, 0x36,
+		0x85, 0xe7, 0x04, 0xbb, 0x64, 0xca, 0x6a,
+		0xd9, 0x21, 0x8e, 0x95, 0xa0, 0x83, 0x95,
+		0x10, 0x48, 0xfa, 0x00, 0x54, 0x90, 0xe9,
+		0x81, 0x86, 0xa0, 0x4a, 0x6e, 0xbe, 0x9b,
+		0xf0, 0x73, 0x0a, 0x17, 0xbb, 0x57, 0x81,
+		0x17, 0xaf, 0xd6, 0x70, 0x1f, 0xe8, 0x6d,
+		0x32, 0x59, 0x14, 0x39, 0xd8, 0x1d, 0xec,
+		0x59, 0xe4, 0x98, 0x4d, 0x44, 0xf3, 0x4f,
+		0x7b, 0x47, 0xd9, 0x92, 0x3b, 0xd9, 0x5c,
+		0x98, 0xd5, 0xf1, 0xc9, 0x8b, 0x9d, 0xb1,
+		0x65, 0xb3, 0xe1, 0x87, 0xa4, 0x6a, 0xcc,
+		0x42, 0x96, 0x66, 0xdb, 0x5f, 0xf9, 0xe1,
+		0xa1, 0x72, 0xb6, 0x05, 0x02, 0x1f, 0xa3,
+		0x14, 0x3e, 0xfe, 0x99, 0x7f, 0xeb, 0x42,
+		0xcf, 0x76, 0x09, 0x19, 0xd2, 0xd2, 0x99,
+		0x75, 0x1c, 0x67, 0xda, 0x4d, 0xf4, 0x87,
+		0xe5, 0x55, 0x8b, 0xed, 0x01, 0x82, 0xf6,
+		0xd6, 0x1c, 0x5c, 0x05, 0x96, 0x96, 0x79,
+		0xc1, 0x61, 0x87, 0x74, 0xcd, 0x29, 0x83,
+		0x27, 0xae, 0x47, 0x87, 0x36, 0x34, 0xab,
+		0xc4, 0x73, 0x76, 0x58, 0x1b, 0x4a, 0xec,
+		0x0e, 0x4c, 0x2f, 0xb1, 0x76, 0x08, 0x7f,
+		0xaf, 0xfa, 0x6d, 0x8c, 0xde, 0xe4, 0xae,
+		0x58, 0x87, 0xe7, 0xa0, 0x27, 0x05, 0x0d,
+		0xf5, 0xa7, 0xfb, 0x2a, 0x75, 0x33, 0xd9,
+		0x3b, 0x65, 0x60, 0xa4, 0x13, 0x27, 0xa5,
+		0xe5, 0x1b, 0x83, 0x78, 0x7a, 0xd7, 0xec,
+		0x0c, 0xed, 0x8b, 0xe6, 0x4e, 0x8f, 0xfe,
+		0x6b, 0x5d, 0xbb, 0xa8, 0xee, 0x38, 0x81,
+		0x6f, 0x09, 0x23, 0x08, 0x8f, 0x07, 0x21,
+		0x09, 0x39, 0xf0, 0xf8, 0x03, 0x17, 0x24,
+		0x2a, 0x22, 0x44, 0x84, 0xe1, 0x5c, 0xf3,
+		0x4f, 0x20, 0xdc, 0xc1, 0xe7, 0xeb, 0xbc,
+		0x0b, 0xfb, 0x7b, 0x20, 0x66, 0xa4, 0x27,
+		0xe2, 0x01, 0xb3, 0x5f, 0xb7, 0x47, 0xa1,
+		0x88, 0x4b, 0x8c, 0x47, 0xda, 0x36, 0x98,
+		0x60, 0xd7, 0x46, 0x92, 0x0b, 0x7e, 0x5b,
+		0x4e, 0x34, 0x50, 0x12, 0x67, 0x50, 0x8d,
+		0xe7, 0xc9, 0xe4, 0x96, 0xef, 0xae, 0x2b,
+		0xc7, 0xfa, 0x36, 0x29, 0x05, 0xf5, 0x92,
+		0xbd, 0x62, 0xb7, 0xbb, 0x90, 0x66, 0xe0,
+		0xad, 0x14, 0x3e, 0xe7, 0xb4, 0x24, 0xf3,
+		0x04, 0xcf, 0x22, 0x14, 0x86, 0xa4, 0xb8,
+		0xfb, 0x83, 0x56, 0xce, 0xaa, 0xb4, 0x87,
+		0x5a, 0x9e, 0xf2, 0x0b, 0xaf, 0xad, 0x40,
+		0xe1, 0xb5, 0x5c, 0x6b, 0xa7, 0xee, 0x9f,
+		0xbb, 0x1a, 0x68, 0x4d, 0xc3, 0xbf, 0x22,
+		0x4d, 0xbe, 0x58, 0x52, 0xc9, 0xcc, 0x0d,
+		0x88, 0x04, 0xf1, 0xf8, 0xd4, 0xfb, 0xd6,
+		0xad, 0xcf, 0x13, 0x84, 0xd6, 0x2f, 0x90,
+		0x0c, 0x5f, 0xb4, 0xe2, 0xd8, 0x29, 0x26,
+		0x8d, 0x7c, 0x6b, 0xab, 0x91, 0x91, 0x3c,
+		0x25, 0x39, 0x9c, 0x86, 0x08, 0x39, 0x54,
+		0x59, 0x0d, 0xa4, 0xa8, 0x31, 0x9f, 0xa3,
+		0xbc, 0xc2, 0xcb, 0xf9, 0x30, 0x49, 0xc3,
+		0x68, 0x0e, 0xfc, 0x2b, 0x9f, 0xce, 0x59,
+		0x02, 0xfa, 0xd4, 0x4e, 0x11, 0x49, 0x0d,
+		0x93, 0x0c, 0xae, 0x57, 0xd7, 0x74, 0xdd,
+		0x13, 0x1a, 0x15, 0x79, 0x10, 0xcc, 0x99,
+		0x32, 0x9b, 0x57, 0x6d, 0x53, 0x75, 0x1f,
+		0x6d, 0xbb, 0xe4, 0xbc, 0xa9, 0xd4, 0xdb,
+		0x06, 0xe7, 0x09, 0xb0, 0x6f, 0xca, 0xb3,
+		0xb1, 0xed, 0xc5, 0x0b, 0x8d, 0x8e, 0x70,
+		0xb0, 0xbf, 0x8b, 0xad, 0x2f, 0x29, 0x92,
+		0xdd, 0x5a, 0x19, 0x3d, 0xca, 0xca, 0xed,
+		0x05, 0x26, 0x25, 0xee, 0xee, 0xa9, 0xdd,
+		0xa0, 0xe3, 0x78, 0xe0, 0x56, 0x99, 0x2f,
+		0xa1, 0x3f, 0x07, 0x5e, 0x91, 0xfb, 0xc4,
+		0xb3, 0xac, 0xee, 0x07, 0xa4, 0x6a, 0xcb,
+		0x42, 0xae, 0xdf, 0x09, 0xe7, 0xd0, 0xbb,
+		0xc6, 0xd4, 0x38, 0x58, 0x7d, 0xb4, 0x45,
+		0x98, 0x38, 0x21, 0xc8, 0xc1, 0x3c, 0x81,
+		0x12, 0x7e, 0x37, 0x03, 0xa8, 0xcc, 0xf3,
+		0xf9, 0xd9, 0x9d, 0x8f, 0xc1, 0xa1, 0xcc,
+		0xc1, 0x1b, 0xe3, 0xa8, 0x93, 0x91, 0x2c,
+		0x0a, 0xe8, 0x1f, 0x28, 0x13, 0x44, 0x07,
+		0x68, 0x5a, 0x8f, 0x27, 0x41, 0x18, 0xc9,
+		0x31, 0xc4, 0xc1, 0x71, 0xe2, 0xf0, 0xc4,
+		0xf4, 0x1e, 0xac, 0x29, 0x49, 0x2f, 0xd0,
+		0xc0, 0x98, 0x13, 0xa6, 0xbc, 0x5e, 0x34,
+		0x28, 0xa7, 0x30, 0x13, 0x8d, 0xb4, 0xca,
+		0x91, 0x26, 0x6c, 0xda, 0x35, 0xb5, 0xf1,
+		0xbf, 0x3f, 0x35, 0x3b, 0x87, 0x37, 0x63,
+		0x40, 0x59, 0x73, 0x49, 0x06, 0x59, 0x04,
+		0xe0, 0x84, 0x16, 0x3a, 0xe8, 0xc4, 0x28,
+		0xd1, 0xf5, 0x11, 0x9c, 0x34, 0xf4, 0x5a,
+		0xc0, 0xf8, 0x67, 0x47, 0x1c, 0x90, 0x63,
+		0xbc, 0x06, 0x39, 0x2e, 0x8a, 0xa5, 0xa0,
+		0xf1, 0x6b, 0x41, 0xb1, 0x16, 0xbd, 0xb9,
+		0x50, 0x78, 0x72, 0x91, 0x8e, 0x8c, 0x99,
+		0x0f, 0x7d, 0x99, 0x7e, 0x77, 0x36, 0x85,
+		0x87, 0x1f, 0x2e, 0x47, 0x13, 0x55, 0xf8,
+		0x07, 0xba, 0x7b, 0x1c, 0xaa, 0xbf, 0x20,
+		0xd0, 0xfa, 0xc4, 0xe1, 0xd0, 0xb3, 0xe4,
+		0xf4, 0xf9, 0x57, 0x8d, 0x56, 0x19, 0x4a,
+		0xdc, 0x4c, 0x83, 0xc8, 0xf1, 0x30, 0xc0,
+		0xb5, 0xdf, 0x67, 0x25, 0x58, 0xd8, 0x09,
+		0x41, 0x37, 0x2e, 0x0b, 0x47, 0x2b, 0x86,
+		0x4b, 0x73, 0x38, 0xf0, 0xa0, 0x6b, 0x83,
+		0x30, 0x80, 0x3e, 0x46, 0xb5, 0x09, 0xc8,
+		0x6d, 0x3e, 0x97, 0xaa, 0x70, 0x4e, 0x8c,
+		0x75, 0x29, 0xec, 0x8a, 0x37, 0x4a, 0x81,
+		0xfd, 0x92, 0xf1, 0x29, 0xf0, 0xe8, 0x9d,
+		0x8c, 0xb4, 0x39, 0x2d, 0x67, 0x06, 0xcd,
+		0x5f, 0x25, 0x02, 0x30, 0xbb, 0x6b, 0x41,
+		0x93, 0x55, 0x1e, 0x0c, 0xc9, 0x6e, 0xb5,
+		0xd5, 0x9f, 0x80, 0xf4, 0x7d, 0x9d, 0x8a,
+		0x0d, 0x8d, 0x3b, 0x15, 0x14, 0xc9, 0xdf,
+		0x03, 0x9c, 0x78, 0x39, 0x4e, 0xa0, 0xdc,
+		0x3a, 0x1b, 0x8c, 0xdf, 0xaa, 0xed, 0x25,
+		0xda, 0x60, 0xdd, 0x30, 0x64, 0x09, 0xcc,
+		0x94, 0x53, 0xa1, 0xad, 0xfd, 0x9e, 0xe7,
+		0x65, 0x15, 0xb8, 0xb1, 0xda, 0x9a, 0x28,
+		0x80, 0x51, 0x88, 0x93, 0x92, 0xe3, 0x03,
+		0xdf, 0x70, 0xba, 0x1b, 0x59, 0x3b, 0xb4,
+		0x8a, 0xb6, 0x0b, 0x0a, 0xa8, 0x48, 0xdf,
+		0xcc, 0x74, 0x4c, 0x71, 0x80, 0x08, 0xec,
+		0xc8, 0x8a, 0x73, 0xf5, 0x0e, 0x3d, 0xec,
+		0x16, 0xf6, 0x32, 0xfd, 0xf3, 0x6b, 0xba,
+		0xa9, 0x65, 0xd1, 0x87, 0xe2, 0x56, 0xcd,
+		0xde, 0x2c, 0xa4, 0x1b, 0x25, 0x81, 0xb2,
+		0xed, 0xea, 0xe9, 0x11, 0x07, 0xf5, 0x17,
+		0xd0, 0xca, 0x5d, 0x07, 0xb9, 0xb2, 0xa9,
+		0xa9, 0xee, 0x42, 0x33, 0x93, 0x21, 0x30,
+		0x5e, 0xd2, 0x58, 0xfd, 0xdd, 0x73, 0x0d,
+		0xb2, 0x93, 0x58, 0x77, 0x78, 0x40, 0x69,
+		0xba, 0x3c, 0x95, 0x1c, 0x61, 0xc6, 0xc6,
+		0x97, 0x1c, 0xef, 0x4d, 0x91, 0x0a, 0x42,
+		0x91, 0x1d, 0x14, 0x93, 0xf5, 0x78, 0x41,
+		0x32, 0x8a, 0x0a, 0x43, 0xd4, 0x3e, 0x6b,
+		0xb0, 0xd8, 0x0e, 0x04,
+	}
+
+	av1Pkt := &AV1Packet{}
+	if _, err := av1Pkt.Unmarshal(av1Payload); err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(av1Pkt, &AV1Packet{
+		Z: false,
+		Y: true,
+		W: 2,
+		N: true,
+		OBUElements: [][]byte{
+			av1Payload[2:14],
+			av1Payload[14:],
+		},
+	}) {
+		t.Fatal("AV1 Unmarshal didn't store the expected results in the packet")
+	}
+}
diff --git a/pkg/obu/leb128_test.go b/pkg/obu/leb128_test.go
new file mode 100644
index 0000000..42cf777
--- /dev/null
+++ b/pkg/obu/leb128_test.go
@@ -0,0 +1,39 @@
+package obu
+
+import (
+	"errors"
+	"testing"
+)
+
+func TestLEB128(t *testing.T) {
+	for _, test := range []struct {
+		Value   uint
+		Encoded uint
+	}{
+		{0, 0},
+		{5, 5},
+		{999999, 0xBF843D},
+	} {
+		test := test
+
+		encoded := EncodeLEB128(test.Value)
+		if encoded != test.Encoded {
+			t.Fatalf("Actual(%d) did not equal expected(%d)", encoded, test.Encoded)
+		}
+
+		decoded := decodeLEB128(encoded)
+		if decoded != test.Value {
+			t.Fatalf("Actual(%d) did not equal expected(%d)", decoded, test.Value)
+		}
+	}
+}
+
+func TestReadLeb128(t *testing.T) {
+	if _, _, err := ReadLeb128(nil); !errors.Is(err, ErrFailedToReadLEB128) {
+		t.Fatal("ReadLeb128 on a nil buffer should return an error")
+	}
+
+	if _, _, err := ReadLeb128([]byte{0xFF}); !errors.Is(err, ErrFailedToReadLEB128) {
+		t.Fatal("ReadLeb128 on a buffer with all MSB set should fail")
+	}
+}