From 6cf5e9bfef24e0eaac09cc12c42b196393fb2c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Bach=C3=A9?= Date: Fri, 25 Jun 2021 10:44:59 +0200 Subject: [PATCH] Implement a H265/HEVC packet decoder Relates to #87 --- codecs/h265_packet.go | 803 +++++++++++++++++++++++++++++++++++ codecs/h265_packet_test.go | 844 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1647 insertions(+) create mode 100644 codecs/h265_packet.go create mode 100644 codecs/h265_packet_test.go diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go new file mode 100644 index 0000000..a4ae843 --- /dev/null +++ b/codecs/h265_packet.go @@ -0,0 +1,803 @@ +package codecs + +import ( + "errors" + "fmt" +) + +// +// Errors +// + +var ( + errH265CorruptedPacket = errors.New("corrupted h265 packet") + errInvalidH265PacketType = errors.New("invalid h265 packet type") +) + +// +// Network Abstraction Unit Header implementation +// + +const ( + // sizeof(uint16) + h265NaluHeaderSize = 2 + // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 + h265NaluAggregationPacketType = 48 + // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 + h265NaluFragmentationUnitType = 49 + // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 + h265NaluPACIPacketType = 50 +) + +// H265NALUHeader is a H265 NAL Unit Header +// https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 +// +---------------+---------------+ +// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |F| Type | LayerID | TID | +// +-------------+-----------------+ +type H265NALUHeader uint16 + +func newH265NALUHeader(highByte, lowByte uint8) H265NALUHeader { + return H265NALUHeader((uint16(highByte) << 8) | uint16(lowByte)) +} + +// F is the forbidden bit, should always be 0. +func (h H265NALUHeader) F() bool { + return (uint16(h) >> 15) != 0 +} + +// Type of NAL Unit. +func (h H265NALUHeader) Type() uint8 { + // 01111110 00000000 + const mask = 0b01111110 << 8 + return uint8((uint16(h) & mask) >> (8 + 1)) +} + +// IsTypeVCLUnit returns whether or not the NAL Unit type is a VCL NAL unit. +func (h H265NALUHeader) IsTypeVCLUnit() bool { + // Type is coded on 6 bits + const msbMask = 0b00100000 + return (h.Type() & msbMask) == 0 +} + +// LayerID should always be 0 in non-3D HEVC context. +func (h H265NALUHeader) LayerID() uint8 { + // 00000001 11111000 + const mask = (0b00000001 << 8) | 0b11111000 + return uint8((uint16(h) & mask) >> 3) +} + +// TID is the temporal identifier of the NAL unit +1. +func (h H265NALUHeader) TID() uint8 { + const mask = 0b00000111 + return uint8(uint16(h) & mask) +} + +// IsAggregationPacket returns whether or not the packet is an Aggregation packet. +func (h H265NALUHeader) IsAggregationPacket() bool { + return h.Type() == h265NaluAggregationPacketType +} + +// IsFragmentationUnit returns whether or not the packet is a Fragmentation Unit packet. +func (h H265NALUHeader) IsFragmentationUnit() bool { + return h.Type() == h265NaluFragmentationUnitType +} + +// IsPACIPacket returns whether or not the packet is a PACI packet. +func (h H265NALUHeader) IsPACIPacket() bool { + return h.Type() == h265NaluPACIPacketType +} + +// +// Single NAL Unit Packet implementation +// + +// H265SingleNALUnitPacket represents a NALU packet, containing exactly one NAL unit. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PayloadHdr | DONL (conditional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// | NAL unit payload data | +// | | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | :...OPTIONAL RTP padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.1 +type H265SingleNALUnitPacket struct { + // payloadHeader is the header of the H265 packet. + payloadHeader H265NALUHeader + // donl is a 16-bit field, that may or may not be present. + donl *uint16 + // payload of the fragmentation unit. + payload []byte + + mightNeedDONL bool +} + +// WithDONL can be called to specify whether or not DONL might be parsed. +// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. +func (p *H265SingleNALUnitPacket) WithDONL(value bool) { + p.mightNeedDONL = value +} + +// Unmarshal parses the passed byte slice and stores the result in the H265SingleNALUnitPacket this method is called upon. +func (p *H265SingleNALUnitPacket) Unmarshal(payload []byte) ([]byte, error) { + // sizeof(headers) + const totalHeaderSize = h265NaluHeaderSize + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= totalHeaderSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) + } + + payloadHeader := newH265NALUHeader(payload[0], payload[1]) + if payloadHeader.F() { + return nil, errH265CorruptedPacket + } + if payloadHeader.IsFragmentationUnit() || payloadHeader.IsPACIPacket() || payloadHeader.IsAggregationPacket() { + return nil, errInvalidH265PacketType + } + + payload = payload[2:] + + if p.mightNeedDONL { + // sizeof(uint16) + if len(payload) <= 2 { + return nil, errShortPacket + } + + donl := (uint16(payload[0]) << 8) | uint16(payload[1]) + p.donl = &donl + payload = payload[2:] + } + + p.payloadHeader = payloadHeader + p.payload = payload + + return nil, nil +} + +// PayloadHeader returns the NALU header of the packet. +func (p *H265SingleNALUnitPacket) PayloadHeader() H265NALUHeader { + return p.payloadHeader +} + +// DONL returns the DONL of the packet. +func (p *H265SingleNALUnitPacket) DONL() *uint16 { + return p.donl +} + +// Payload returns the Fragmentation Unit packet payload. +func (p *H265SingleNALUnitPacket) Payload() []byte { + return p.payload +} + +func (p *H265SingleNALUnitPacket) isH265Packet() {} + +// +// Aggregation Packets implementation +// + +// H265AggregationUnitFirst represent the First Aggregation Unit in an AP. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : DONL (conditional) | NALU size | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NALU size | | +// +-+-+-+-+-+-+-+-+ NAL unit | +// | | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 +type H265AggregationUnitFirst struct { + donl *uint16 + nalUnitSize uint16 + nalUnit []byte +} + +// DONL field, when present, specifies the value of the 16 least +// significant bits of the decoding order number of the aggregated NAL +// unit. +func (u H265AggregationUnitFirst) DONL() *uint16 { + return u.donl +} + +// NALUSize represents the size, in bytes, of the NalUnit. +func (u H265AggregationUnitFirst) NALUSize() uint16 { + return u.nalUnitSize +} + +// NalUnit payload. +func (u H265AggregationUnitFirst) NalUnit() []byte { + return u.nalUnit +} + +// H265AggregationUnit represent the an Aggregation Unit in an AP, which is not the first one. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : DOND (cond) | NALU size | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// | NAL unit | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 +type H265AggregationUnit struct { + dond *uint8 + nalUnitSize uint16 + nalUnit []byte +} + +// DOND field plus 1 specifies the difference between +// the decoding order number values of the current aggregated NAL unit +// and the preceding aggregated NAL unit in the same AP. +func (u H265AggregationUnit) DOND() *uint8 { + return u.dond +} + +// NALUSize represents the size, in bytes, of the NalUnit. +func (u H265AggregationUnit) NALUSize() uint16 { + return u.nalUnitSize +} + +// NalUnit payload. +func (u H265AggregationUnit) NalUnit() []byte { + return u.nalUnit +} + +// H265AggregationPacket represents an Aggregation packet. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PayloadHdr (Type=48) | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +// | | +// | two or more aggregation units | +// | | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | :...OPTIONAL RTP padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 +type H265AggregationPacket struct { + firstUnit *H265AggregationUnitFirst + otherUnits []H265AggregationUnit + + mightNeedDONL bool +} + +// WithDONL can be called to specify whether or not DONL might be parsed. +// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. +func (p *H265AggregationPacket) WithDONL(value bool) { + p.mightNeedDONL = value +} + +// Unmarshal parses the passed byte slice and stores the result in the H265AggregationPacket this method is called upon. +func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { + // sizeof(headers) + const totalHeaderSize = h265NaluHeaderSize + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= totalHeaderSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) + } + + payloadHeader := newH265NALUHeader(payload[0], payload[1]) + if payloadHeader.F() { + return nil, errH265CorruptedPacket + } + if !payloadHeader.IsAggregationPacket() { + return nil, errInvalidH265PacketType + } + + // First parse the first aggregation unit + payload = payload[2:] + firstUnit := &H265AggregationUnitFirst{} + + if p.mightNeedDONL { + if len(payload) < 2 { + return nil, errShortPacket + } + + donl := (uint16(payload[0]) << 8) | uint16(payload[1]) + firstUnit.donl = &donl + + payload = payload[2:] + } + if len(payload) < 2 { + return nil, errShortPacket + } + firstUnit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) + payload = payload[2:] + + if len(payload) < int(firstUnit.nalUnitSize) { + return nil, errShortPacket + } + + firstUnit.nalUnit = payload[:firstUnit.nalUnitSize] + payload = payload[firstUnit.nalUnitSize:] + + // Parse remaining Aggregation Units + var units []H265AggregationUnit + for { + unit := H265AggregationUnit{} + + if p.mightNeedDONL { + if len(payload) < 1 { + break + } + + dond := payload[0] + unit.dond = &dond + + payload = payload[1:] + } + + if len(payload) < 2 { + break + } + unit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) + payload = payload[2:] + + if len(payload) < int(unit.nalUnitSize) { + break + } + + unit.nalUnit = payload[:unit.nalUnitSize] + payload = payload[unit.nalUnitSize:] + + units = append(units, unit) + } + + // There need to be **at least** two Aggregation Units (first + another one) + if len(units) == 0 { + return nil, errShortPacket + } + + p.firstUnit = firstUnit + p.otherUnits = units + + return nil, nil +} + +// FirstUnit returns the first Aggregated Unit of the packet. +func (p *H265AggregationPacket) FirstUnit() *H265AggregationUnitFirst { + return p.firstUnit +} + +// OtherUnits returns the all the other Aggregated Unit of the packet (excluding the first one). +func (p *H265AggregationPacket) OtherUnits() []H265AggregationUnit { + return p.otherUnits +} + +func (p *H265AggregationPacket) isH265Packet() {} + +// +// Fragmentation Unit implementation +// + +const ( + // sizeof(uint8) + h265FragmentationUnitHeaderSize = 1 +) + +// H265FragmentationUnitHeader is a H265 FU Header +// +---------------+ +// |0|1|2|3|4|5|6|7| +// +-+-+-+-+-+-+-+-+ +// |S|E| FuType | +// +---------------+ +type H265FragmentationUnitHeader uint8 + +// S represents the start of a fragmented NAL unit. +func (h H265FragmentationUnitHeader) S() bool { + const mask = 0b10000000 + return ((h & mask) >> 7) != 0 +} + +// E represents the end of a fragmented NAL unit. +func (h H265FragmentationUnitHeader) E() bool { + const mask = 0b01000000 + return ((h & mask) >> 6) != 0 +} + +// FuType MUST be equal to the field Type of the fragmented NAL unit. +func (h H265FragmentationUnitHeader) FuType() uint8 { + const mask = 0b00111111 + return uint8(h) & mask +} + +// H265FragmentationUnitPacket represents a single Fragmentation Unit packet. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PayloadHdr (Type=49) | FU header | DONL (cond) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +// | DONL (cond) | | +// |-+-+-+-+-+-+-+-+ | +// | FU payload | +// | | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | :...OPTIONAL RTP padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 +type H265FragmentationUnitPacket struct { + // payloadHeader is the header of the H265 packet. + payloadHeader H265NALUHeader + // fuHeader is the header of the fragmentation unit + fuHeader H265FragmentationUnitHeader + // donl is a 16-bit field, that may or may not be present. + donl *uint16 + // payload of the fragmentation unit. + payload []byte + + mightNeedDONL bool +} + +// WithDONL can be called to specify whether or not DONL might be parsed. +// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. +func (p *H265FragmentationUnitPacket) WithDONL(value bool) { + p.mightNeedDONL = value +} + +// Unmarshal parses the passed byte slice and stores the result in the H265FragmentationUnitPacket this method is called upon. +func (p *H265FragmentationUnitPacket) Unmarshal(payload []byte) ([]byte, error) { + // sizeof(headers) + const totalHeaderSize = h265NaluHeaderSize + h265FragmentationUnitHeaderSize + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= totalHeaderSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) + } + + payloadHeader := newH265NALUHeader(payload[0], payload[1]) + if payloadHeader.F() { + return nil, errH265CorruptedPacket + } + if !payloadHeader.IsFragmentationUnit() { + return nil, errInvalidH265PacketType + } + + fuHeader := H265FragmentationUnitHeader(payload[2]) + payload = payload[3:] + + if fuHeader.S() && p.mightNeedDONL { + // sizeof(uint16) + if len(payload) <= 2 { + return nil, errShortPacket + } + + donl := (uint16(payload[0]) << 8) | uint16(payload[1]) + p.donl = &donl + payload = payload[2:] + } + + p.payloadHeader = payloadHeader + p.fuHeader = fuHeader + p.payload = payload + + return nil, nil +} + +// PayloadHeader returns the NALU header of the packet. +func (p *H265FragmentationUnitPacket) PayloadHeader() H265NALUHeader { + return p.payloadHeader +} + +// FuHeader returns the Fragmentation Unit Header of the packet. +func (p *H265FragmentationUnitPacket) FuHeader() H265FragmentationUnitHeader { + return p.fuHeader +} + +// DONL returns the DONL of the packet. +func (p *H265FragmentationUnitPacket) DONL() *uint16 { + return p.donl +} + +// Payload returns the Fragmentation Unit packet payload. +func (p *H265FragmentationUnitPacket) Payload() []byte { + return p.payload +} + +func (p *H265FragmentationUnitPacket) isH265Packet() {} + +// +// PACI implementation +// + +// H265PACIPacket represents a single H265 PACI packet. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PayloadHdr (Type=50) |A| cType | PHSsize |F0..2|Y| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Payload Header Extension Structure (PHES) | +// |=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=| +// | | +// | PACI payload: NAL unit | +// | . . . | +// | | +// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | :...OPTIONAL RTP padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 +type H265PACIPacket struct { + // payloadHeader is the header of the H265 packet. + payloadHeader H265NALUHeader + + // Field which holds value for `A`, `cType`, `PHSsize`, `F0`, `F1`, `F2` and `Y` fields. + paciHeaderFields uint16 + + // phes is a header extension, of byte length `PHSsize` + phes []byte + + // Payload contains NAL units & optional padding + payload []byte +} + +// PayloadHeader returns the NAL Unit Header. +func (p *H265PACIPacket) PayloadHeader() H265NALUHeader { + return p.payloadHeader +} + +// A copies the F bit of the PACI payload NALU. +func (p *H265PACIPacket) A() bool { + const mask = 0b10000000 << 8 + return (p.paciHeaderFields & mask) != 0 +} + +// CType copies the Type field of the PACI payload NALU. +func (p *H265PACIPacket) CType() uint8 { + const mask = 0b01111110 << 8 + return uint8((p.paciHeaderFields & mask) >> (8 + 1)) +} + +// PHSsize indicates the size of the PHES field. +func (p *H265PACIPacket) PHSsize() uint8 { + const mask = (0b00000001 << 8) | 0b11110000 + return uint8((p.paciHeaderFields & mask) >> 4) +} + +// F0 indicates the presence of a Temporal Scalability support extension in the PHES. +func (p *H265PACIPacket) F0() bool { + const mask = 0b00001000 + return (p.paciHeaderFields & mask) != 0 +} + +// F1 must be zero, reserved for future extensions. +func (p *H265PACIPacket) F1() bool { + const mask = 0b00000100 + return (p.paciHeaderFields & mask) != 0 +} + +// F2 must be zero, reserved for future extensions. +func (p *H265PACIPacket) F2() bool { + const mask = 0b00000010 + return (p.paciHeaderFields & mask) != 0 +} + +// Y must be zero, reserved for future extensions. +func (p *H265PACIPacket) Y() bool { + const mask = 0b00000001 + return (p.paciHeaderFields & mask) != 0 +} + +// PHES contains header extensions. Its size is indicated by PHSsize. +func (p *H265PACIPacket) PHES() []byte { + return p.phes +} + +// Payload is a single NALU or NALU-like struct, not including the first two octets (header). +func (p *H265PACIPacket) Payload() []byte { + return p.payload +} + +// TSCI returns the Temporal Scalability Control Information extension, if present. +func (p *H265PACIPacket) TSCI() *H265TSCI { + if !p.F0() || p.PHSsize() < 3 { + return nil + } + + tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[0])) + return &tsci +} + +// Unmarshal parses the passed byte slice and stores the result in the H265PACIPacket this method is called upon. +func (p *H265PACIPacket) Unmarshal(payload []byte) ([]byte, error) { + // sizeof(headers) + const totalHeaderSize = h265NaluHeaderSize + 2 + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= totalHeaderSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) + } + + payloadHeader := newH265NALUHeader(payload[0], payload[1]) + if payloadHeader.F() { + return nil, errH265CorruptedPacket + } + if !payloadHeader.IsPACIPacket() { + return nil, errInvalidH265PacketType + } + + paciHeaderFields := (uint16(payload[2]) << 8) | uint16(payload[3]) + payload = payload[4:] + + p.paciHeaderFields = paciHeaderFields + headerExtensionSize := p.PHSsize() + + if len(payload) < int(headerExtensionSize)+1 { + p.paciHeaderFields = 0 + return nil, errShortPacket + } + + p.payloadHeader = payloadHeader + + if headerExtensionSize > 0 { + p.phes = payload[:headerExtensionSize] + } + + payload = payload[headerExtensionSize:] + p.payload = payload + + return nil, nil +} + +func (p *H265PACIPacket) isH265Packet() {} + +// +// Temporal Scalability Control Information +// + +// H265TSCI is a Temporal Scalability Control Information header extension. +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.5 +type H265TSCI uint32 + +// TL0PICIDX see RFC7798 for more details. +func (h H265TSCI) TL0PICIDX() uint8 { + const m1 = 0xFFFF0000 + const m2 = 0xFF00 + return uint8((((h & m1) >> 16) & m2) >> 8) +} + +// IrapPicID see RFC7798 for more details. +func (h H265TSCI) IrapPicID() uint8 { + const m1 = 0xFFFF0000 + const m2 = 0x00FF + return uint8(((h & m1) >> 16) & m2) +} + +// S see RFC7798 for more details. +func (h H265TSCI) S() bool { + const m1 = 0xFF00 + const m2 = 0b10000000 + return (uint8((h&m1)>>8) & m2) != 0 +} + +// E see RFC7798 for more details. +func (h H265TSCI) E() bool { + const m1 = 0xFF00 + const m2 = 0b01000000 + return (uint8((h&m1)>>8) & m2) != 0 +} + +// RES see RFC7798 for more details. +func (h H265TSCI) RES() uint8 { + const m1 = 0xFF00 + const m2 = 0b00111111 + return uint8((h&m1)>>8) & m2 +} + +// +// H265 Packet interface +// + +type isH265Packet interface { + isH265Packet() +} + +var ( + _ isH265Packet = (*H265FragmentationUnitPacket)(nil) + _ isH265Packet = (*H265PACIPacket)(nil) + _ isH265Packet = (*H265SingleNALUnitPacket)(nil) + _ isH265Packet = (*H265AggregationPacket)(nil) +) + +// +// Packet implementation +// + +// H265Packet represents a H265 packet, stored in the payload of an RTP packet. +type H265Packet struct { + packet isH265Packet + mightNeedDONL bool +} + +// WithDONL can be called to specify whether or not DONL might be parsed. +// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. +func (p *H265Packet) WithDONL(value bool) { + p.mightNeedDONL = value +} + +// Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon +func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= h265NaluHeaderSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), h265NaluHeaderSize) + } + + payloadHeader := newH265NALUHeader(payload[0], payload[1]) + if payloadHeader.F() { + return nil, errH265CorruptedPacket + } + + switch { + case payloadHeader.IsPACIPacket(): + decoded := &H265PACIPacket{} + if _, err := decoded.Unmarshal(payload); err != nil { + return nil, err + } + + p.packet = decoded + + case payloadHeader.IsFragmentationUnit(): + decoded := &H265FragmentationUnitPacket{} + decoded.WithDONL(p.mightNeedDONL) + + if _, err := decoded.Unmarshal(payload); err != nil { + return nil, err + } + + p.packet = decoded + + case payloadHeader.IsAggregationPacket(): + decoded := &H265AggregationPacket{} + decoded.WithDONL(p.mightNeedDONL) + + if _, err := decoded.Unmarshal(payload); err != nil { + return nil, err + } + + p.packet = decoded + + default: + decoded := &H265SingleNALUnitPacket{} + decoded.WithDONL(p.mightNeedDONL) + + if _, err := decoded.Unmarshal(payload); err != nil { + return nil, err + } + + p.packet = decoded + } + + return nil, nil +} + +// Packet returns the populated packet. +// Must be casted to one of: +// - *H265SingleNALUnitPacket +// - *H265FragmentationUnitPacket +// - *H265AggregationPacket +// - *H265PACIPacket +// nolint:golint +func (p *H265Packet) Packet() isH265Packet { + return p.packet +} diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go new file mode 100644 index 0000000..7c25a9c --- /dev/null +++ b/codecs/h265_packet_test.go @@ -0,0 +1,844 @@ +package codecs + +import ( + "reflect" + "testing" +) + +func TestH265_NALU_Header(t *testing.T) { + tt := [...]struct { + RawHeader []byte + + FBit bool + Type uint8 + LayerID uint8 + TID uint8 + + IsAP bool + IsFU bool + IsPACI bool + }{ + // FBit + { + RawHeader: []byte{0x80, 0x00}, + Type: 0, + LayerID: 0, + TID: 0, + FBit: true, + }, + // VPS_NUT + { + RawHeader: []byte{0x40, 0x01}, + Type: 32, + LayerID: 0, + TID: 1, + }, + // SPS_NUT + { + RawHeader: []byte{0x42, 0x01}, + Type: 33, + LayerID: 0, + TID: 1, + }, + // PPS_NUT + { + RawHeader: []byte{0x44, 0x01}, + Type: 34, + LayerID: 0, + TID: 1, + }, + // PREFIX_SEI_NUT + { + RawHeader: []byte{0x4e, 0x01}, + Type: 39, + LayerID: 0, + TID: 1, + }, + // Fragmentation Unit + { + RawHeader: []byte{0x62, 0x01}, + Type: h265NaluFragmentationUnitType, + LayerID: 0, + TID: 1, + IsFU: true, + }, + } + + for _, cur := range tt { + header := newH265NALUHeader(cur.RawHeader[0], cur.RawHeader[1]) + + if header.F() != cur.FBit { + t.Fatal("invalid F bit") + } + + if header.Type() != cur.Type { + t.Fatal("invalid Type") + } + + // For any type < 32, NAL is a VLC NAL unit. + if header.IsTypeVCLUnit() != (header.Type() < 32) { + t.Fatal("invalid IsTypeVCLUnit") + } + + if header.IsAggregationPacket() != cur.IsAP { + t.Fatal("invalid Type (aggregation packet)") + } + + if header.IsFragmentationUnit() != cur.IsFU { + t.Fatal("invalid Type (fragmentation unit)") + } + + if header.IsPACIPacket() != cur.IsPACI { + t.Fatal("invalid Type (PACI)") + } + + if header.LayerID() != cur.LayerID { + t.Fatal("invalid LayerID") + } + + if header.TID() != cur.TID { + t.Fatal("invalid TID") + } + } +} + +func TestH265_FU_Header(t *testing.T) { + tt := [...]struct { + header H265FragmentationUnitHeader + + S bool + E bool + Type uint8 + }{ + // Start | IDR_W_RADL + { + header: H265FragmentationUnitHeader(0x93), + S: true, + E: false, + Type: 19, + }, + // Continuation | IDR_W_RADL + { + header: H265FragmentationUnitHeader(0x13), + S: false, + E: false, + Type: 19, + }, + // End | IDR_W_RADL + { + header: H265FragmentationUnitHeader(0x53), + S: false, + E: true, + Type: 19, + }, + // Start | TRAIL_R + { + header: H265FragmentationUnitHeader(0x81), + S: true, + E: false, + Type: 1, + }, + // Continuation | TRAIL_R + { + header: H265FragmentationUnitHeader(0x01), + S: false, + E: false, + Type: 1, + }, + // End | TRAIL_R + { + header: H265FragmentationUnitHeader(0x41), + S: false, + E: true, + Type: 1, + }, + } + + for _, cur := range tt { + if cur.header.S() != cur.S { + t.Fatal("invalid S field") + } + + if cur.header.E() != cur.E { + t.Fatal("invalid E field") + } + + if cur.header.FuType() != cur.Type { + t.Fatal("invalid FuType field") + } + } +} + +func TestH265_SingleNALUnitPacket(t *testing.T) { + tt := [...]struct { + Raw []byte + WithDONL bool + ExpectedPacket *H265SingleNALUnitPacket + ExpectedErr error + }{ + { + Raw: nil, + ExpectedErr: errNilPacket, + }, + { + Raw: []byte{}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93}, + ExpectedErr: errShortPacket, + }, + // FBit enabled in H265NALUHeader + { + Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errH265CorruptedPacket, + }, + // Type '49' in H265NALUHeader + { + Raw: []byte{0x62, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + // Type '50' in H265NALUHeader + { + Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + { + Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, + ExpectedPacket: &H265SingleNALUnitPacket{ + payloadHeader: newH265NALUHeader(0x01, 0x01), + payload: []byte{0xab, 0xcd, 0xef}, + }, + }, + // DONL, payload too small + { + Raw: []byte{0x01, 0x01, 0x93, 0xaf}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + { + Raw: []byte{0x01, 0x01, 0xaa, 0xbb, 0xcc}, + ExpectedPacket: &H265SingleNALUnitPacket{ + payloadHeader: newH265NALUHeader(0x01, 0x01), + donl: uint16ptr((uint16(0xaa) << 8) | uint16(0xbb)), + payload: []byte{0xcc}, + }, + WithDONL: true, + }, + } + + for _, cur := range tt { + parsed := &H265SingleNALUnitPacket{} + if cur.WithDONL { + parsed.WithDONL(cur.WithDONL) + } + + // Just for code coverage sake + parsed.isH265Packet() + + _, err := parsed.Unmarshal(cur.Raw) + + if cur.ExpectedErr != nil && err == nil { + t.Fatal("should error") + } else if cur.ExpectedErr == nil && err != nil { + t.Fatal("should not error") + } + + if cur.ExpectedPacket == nil { + continue + } + + if cur.ExpectedPacket.PayloadHeader() != parsed.PayloadHeader() { + t.Fatal("invalid payload header") + } + + if cur.ExpectedPacket.DONL() != nil && (*parsed.DONL() != *cur.ExpectedPacket.DONL()) { + t.Fatal("invalid DONL") + } + + if !reflect.DeepEqual(cur.ExpectedPacket.Payload(), parsed.Payload()) { + t.Fatal("invalid payload") + } + } +} + +func TestH265_AggregationPacket(t *testing.T) { + tt := [...]struct { + Raw []byte + WithDONL bool + ExpectedPacket *H265AggregationPacket + ExpectedErr error + }{ + { + Raw: nil, + ExpectedErr: errNilPacket, + }, + { + Raw: []byte{}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93}, + ExpectedErr: errShortPacket, + }, + // FBit enabled in H265NALUHeader + { + Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errH265CorruptedPacket, + }, + // Type '48' in H265NALUHeader + { + Raw: []byte{0xE0, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + // Small payload + { + Raw: []byte{0x60, 0x01, 0x00, 0x1}, + ExpectedErr: errShortPacket, + }, + // Small payload + { + Raw: []byte{0x60, 0x01, 0x00}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Small payload + { + Raw: []byte{0x60, 0x01, 0x00, 0x1}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Small payload + { + Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x02}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Single Aggregation Unit + { + Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Incomplete second Aggregation Unit + { + Raw: []byte{ + 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + // DONL + 0x00, + }, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Incomplete second Aggregation Unit + { + Raw: []byte{ + 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + // DONL, NAL Unit size (2 bytes) + 0x00, 0x55, 0x55, + }, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Valid Second Aggregation Unit + { + Raw: []byte{ + 0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, + // DONL, NAL Unit size (2 bytes), Payload + 0x77, 0x00, 0x01, 0xaa, + }, + WithDONL: true, + ExpectedPacket: &H265AggregationPacket{ + firstUnit: &H265AggregationUnitFirst{ + donl: uint16ptr(0xccdd), + nalUnitSize: 2, + nalUnit: []byte{0xff, 0xee}, + }, + otherUnits: []H265AggregationUnit{ + { + dond: uint8ptr(0x77), + nalUnitSize: 1, + nalUnit: []byte{0xaa}, + }, + }, + }, + }, + } + + for _, cur := range tt { + parsed := &H265AggregationPacket{} + if cur.WithDONL { + parsed.WithDONL(cur.WithDONL) + } + + // Just for code coverage sake + parsed.isH265Packet() + + _, err := parsed.Unmarshal(cur.Raw) + + if cur.ExpectedErr != nil && err == nil { + t.Fatal("should error") + } else if cur.ExpectedErr == nil && err != nil { + t.Fatal("should not error") + } + + if cur.ExpectedPacket == nil { + continue + } + + if cur.ExpectedPacket.FirstUnit() != nil { + if parsed.FirstUnit().NALUSize() != cur.ExpectedPacket.FirstUnit().NALUSize() { + t.Fatal("invalid first unit NALUSize") + } + + if cur.ExpectedPacket.FirstUnit().DONL() != nil && *cur.ExpectedPacket.FirstUnit().DONL() != *parsed.FirstUnit().DONL() { + t.Fatal("invalid first unit DONL") + } + + if !reflect.DeepEqual(cur.ExpectedPacket.FirstUnit().NalUnit(), parsed.FirstUnit().NalUnit()) { + t.Fatal("invalid first unit NalUnit") + } + } + + if len(cur.ExpectedPacket.OtherUnits()) != len(parsed.OtherUnits()) { + t.Fatal("number of other units mismatch") + } + + for ndx, unit := range cur.ExpectedPacket.OtherUnits() { + if parsed.OtherUnits()[ndx].NALUSize() != unit.NALUSize() { + t.Fatal("invalid unit NALUSize") + } + + if unit.DOND() != nil && *unit.DOND() != *parsed.OtherUnits()[ndx].DOND() { + t.Fatal("invalid unit DOND") + } + + if !reflect.DeepEqual(unit.NalUnit(), parsed.OtherUnits()[ndx].NalUnit()) { + t.Fatal("invalid first unit NalUnit") + } + } + + if !reflect.DeepEqual(cur.ExpectedPacket.OtherUnits(), parsed.OtherUnits()) { + t.Fatal("invalid payload") + } + } +} + +func TestH265_FragmentationUnitPacket(t *testing.T) { + tt := [...]struct { + Raw []byte + WithDONL bool + ExpectedFU *H265FragmentationUnitPacket + ExpectedErr error + }{ + { + Raw: nil, + ExpectedErr: errNilPacket, + }, + { + Raw: []byte{}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93}, + ExpectedErr: errShortPacket, + }, + // FBit enabled in H265NALUHeader + { + Raw: []byte{0x80, 0x01, 0x93, 0xaf}, + ExpectedErr: errH265CorruptedPacket, + }, + // Type not '49' in H265NALUHeader + { + Raw: []byte{0x40, 0x01, 0x93, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + { + Raw: []byte{0x62, 0x01, 0x93, 0xaf}, + ExpectedFU: &H265FragmentationUnitPacket{ + payloadHeader: newH265NALUHeader(0x62, 0x01), + fuHeader: H265FragmentationUnitHeader(0x93), + donl: nil, + payload: []byte{0xaf}, + }, + }, + { + Raw: []byte{0x62, 0x01, 0x93, 0xcc}, + WithDONL: true, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, + WithDONL: true, + ExpectedFU: &H265FragmentationUnitPacket{ + payloadHeader: newH265NALUHeader(0x62, 0x01), + fuHeader: H265FragmentationUnitHeader(0x93), + donl: uint16ptr((uint16(0xcc) << 8) | uint16(0xdd)), + payload: []byte{0xaf, 0x0d, 0x5a}, + }, + }, + } + + for _, cur := range tt { + parsed := &H265FragmentationUnitPacket{} + if cur.WithDONL { + parsed.WithDONL(cur.WithDONL) + } + + // Just for code coverage sake + parsed.isH265Packet() + + _, err := parsed.Unmarshal(cur.Raw) + + if cur.ExpectedErr != nil && err == nil { + t.Fatal("should error") + } else if cur.ExpectedErr == nil && err != nil { + t.Fatal("should not error") + } + + if cur.ExpectedFU == nil { + continue + } + + if parsed.PayloadHeader() != cur.ExpectedFU.PayloadHeader() { + t.Fatal("invalid payload header") + } + + if parsed.FuHeader() != cur.ExpectedFU.FuHeader() { + t.Fatal("invalid FU header") + } + + if cur.ExpectedFU.DONL() != nil && (*parsed.DONL() != *cur.ExpectedFU.DONL()) { + t.Fatal("invalid DONL") + } + + if !reflect.DeepEqual(parsed.Payload(), cur.ExpectedFU.Payload()) { + t.Fatal("invalid Payload") + } + } +} + +func TestH265_TemporalScalabilityControlInformation(t *testing.T) { + tt := [...]struct { + Value H265TSCI + ExpectedTL0PICIDX uint8 + ExpectedIrapPicID uint8 + ExpectedS bool + ExpectedE bool + ExpectedRES uint8 + }{ + {}, + { + Value: H265TSCI((uint32(0xCA) << 24) | (uint32(0xFE) << 16)), + ExpectedTL0PICIDX: 0xCA, + ExpectedIrapPicID: 0xFE, + }, + { + Value: H265TSCI(uint32(1) << 15), + ExpectedS: true, + }, + { + Value: H265TSCI(uint32(1) << 14), + ExpectedE: true, + }, + { + Value: H265TSCI(uint32(0x0A) << 8), + ExpectedRES: 0x0A, + }, + // Sets RES, and force sets S and E to 0. + { + Value: H265TSCI((uint32(0xAA) << 8) & ^(uint32(1) << 15) & ^(uint32(1) << 14)), + ExpectedRES: 0xAA & 0b00111111, + }, + } + + for _, cur := range tt { + if cur.Value.TL0PICIDX() != cur.ExpectedTL0PICIDX { + t.Fatal("invalid TL0PICIDX") + } + + if cur.Value.IrapPicID() != cur.ExpectedIrapPicID { + t.Fatal("invalid IrapPicID") + } + + if cur.Value.S() != cur.ExpectedS { + t.Fatal("invalid S") + } + + if cur.Value.E() != cur.ExpectedE { + t.Fatal("invalid E") + } + + if cur.Value.RES() != cur.ExpectedRES { + t.Fatal("invalid RES") + } + } +} + +func TestH265_PACI_Packet(t *testing.T) { + tt := [...]struct { + Raw []byte + ExpectedFU *H265PACIPacket + ExpectedErr error + }{ + { + Raw: nil, + ExpectedErr: errNilPacket, + }, + { + Raw: []byte{}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93}, + ExpectedErr: errShortPacket, + }, + // FBit enabled in H265NALUHeader + { + Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errH265CorruptedPacket, + }, + // Type not '50' in H265NALUHeader + { + Raw: []byte{0x40, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + // Invalid header extension size + { + Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errInvalidH265PacketType, + }, + // No Header Extension + { + Raw: []byte{0x64, 0x01, 0x64, 0x00, 0xab, 0xcd, 0xef}, + ExpectedFU: &H265PACIPacket{ + payloadHeader: newH265NALUHeader(0x64, 0x01), + paciHeaderFields: (uint16(0x64) << 8) | uint16(0x00), + phes: nil, + payload: []byte{0xab, 0xcd, 0xef}, + }, + }, + // Header Extension 1 byte + { + Raw: []byte{0x64, 0x01, 0x64, 0x10, 0xff, 0xab, 0xcd, 0xef}, + ExpectedFU: &H265PACIPacket{ + payloadHeader: newH265NALUHeader(0x64, 0x01), + paciHeaderFields: (uint16(0x64) << 8) | uint16(0x10), + phes: []byte{0xff}, + payload: []byte{0xab, 0xcd, 0xef}, + }, + }, + // Header Extension TSCI + { + Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, + ExpectedFU: &H265PACIPacket{ + payloadHeader: newH265NALUHeader(0x64, 0x01), + paciHeaderFields: (uint16(0x64) << 8) | uint16(0b00111000), + phes: []byte{0xaa, 0xbb, 0x80}, + payload: []byte{0xab, 0xcd, 0xef}, + }, + }, + } + + for _, cur := range tt { + parsed := &H265PACIPacket{} + _, err := parsed.Unmarshal(cur.Raw) + + // Just for code coverage sake + parsed.isH265Packet() + + if cur.ExpectedErr != nil && err == nil { + t.Fatal("should error") + } else if cur.ExpectedErr == nil && err != nil { + t.Fatal("should not error") + } + + if cur.ExpectedFU == nil { + continue + } + + if cur.ExpectedFU.PayloadHeader() != parsed.PayloadHeader() { + t.Fatal("invalid PayloadHeader") + } + + if cur.ExpectedFU.A() != parsed.A() { + t.Fatal("invalid A") + } + + if cur.ExpectedFU.CType() != parsed.CType() { + t.Fatal("invalid CType") + } + + if cur.ExpectedFU.PHSsize() != parsed.PHSsize() { + t.Fatal("invalid PHSsize") + } + + if cur.ExpectedFU.F0() != parsed.F0() { + t.Fatal("invalid F0") + } + + if cur.ExpectedFU.F1() != parsed.F1() { + t.Fatal("invalid F1") + } + + if cur.ExpectedFU.F2() != parsed.F2() { + t.Fatal("invalid F2") + } + + if cur.ExpectedFU.Y() != parsed.Y() { + t.Fatal("invalid Y") + } + + if !reflect.DeepEqual(cur.ExpectedFU.PHES(), parsed.PHES()) { + t.Fatal("invalid PHES") + } + + if !reflect.DeepEqual(cur.ExpectedFU.Payload(), parsed.Payload()) { + t.Fatal("invalid Payload") + } + + if cur.ExpectedFU.TSCI() != nil && (*cur.ExpectedFU.TSCI() != *parsed.TSCI()) { + t.Fatal("invalid TSCI") + } + } +} + +func TestH265_Packet(t *testing.T) { + tt := [...]struct { + Raw []byte + WithDONL bool + ExpectedPacketType reflect.Type + ExpectedErr error + }{ + { + Raw: nil, + ExpectedErr: errNilPacket, + }, + { + Raw: []byte{}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x62, 0x01, 0x93}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x64, 0x01, 0x93, 0xaf}, + ExpectedErr: errShortPacket, + }, + { + Raw: []byte{0x01, 0x01}, + WithDONL: true, + ExpectedErr: errShortPacket, + }, + // FBit enabled in H265NALUHeader + { + Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, + ExpectedErr: errH265CorruptedPacket, + }, + // Valid H265SingleNALUnitPacket + { + Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, + ExpectedPacketType: reflect.TypeOf((*H265SingleNALUnitPacket)(nil)), + }, + // Invalid H265SingleNALUnitPacket + { + Raw: []byte{0x01, 0x01, 0x93, 0xaf}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + // Valid H265PACIPacket + { + Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, + ExpectedPacketType: reflect.TypeOf((*H265PACIPacket)(nil)), + }, + // Valid H265FragmentationUnitPacket + { + Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, + ExpectedPacketType: reflect.TypeOf((*H265FragmentationUnitPacket)(nil)), + WithDONL: true, + }, + // Valid H265AggregationPacket + { + Raw: []byte{0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 0x77, 0x00, 0x01, 0xaa}, + ExpectedPacketType: reflect.TypeOf((*H265AggregationPacket)(nil)), + WithDONL: true, + }, + // Invalid H265AggregationPacket + { + Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, + ExpectedErr: errShortPacket, + WithDONL: true, + }, + } + + for _, cur := range tt { + pck := &H265Packet{} + if cur.WithDONL { + pck.WithDONL(true) + } + + _, err := pck.Unmarshal(cur.Raw) + + if cur.ExpectedErr != nil && err == nil { + t.Fatal("should error") + } else if cur.ExpectedErr == nil && err != nil { + t.Fatal("should not error") + } + + if cur.ExpectedErr != nil { + continue + } + + if reflect.TypeOf(pck.Packet()) != cur.ExpectedPacketType { + t.Fatal("invalid packet type") + } + } +} + +func TestH265_Packet_Real(t *testing.T) { + // Tests decoding of real H265 payloads extracted from a Wireshark dump. + + tt := [...]string{ + "\x40\x01\x0c\x01\xff\xff\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xac\x09", + "\x42\x01\x01\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xa0\x03\xc0\x80\x10\xe5\x8d\xae\x49\x32\xf4\xdc\x04\x04\x04\x02", + "\x44\x01\xc0\xf2\xf0\x3c\x90", + "\x4e\x01\xe5\x04\x61\x0c\x00\x00\x80", + "\x62\x01\x93\xaf\x0d\x5a\xfe\x67\x77\x29\xc0\x74\xf3\x57\x4c\x16\x94\xaa\x7c\x2a\x64\x5f\xe9\xa5\xb7\x2a\xa3\x95\x9d\x94\xa7\xb4\xd3\xc4\x4a\xb1\xb7\x69\xca\xbe\x75\xc5\x64\xa8\x97\x4b\x8a\xbf\x7e\xf0\x0f\xc3\x22\x60\x67\xab\xae\x96\xd6\x99\xca\x7a\x8d\x35\x93\x1a\x67\x60\xe7\xbe\x7e\x13\x95\x3c\xe0\x11\xc1\xc1\xa7\x48\xef\xf7\x7b\xb0\xeb\x35\x49\x81\x4e\x4e\x54\xf7\x31\x6a\x38\xa1\xa7\x0c\xd6\xbe\x3b\x25\xba\x08\x19\x0b\x49\xfd\x90\xbb\x73\x7a\x45\x8c\xb9\x73\x43\x04\xc5\x5f\xda\x0f\xd5\x70\x4c\x11\xee\x72\xb8\x6a\xb4\x95\x62\x64\xb6\x23\x14\x7e\xdb\x0e\xa5\x0f\x86\x31\xe4\xd1\x64\x56\x43\xf6\xb7\xe7\x1b\x93\x4a\xeb\xd0\xa6\xe3\x1f\xce\xda\x15\x67\x05\xb6\x77\x36\x8b\x27\x5b\xc6\xf2\x95\xb8\x2b\xcc\x9b\x0a\x03\x05\xbe\xc3\xd3\x85\xf5\x69\xb6\x19\x1f\x63\x2d\x8b\x65\x9e\xc3\x9d\xd2\x44\xb3\x7c\x86\x3b\xea\xa8\x5d\x02\xe5\x40\x03\x20\x76\x48\xff\xf6\x2b\x0d\x18\xd6\x4d\x49\x70\x1a\x5e\xb2\x89\xca\xec\x71\x41\x79\x4e\x94\x17\x0c\x57\x51\x55\x14\x61\x40\x46\x4b\x3e\x17\xb2\xc8\xbd\x1c\x06\x13\x91\x72\xf8\xc8\xfc\x6f\xb0\x30\x9a\xec\x3b\xa6\xc9\x33\x0b\xa5\xe5\xf4\x65\x7a\x29\x8b\x76\x62\x81\x12\xaf\x20\x4c\xd9\x21\x23\x9e\xeb\xc9\x0e\x5b\x29\x35\x7f\x41\xcd\xce\xa1\xc4\xbe\x01\x30\xb9\x11\xc3\xb1\xe4\xce\x45\xd2\x5c\xb3\x1e\x69\x78\xba\xb1\x72\xe4\x88\x54\xd8\x5d\xd0\xa8\x3a\x74\xad\xe5\xc7\xc1\x59\x7c\x78\x15\x26\x37\x3d\x50\xae\xb3\xa4\x5b\x6c\x7d\x65\x66\x85\x4d\x16\x9a\x67\x74\xad\x55\x32\x3a\x84\x85\x0b\x6a\xeb\x24\x97\xb4\x20\x4d\xca\x41\x61\x7a\xd1\x7b\x60\xdb\x7f\xd5\x61\x22\xcf\xd1\x7e\x4c\xf3\x85\xfd\x13\x63\xe4\x9d\xed\xac\x13\x0a\xa0\x92\xb7\x34\xde\x65\x0f\xd9\x0f\x9b\xac\xe2\x47\xe8\x5c\xb3\x11\x8e\xc6\x08\x19\xd0\xb0\x85\x52\xc8\x5c\x1b\x08\x0a\xce\xc9\x6b\xa7\xef\x95\x2f\xd0\xb8\x63\xe5\x4c\xd4\xed\x6e\x87\xe9\xd4\x0a\xe6\x11\x44\x63\x00\x94\x18\xe9\x28\xba\xcf\x92\x43\x06\x59\xdd\x37\x4f\xd3\xef\x9d\x31\x5e\x9b\x48\xf9\x1f\x3e\x7b\x95\x3a\xbd\x1f\x71\x55\x0c\x06\xf9\x86\xf8\x3d\x39\x16\x50\xb3\x21\x11\x19\x6f\x70\xa9\x48\xe8\xbb\x0a\x11\x23\xf8\xab\xfe\x44\xe0\xbb\xe8\x64\xfa\x85\xe4\x02\x55\x88\x41\xc6\x30\x7f\x10\xad\x75\x02\x4b\xef\xe1\x0b\x06\x3c\x10\x49\x83\xf9\xd1\x3e\x3e\x67\x86\x4c\xf8\x9d\xde\x5a\xc4\xc8\xcf\xb6\xf4\xb0\xd3\x34\x58\xd4\x7b\x4d\xd3\x37\x63\xb2\x48\x8a\x7e\x20\x00\xde\xb4\x42\x8f\xda\xe9\x43\x9e\x0c\x16\xce\x79\xac\x2c\x70\xc1\x89\x05\x36\x62\x6e\xd9\xbc\xfb\x63\xc6\x79\x89\x3c\x90\x89\x2b\xd1\x8c\xe0\xc2\x54\xc7\xd6\xb4\xe8\x9e\x96\x55\x6e\x7b\xd5\x7f\xac\xd4\xa7\x1c\xa0\xdf\x01\x30\xad\xc0\x9f\x69\x06\x10\x43\x7f\xf4\x5d\x62\xa3\xea\x73\xf2\x14\x79\x19\x13\xea\x59\x14\x79\xa8\xe7\xce\xce\x44\x25\x13\x41\x18\x57\xdd\xce\xe4\xbe\xcc\x20\x80\x29\x71\x73\xa7\x7c\x86\x39\x76\xf4\xa7\x1c\x63\x24\x21\x93\x1e\xb5\x9a\x5c\x8a\x9e\xda\x8b\x9d\x88\x97\xfc\x98\x7d\x26\x74\x04\x1f\xa8\x10\x4f\x45\xcd\x46\xe8\x28\xe4\x8e\x59\x67\x63\x4a\xcf\x1e\xed\xdd\xbb\x79\x2f\x8d\x94\xab\xfc\xdb\xc5\x79\x1a\x4d\xcd\x53\x41\xdf\xd1\x7a\x8f\x46\x3e\x1f\x79\x88\xe3\xee\x9f\xc4\xc1\xe6\x2e\x89\x4d\x28\xc9\xca\x28\xc2\x0a\xc5\xc7\xf1\x22\xcd\xb3\x36\xfa\xe3\x7e\xa6\xcd\x95\x55\x5e\x0e\x1a\x75\x7f\x65\x27\xd3\x37\x4f\x23\xc5\xab\x49\x68\x4e\x02\xb5\xbf\xd7\x95\xc0\x78\x67\xbc\x1a\xe9\xae\x6f\x44\x58\x8a\xc2\xce\x42\x98\x4e\x77\xc7\x2a\xa0\xa7\x7d\xe4\x3b\xd1\x20\x82\x1a\xd3\xe2\xc7\x76\x5d\x06\x46\xb5\x24\xd7\xfb\x57\x63\x2b\x19\x51\x48\x65\x6d\xfb\xe0\x98\xd1\x14\x0e\x17\x64\x29\x34\x6f\x6e\x66\x9e\x8d\xc9\x89\x49\x69\xee\x74\xf3\x35\xe6\x8b\x67\x56\x95\x7f\x1b\xe9\xed\x8c\x0f\xe2\x19\x59\xbf\x03\x35\x55\x3c\x04\xbc\x40\x52\x90\x10\x08\xad\xa7\x65\xe0\x31\xcb\xcf\x3d\xd4\x62\x68\x01\x0d\xed\xf5\x28\x64\x2d\xaa\x7c\x99\x15\x8d\x70\x32\x53\xb8\x9d\x0a\x3c\xbf\x91\x02\x04\xd0\xee\x87\xce\x04\xcc\x3e\xa8\x20\xfd\x97\xdf\xbf\x4a\xbc\xfc\xc9\x7c\x77\x21\xcc\x23\x6f\x59\x38\xd8\xd9\xa0\x0e\xb1\x23\x4e\x04\x3f\x14\x9e\xcc\x05\x54\xab\x20\x69\xed\xa4\xd5\x1d\xb4\x1b\x52\xed\x6a\xea\xeb\x7f\xd1\xbc\xfd\x75\x20\xa0\x1c\x59\x8c\x5a\xa1\x2a\x70\x64\x11\xb1\x7b\xc1\x24\x80\x28\x51\x4c\x94\xa1\x95\x64\x72\xe8\x90\x67\x38\x74\x2b\xab\x38\x46\x12\x71\xce\x19\x98\x98\xf7\x89\xd4\xfe\x2f\x2a\xc5\x61\x20\xd0\xa4\x1a\x51\x3c\x82\xc8\x18\x31\x7a\x10\xe8\x1c\xc6\x95\x5a\xa0\x82\x88\xce\x8f\x4b\x47\x85\x7e\x89\x95\x95\x52\x1e\xac\xce\x45\x57\x61\x38\x97\x2b\x62\xa5\x14\x6f\xc3\xaa\x6c\x35\x83\xc9\xa3\x1e\x30\x89\xf4\xb1\xea\x4f\x39\xde\xde\xc7\x46\x5c\x0e\x85\x41\xec\x6a\xa4\xcb\xee\x70\x9c\x57\xd9\xf4\xa1\xc3\x9c\x2a\x0a\xf0\x5d\x58\xb0\xae\xd4\xdc\xc5\x6a\xa8\x34\xfa\x23\xef\xef\x08\x39\xc3\x3d\xea\x11\x6e\x6a\xe0\x1e\xd0\x52\xa8\xc3\x6e\xc9\x1c\xfc\xd0\x0c\x4c\xea\x0d\x82\xcb\xdd\x29\x1a\xc4\x4f\x6e\xa3\x4d\xcb\x7a\x38\x77\xe5\x15\x6e\xad\xfa\x9d\x2f\x02\xb6\x39\x84\x3a\x60\x8f\x71\x9f\x92\xe5\x24\x4f\xbd\x18\x49\xd5\xef\xbf\x70\xfb\xd1\x4c\x2e\xfc\x2f\x36\xf3\x00\x31\x2e\x90\x18\xcc\xf4\x71\xb9\xe4\xf9\xbe\xcb\x5e\xff\xf3\xe7\xf8\xca\x03\x60\x66\xb3\xc9\x5a\xf9\x74\x09\x02\x57\xb6\x90\x94\xfc\x41\x35\xdc\x35\x3f\x32\x7a\xa6\xa5\xcd\x8a\x8f\xc8\x3d\xc8\x81\xc3\xec\x37\x74\x86\x61\x41\x0d\xc5\xe2\xc8\x0c\x84\x2b\x3b\x71\x58\xde\x1b\xe3\x20\x65\x2e\x76\xf4\x98\xd8\xaa\x78\xe6\xeb\xb8\x85\x0d\xa0\xd0\xf5\x57\x64\x01\x58\x55\x82\xd5\x0f\x2d\x9c\x3e\x2a\xa0\x7e\xaf\x42\xf3\x37\xd1\xb3\xaf\xda\x5b\xa9\xda\xe3\x89\x5d\xf1\xca\xa5\x12\x3d\xe7\x91\x95\x53\x21\x72\xca\x7f\xf6\x79\x59\x21\xcf\x30\x18\xfb\x78\x55\x40\x59\xc3\xf9\xf1\xdd\x58\x44\x5e\x83\x11\x5c\x2d\x1d\x91\xf6\x01\x3d\x3f\xd4\x33\x81\x66\x6c\x40\x7a\x9d\x70\x10\x58\xe6\x53\xad\x85\x11\x99\x3e\x4b\xbc\x31\xc6\x78\x9d\x79\xc5\xde\x9f\x2e\x43\xfa\x76\x84\x2f\xfd\x28\x75\x12\x48\x25\xfd\x15\x8c\x29\x6a\x91\xa4\x63\xc0\xa2\x8c\x41\x3c\xf1\xb0\xf8\xdf\x66\xeb\xbd\x14\x88\xa9\x81\xa7\x35\xc4\x41\x40\x6c\x10\x3f\x09\xbd\xb5\xd3\x7a\xee\x4b\xd5\x86\xff\x36\x03\x6b\x78\xde", + "\x62\x01\x53\x8a\xe9\x25\xe1\x06\x09\x8e\xba\x12\x74\x87\x09\x9a\x95\xe4\x86\x62\x2b\x4b\xf9\xa6\x2e\x7b\x35\x43\xf7\x39\x99\x0f\x3b\x6f\xfd\x1a\x6e\x23\x54\x70\xb5\x1d\x10\x1c\x63\x40\x96\x99\x41\xb6\x96\x0b\x70\x98\xec\x17\xb0\xaa\xdc\x4a\xab\xe8\x3b\xb7\x6b\x00\x1c\x5b\xc3\xe0\xa2\x8b\x7c\x17\xc8\x92\xc9\xb0\x92\xb6\x70\x84\x95\x30", + "\x4e\x01\xe5\x04\x35\xac\x00\x00\x80", + "\x62\x01\x41\xb0\x75\x5c\x27\x46\xef\x8a\xe7\x1d\x50\x38\xb2\x13\x33\xe0\x79\x35\x1b\xc2\xb5\x79\x73\xe7\xc2\x6f\xb9\x1a\x8c\x21\x0e\xa9\x54\x17\x6c\x41\xab\xc8\x16\x57\xec\x5e\xeb\x89\x3b\xa9\x90\x8c\xff\x4d\x46\x8b\xf0\xd9\xc0\xd0\x51\xcf\x8b\x88\xf1\x5f\x1e\x9e\xc1\xb9\x1f\xe3\x06\x45\x35\x8a\x47\xe8\x9a\xf2\x4f\x19\x4c\xf8\xce\x68\x1b\x63\x34\x11\x75\xea\xe5\xb1\x0f\x38\xcc\x05\x09\x8b\x3e\x2b\x88\x84\x9d\xc5\x03\xc3\xc0\x90\x32\xe2\x45\x69\xb1\xe5\xf7\x68\x6b\x16\x90\xa0\x40\xe6\x18\x74\xd8\x68\xf3\x34\x38\x99\xf2\x6c\xb7\x1a\x35\x21\xca\x52\x56\x4c\x7f\xb2\xa3\xd5\xb8\x40\x50\x48\x3e\xdc\xdf\x0b\xf5\x54\x5a\x15\x1a\xe2\xc3\xb4\x94\xda\x3f\xb5\x34\xa2\xca\xbc\x2f\xe0\xa4\xe5\x69\xf4\xbf\x62\x4d\x15\x21\x1b\x11\xfc\x39\xaa\x86\x74\x96\x63\xfd\x07\x53\x26\xf6\x34\x72\xeb\x14\x37\x98\x0d\xf4\x68\x91\x2c\x6b\x46\x83\x88\x82\x04\x8b\x9f\xb8\x32\x73\x75\x8b\xf9\xac\x71\x42\xd1\x2d\xb4\x28\x28\xf5\x78\xe0\x32\xf3\xe1\xfc\x43\x6b\xf9\x92\xf7\x48\xfe\x7f\xc0\x17\xbd\xfd\xba\x2f\x58\x6f\xee\x84\x03\x18\xce\xb0\x9d\x8d\xeb\x22\xf1\xfc\xb1\xcf\xff\x2f\xb2\x9f\x6c\xe5\xb4\x69\xdc\xdd\x20\x93\x00\x30\xad\x56\x04\x66\x7e\xa3\x3c\x18\x4b\x43\x66\x00\x27\x1e\x1c\x09\x11\xd8\xf4\x8a\x9e\xc5\x6a\x94\xe5\xae\x0b\x8a\xbe\x84\xda\xe5\x44\x7f\x38\x1c\xe7\xbb\x03\x19\x66\xe1\x5d\x1d\xc1\xbd\x3d\xc6\xb7\xe3\xff\x7f\x8e\xff\x1e\xf6\x9e\x6f\x58\x27\x74\x65\xef\x02\x5d\xa4\xde\x27\x7f\x51\xe3\x4b\x9e\x3f\x79\x83\xbd\x1b\x8f\x0d\x77\xfb\xbc\xc5\x9f\x15\xa7\x4e\x05\x8a\x24\x97\x66\xb2\x7c\xf6\xe1\x84\x54\xdb\x39\x5e\xf6\x1b\x8f\x05\x73\x1d\xb6\x8e\xd7\x09\x9a\xc5\x92\x80", + } + + for _, cur := range tt { + pck := &H265Packet{} + _, err := pck.Unmarshal([]byte(cur)) + if err != nil { + t.Fatal("invalid packet type") + } + } +} + +func uint8ptr(v uint8) *uint8 { + return &v +} + +func uint16ptr(v uint16) *uint16 { + return &v +}