diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 361bcba..31982f8 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -17,7 +17,11 @@ import ( var ( errH265CorruptedPacket = errors.New("corrupted h265 packet") errInvalidH265PacketType = errors.New("invalid h265 packet type") + errMissingDonl = errors.New("expecting all aggregated packets to have DONL values") + errDonlOutOfOrder = errors.New("expecting aggregation packets to have increasing DONL values") + errDondTooLarge = errors.New("expecint DONL difference between packets to be no more than 256") errExpectFragmentationStartUnit = errors.New("expecting a fragmentation start unit") + errH265PACIPHESTooLong = errors.New("expecting a PHES field shorter than 32 bytes") ) // @@ -27,23 +31,27 @@ var ( const ( // sizeof(uint16). h265NaluHeaderSize = 2 + // sizeof(uint16). + h265NaluDonlSize = 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 + h265NaluPACIPacketType = 50 + h265AggregatedPacketMaxSize = ^uint16(0) + h265AggregatedPacketLengthSize = 2 ) // 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 | -* +-------------+-----------------+ -**/ +// +// +---------------+---------------+ +// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |F| Type | LayerID | TID | +// +-------------+-----------------+ +// // . type H265NALUHeader uint16 @@ -107,71 +115,171 @@ func (h H265NALUHeader) IsPACIPacket() bool { // // 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 | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ +// +// 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 { +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 +// PayloadHeader returns the NALU header of the packet. +func (p *h265SingleNALUnitPacket) PayloadHeader() H265NALUHeader { + return p.payloadHeader } -// 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 +// DONL returns the DONL of the packet. +func (p *h265SingleNALUnitPacket) DONL() *uint16 { + return p.donl } -// 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 { +// Payload returns the Fragmentation Unit packet payload. +func (p *h265SingleNALUnitPacket) Payload() []byte { + return p.payload +} + +func (p *h265SingleNALUnitPacket) wireSize() int { + size := h265NaluHeaderSize + if p.donl != nil { + size += h265NaluDonlSize + } + size += len(p.payload) + + return size +} + +func parseH265SingleNalUnitPacket(buf []byte, withDONL bool) (*h265SingleNALUnitPacket, error) { + if buf == 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]) + minSize := h265NaluHeaderSize + + if withDONL { + minSize += h265NaluDonlSize + } + + if len(buf) <= minSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(buf), minSize) + } + + payloadHeader := newH265NALUHeader(buf[0], buf[1]) + if payloadHeader.F() { return nil, errH265CorruptedPacket } + if payloadHeader.IsFragmentationUnit() || payloadHeader.IsPACIPacket() || payloadHeader.IsAggregationPacket() { return nil, errInvalidH265PacketType } - payload = payload[2:] + var donl *uint16 - if p.mightNeedDONL { - // sizeof(uint16) - if len(payload) <= 2 { - return nil, errShortPacket - } + buf = buf[2:] - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) - p.donl = &donl - payload = payload[2:] + if withDONL { + donlValue := binary.BigEndian.Uint16(buf[:2]) + donl = &donlValue + buf = buf[2:] } - p.payloadHeader = payloadHeader - p.payload = payload + packet := h265SingleNALUnitPacket{ + payloadHeader, + donl, + buf, + } + + return &packet, nil +} + +func (p *h265SingleNALUnitPacket) isH265Packet() {} + +func (p *h265SingleNALUnitPacket) header() H265NALUHeader { + return p.payloadHeader +} + +func (p *h265SingleNALUnitPacket) toAnnexB(buf []byte) []byte { + buf = append(buf, annexbNALUStartCode...) + + donl := p.donl + p.donl = nil + buf = p.serialize(buf) + p.donl = donl + + return buf +} + +func (p *h265SingleNALUnitPacket) serialize(buf []byte) []byte { + buf = binary.BigEndian.AppendUint16(buf, uint16(p.payloadHeader)) + + if p.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *p.donl) + } + + buf = append(buf, p.payload...) + + return buf +} + +// 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 +// +// Deprecated: replaced with a private type instead, will be removed in a future release. +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) { + parsed, err := parseH265SingleNalUnitPacket(payload, p.mightNeedDONL) + if err != nil { + return nil, err + } + p.payloadHeader = parsed.payloadHeader + p.donl = parsed.donl + p.payload = parsed.payload return nil, nil } @@ -191,36 +299,305 @@ func (p *H265SingleNALUnitPacket) Payload() []byte { return p.payload } -func (p *H265SingleNALUnitPacket) isH265Packet() {} +// +// Aggregation Packets implementation +// -func (p *H265SingleNALUnitPacket) doPackaging(buf []byte) []byte { - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) +// 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 { + payloadHeader H265NALUHeader + donl *uint16 + payload []byte +} + +// PayloadHeader returns the NALU header of the packet. +func (p *h265AggregationPacket) PayloadHeader() H265NALUHeader { + return p.payloadHeader +} + +// DONL returns the DONL of the packet. +func (p *h265AggregationPacket) DONL() *uint16 { + return p.donl +} + +// Payload returns the Fragmentation Unit packet payload. +func (p *h265AggregationPacket) Payload() []byte { + return p.payload +} + +func (p *h265AggregationPacket) isH265Packet() {} + +func (p *h265AggregationPacket) header() H265NALUHeader { + return p.payloadHeader +} + +func (p *h265AggregationPacket) serialize(buf []byte) []byte { + buf = binary.BigEndian.AppendUint16(buf, uint16(p.payloadHeader)) + + if p.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *p.donl) + } buf = append(buf, p.payload...) return buf } -// -// Aggregation Packets implementation -// +func parseH265AggregationPacket(buf []byte, withDONL bool) (*h265AggregationPacket, error) { + // header + 2 length fields + minSize := h265NaluHeaderSize + (h265AggregatedPacketLengthSize * 2) + payloadStart := h265NaluHeaderSize + + if withDONL { + payloadStart += h265NaluDonlSize + minSize += h265NaluDonlSize + } + + if len(buf) < minSize { + return nil, errShortPacket + } + + header := H265NALUHeader(binary.BigEndian.Uint16(buf[0:2])) + + if !header.IsAggregationPacket() { + return nil, errInvalidH265PacketType + } + + var donl *uint16 + + if withDONL { + donlValue := binary.BigEndian.Uint16(buf[2:4]) + donl = &donlValue + } + + payload := buf[payloadStart:] + + packet := h265AggregationPacket{ + header, + donl, + payload, + } + + return &packet, nil +} + +// returns whether this NALU can even fit inside an AP with another NALU. +func canAggregateH265(mtu uint16, packet *h265SingleNALUnitPacket) bool { + // must leave enough space for the AP header, optionally its DONL field, 2 length headers, a 2nd AU's header + // and a second packet's DOND field + return packet.wireSize()+(h265AggregatedPacketLengthSize*2)+h265NaluHeaderSize+1 <= int(mtu) +} + +// returns whether inserting a new packet will make this list of packets too big to aggregate within the MTU. +func shouldAggregateH265Now(mtu uint16, packets []h265SingleNALUnitPacket, newPacket h265SingleNALUnitPacket) bool { + if len(packets) < 1 { + return false + } + // AP header + each AU's size field + totalSize := h265NaluHeaderSize + ((len(packets) + 1) * h265AggregatedPacketLengthSize) + hasDonl := packets[0].donl != nil + // first AU's DONL field + if hasDonl { + totalSize += 2 + } + + if hasDonl && newPacket.donl == nil { + return true + } + + for _, p := range packets { + totalSize += p.wireSize() + // individual AUs have their DONL fields replaced with DOND (1 byte) + if hasDonl { + totalSize -= 1 + } + } + + totalSize += newPacket.wireSize() + if hasDonl { + totalSize -= 1 + } + + return totalSize > int(mtu) +} + +// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 +// nolint: cyclop // hot path +func newH265AggregationPacket(packets []h265SingleNALUnitPacket) (*h265AggregationPacket, error) { + if packets == nil { + return nil, errNilPacket + } + if len(packets) < 2 { + return nil, errNotEnoughPackets + } + + donlExpected := packets[0].donl != nil + var aggrDonl *uint16 + if donlExpected { + aggrDonlVal := *packets[0].donl + aggrDonl = &aggrDonlVal + } + + header := uint16(0) + header |= h265NaluAggregationPacketType << 9 + + firstPacket := packets[0] + if firstPacket.wireSize() > int(h265AggregatedPacketMaxSize) { + return nil, errPacketTooLarge + } + + fBit := firstPacket.payloadHeader.F() + layerID := firstPacket.payloadHeader.LayerID() + tid := firstPacket.payloadHeader.TID() + + payload := make([]byte, 0) + + lastDonl := packets[0].donl + for i, packet := range packets { + if donlExpected && packet.donl == nil { + return nil, errMissingDonl + } + if i > 0 && packet.donl != nil { + // the 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. + dond := int(*packet.donl) - int(*lastDonl) - 1 + if dond < 0 { + return nil, errDonlOutOfOrder + } + if dond > int(^uint8(0)) { + return nil, errDondTooLarge + } + payload = append(payload, uint8(dond)) + lastDonl = packet.donl + } + // following AUs' DONs are derived from the DOND field + packet.donl = nil + + if packet.wireSize() > int(h265AggregatedPacketMaxSize) { + return nil, errPacketTooLarge + } + + if packet.payloadHeader.F() { + fBit = true + } + pLayerID := packet.payloadHeader.LayerID() + if pLayerID < layerID { + layerID = pLayerID + } + pTid := packet.payloadHeader.TID() + if pTid < tid { + tid = pTid + } + + // nolint: gosec // Already checked for max size + payload = binary.BigEndian.AppendUint16(payload, uint16(packet.wireSize())) + + payload = packet.serialize(payload) + } + + header |= uint16(tid) + header |= uint16(layerID) << 3 + + if fBit { + header |= uint16(0b1) << 15 + } + + packet := h265AggregationPacket{ + H265NALUHeader(header), + aggrDonl, + payload, + } + + return &packet, nil +} + +func splitH265AggregationPacket(packet h265AggregationPacket) ([]h265SingleNALUnitPacket, error) { // nolint:cyclop + curDonl := packet.donl + packets := make([]h265SingleNALUnitPacket, 0) + payload := packet.payload + + i := 0 + for len(payload) > 0 { + minSize := h265AggregatedPacketLengthSize + + // DOND is present starting on 2nd AU + if curDonl != nil && i > 0 { + minSize += 1 + } + + if len(payload) < minSize { + return nil, errShortPacket + } + + var donl *uint16 + if curDonl != nil { + if i == 0 { + donl = curDonl + } else { + donlValue := *curDonl + uint16(payload[0]) + 1 + donl = &donlValue + curDonl = &donlValue + payload = payload[1:] + } + } + + curLen := binary.BigEndian.Uint16(payload[0:2]) + if len(payload[2:]) < int(curLen) { + return nil, errShortPacket + } + + parsed, err := parseH265SingleNalUnitPacket(payload[2:2+curLen], false) + if err != nil { + return nil, err + } + + if curDonl != nil { + parsed.donl = donl + } + packets = append(packets, *parsed) + payload = payload[2+curLen:] + + i++ + } + if len(packets) < 2 { + return nil, errNotEnoughPackets + } + + return packets, nil +} // 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 | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | : -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ +// +// 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 +// +// Deprecated: replaced with a private type instead, will be removed in a future release. type H265AggregationUnitFirst struct { donl *uint16 nalUnitSize uint16 @@ -245,19 +622,21 @@ func (u H265AggregationUnitFirst) NalUnit() []byte { } // 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 | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | : -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ +// +// 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 +// +// Deprecated: replaced with a private type instead, will be removed in a future release. type H265AggregationUnit struct { dond *uint8 nalUnitSize uint16 @@ -282,23 +661,26 @@ func (u H265AggregationUnit) NalUnit() []byte { } // 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 | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ +// +// 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 +// +// Deprecated: replaced with a private type instead, will be removed in a future release. type H265AggregationPacket struct { - firstUnit *H265AggregationUnitFirst - otherUnits []H265AggregationUnit + payloadHeader H265NALUHeader + firstUnit *H265AggregationUnitFirst + otherUnits []H265AggregationUnit mightNeedDONL bool } @@ -312,11 +694,16 @@ func (p *H265AggregationPacket) WithDONL(value bool) { // 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) { //nolint:cyclop // sizeof(headers) - const totalHeaderSize = h265NaluHeaderSize + minSize := h265NaluHeaderSize + (h265AggregatedPacketLengthSize * 2) + + if p.mightNeedDONL { + minSize += h265NaluDonlSize + } + if payload == nil { return nil, errNilPacket - } else if len(payload) <= totalHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) + } else if len(payload) <= minSize { + return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), minSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) @@ -326,6 +713,7 @@ func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //no if !payloadHeader.IsAggregationPacket() { return nil, errInvalidH265PacketType } + p.payloadHeader = payloadHeader // First parse the first aggregation unit payload = payload[2:] @@ -336,7 +724,7 @@ func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //no return nil, errShortPacket } - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) + donl := binary.BigEndian.Uint16(payload[0:2]) firstUnit.donl = &donl payload = payload[2:] @@ -344,7 +732,7 @@ func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //no if len(payload) < 2 { return nil, errShortPacket } - firstUnit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) + firstUnit.nalUnitSize = binary.BigEndian.Uint16(payload[0:2]) payload = payload[2:] if len(payload) < int(firstUnit.nalUnitSize) { @@ -373,11 +761,11 @@ func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //no if len(payload) < 2 { break } - unit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) + unit.nalUnitSize = binary.BigEndian.Uint16(payload[0:2]) payload = payload[2:] if len(payload) < int(unit.nalUnitSize) { - break + return nil, errShortPacket } unit.nalUnit = payload[:unit.nalUnitSize] @@ -387,7 +775,7 @@ func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //no } // There need to be **at least** two Aggregation Units (first + another one) - if len(units) == 0 { + if len(units) < 1 { return nil, errShortPacket } @@ -407,23 +795,6 @@ func (p *H265AggregationPacket) OtherUnits() []H265AggregationUnit { return p.otherUnits } -func (p *H265AggregationPacket) isH265Packet() {} - -func (p *H265AggregationPacket) doPackaging(buf []byte) []byte { - if p.firstUnit == nil { - return buf - } - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, p.firstUnit.nalUnit...) - - for _, unit := range p.otherUnits { - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, unit.nalUnit...) - } - - return buf -} - // // Fragmentation Unit implementation // @@ -435,14 +806,30 @@ const ( // H265FragmentationUnitHeader is a H265 FU Header. // -// +---------------+ -// |0|1|2|3|4|5|6|7| -// +-+-+-+-+-+-+-+-+ -// |S|E| FuType | -// +---------------+ +// +---------------+ +// |0|1|2|3|4|5|6|7| +// +-+-+-+-+-+-+-+-+ +// |S|E| FuType | +// +---------------+ +// // . type H265FragmentationUnitHeader uint8 +func newH265FragmentationUnitHeader( + payloadHeader H265NALUHeader, + s, e bool, //nolint:unparam +) H265FragmentationUnitHeader { + header := payloadHeader.Type() + if s { + header |= 0b1 << 7 + } + if e { + header |= 0b1 << 6 + } + + return H265FragmentationUnitHeader(header) +} + // S represents the start of a fragmented NAL unit. func (h H265FragmentationUnitHeader) S() bool { const mask = 0b10000000 @@ -464,77 +851,219 @@ func (h H265FragmentationUnitHeader) FuType() uint8 { 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. +type h265FragmentationPacket struct { 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 + fuHeader H265FragmentationUnitHeader + donl *uint16 + payload []byte } -// 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 +func (p *h265FragmentationPacket) isH265Packet() {} + +func (p *h265FragmentationPacket) header() H265NALUHeader { + return p.payloadHeader } -// 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) +func (p *h265FragmentationPacket) serialize(buf []byte) []byte { + buf = binary.BigEndian.AppendUint16(buf, uint16(p.payloadHeader)) + buf = append(buf, byte(p.fuHeader)) + + if p.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *p.donl) } - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket + buf = append(buf, p.payload...) + + return buf +} + +func parseH265FragmentationPacket(payload []byte, withDONL bool) (*h265FragmentationPacket, error) { + minSize := h265NaluHeaderSize + h265FragmentationUnitHeaderSize + payloadStart := h265NaluHeaderSize + h265FragmentationUnitHeaderSize + + if withDONL { + minSize += h265NaluDonlSize + payloadStart += h265NaluDonlSize } - if !payloadHeader.IsFragmentationUnit() { + + if len(payload) < minSize { + return nil, errShortPacket + } + + header := H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])) + + if !header.IsFragmentationUnit() { return nil, errInvalidH265PacketType } - fuHeader := H265FragmentationUnitHeader(payload[2]) - payload = payload[3:] + var donl *uint16 + if withDONL { + donlVal := binary.BigEndian.Uint16(payload[3:5]) + donl = &donlVal + } - if fuHeader.S() && p.mightNeedDONL { - // sizeof(uint16) - if len(payload) <= 2 { - return nil, errShortPacket + packet := h265FragmentationPacket{ + header, + H265FragmentationUnitHeader(payload[2]), + donl, + payload[payloadStart:], + } + + return &packet, nil +} + +// Replaces the original header's type with 49, while keeping other fields. +func newH265FragmentationPacketHeader(payloadHeader H265NALUHeader) H265NALUHeader { + typeMask := ^uint16(0b01111110_00000000) + + return H265NALUHeader((uint16(payloadHeader) & typeMask) | (h265NaluFragmentationUnitType << 9)) +} + +// Replaces the FU's payload header's type with the FU Header's type, while keeping other fields. +func rebuildH265FragmentationPacketHeader( + payloadHeader H265NALUHeader, + fuHeader H265FragmentationUnitHeader, +) H265NALUHeader { + typeMask := ^uint16(0b01111110_00000000) + origType := uint8(fuHeader) & 0b00111111 + + return H265NALUHeader((uint16(payloadHeader) & typeMask) | (uint16(origType) << 9)) +} + +// Splits a H265SingleNALUnitPacket into many FU packets. +// +// Errors if the packet would result in a single FU packet. +// +// The P bit is not set in any case. +func newH265FragmentationPackets(mtu uint16, packet *h265SingleNALUnitPacket) ([]h265FragmentationPacket, error) { + if packet == nil { + return nil, errNilPacket + } + + // size of Header, FU header and (optionally) the DONL + overheadSize := 3 + if packet.donl != nil { + overheadSize += 2 + } + + sliceSize := int(mtu) - overheadSize + + if len(packet.payload) <= sliceSize { + return nil, errShortPacket + } + + packets := make([]h265FragmentationPacket, 0) + header := newH265FragmentationPacketHeader(packet.payloadHeader) + + fuPayload := packet.payload + + firstPacket := h265FragmentationPacket{ + payloadHeader: header, + fuHeader: newH265FragmentationUnitHeader(packet.payloadHeader, true, false), + donl: packet.donl, + payload: fuPayload[:sliceSize], + } + packets = append(packets, firstPacket) + fuPayload = fuPayload[sliceSize:] + + for len(fuPayload) > sliceSize { + p := h265FragmentationPacket{ + payloadHeader: header, + fuHeader: newH265FragmentationUnitHeader(packet.payloadHeader, false, false), + donl: nil, + payload: fuPayload[:sliceSize], } + packets = append(packets, p) - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) - p.donl = &donl - payload = payload[2:] + fuPayload = fuPayload[sliceSize:] } - p.payloadHeader = payloadHeader - p.fuHeader = fuHeader - p.payload = payload + lastPacket := h265FragmentationPacket{ + payloadHeader: header, + fuHeader: newH265FragmentationUnitHeader(packet.payloadHeader, false, true), + donl: nil, + payload: fuPayload, + } + packets = append(packets, lastPacket) + + return packets, nil +} + +func rebuildH265FragmentationPackets(packets []h265FragmentationPacket) (*h265SingleNALUnitPacket, error) { + if len(packets) < 2 { + return nil, errNotEnoughPackets + } + + if !packets[0].fuHeader.S() { + return nil, errFirstFragmentationUnitMissing + } + if !packets[len(packets)-1].fuHeader.E() { + return nil, errLastFragmentationUnitMissing + } + + payload := make([]byte, 0) + for _, fu := range packets { + payload = append(payload, fu.payload...) + } + + rebuilt := h265SingleNALUnitPacket{ + payloadHeader: rebuildH265FragmentationPacketHeader(packets[0].payloadHeader, packets[0].fuHeader), + donl: packets[0].donl, + payload: payload, + } + + return &rebuilt, nil +} + +// 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 +// +// Deprecated: replaced with a private type instead, will be removed in a future release. +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) { + parsed, err := parseH265FragmentationPacket(payload, p.mightNeedDONL) + if err != nil { + return nil, err + } + + p.payloadHeader = parsed.payloadHeader + p.fuHeader = parsed.fuHeader + p.donl = parsed.donl + p.payload = parsed.payload return nil, nil } @@ -559,9 +1088,9 @@ func (p *H265FragmentationUnitPacket) Payload() []byte { return p.payload } -func (p *H265FragmentationUnitPacket) isH265Packet() {} - // H265FragmentationPacket represents a Fragmentation packet, which contains one or more Fragmentation Units. +// +// Deprecated: replaced with a private type instead, will be removed in a future release. type H265FragmentationPacket struct { payloadHeader H265NALUHeader donl *uint16 @@ -593,67 +1122,90 @@ func (p *H265FragmentationPacket) Payload() []byte { return p.payload } -func (p *H265FragmentationPacket) isH265Packet() {} +// +// PACI implementation +// -func (p *H265FragmentationPacket) doPackaging(buf []byte) []byte { - if len(p.payload) == 0 { - return buf - } +// paciHeaderFields is the few fields after the payload header of a 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 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type paciHeaderFields uint16 - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) - buf = append(buf, p.payload...) +func (h *paciHeaderFields) A() bool { + return (uint16(*h) & 0b1 << 15) != 0 +} - return buf +func (h *paciHeaderFields) CType() uint8 { + mask := uint16(0b111111) << 9 + + return uint8((uint16(*h) & mask) >> 9) // nolint:gosec // G115 false positive } -func (p *H265FragmentationPacket) appendUnit(unit *H265FragmentationUnitPacket) { - if len(p.payload) > 0 { - // already have end unit - return - } - p.units = append(p.units, unit) - if unit.FuHeader().E() { - for _, u := range p.units { - p.payload = append(p.payload, u.payload...) - } - } +func (h *paciHeaderFields) PHSize() uint8 { + mask := uint16(0b11111) << 4 + + return uint8((uint16(*h) & mask) >> 4) // nolint:gosec // G115 false positive } -// -// PACI implementation -// +func (h *paciHeaderFields) F0() bool { + return (uint16(*h) & 0b1 << 3) != 0 +} + +func (h *paciHeaderFields) F1() bool { + return (uint16(*h) & 0b1 << 2) != 0 +} + +func (h *paciHeaderFields) F2() bool { + return (uint16(*h) & 0b1 << 1) != 0 +} + +func (h *paciHeaderFields) Y() bool { + return (uint16(*h) & 0b1) != 0 +} // 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 | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ +// +// 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 + paciHeaderFields // phes is a header extension, of byte length `PHSsize` phes []byte // Payload contains NAL units & optional padding - payload []byte + payload isH265Packet } // PayloadHeader returns the NAL Unit Header. @@ -661,131 +1213,262 @@ 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 +func (p *H265PACIPacket) PHSsize() uint8 { + return p.paciHeaderFields.PHSize() } -// CType copies the Type field of the PACI payload NALU. -func (p *H265PACIPacket) CType() uint8 { - const mask = 0b01111110 << 8 +// PHES contains header extensions. Its size is indicated by PHSsize. +func (p *H265PACIPacket) PHES() []byte { + return p.phes +} - return uint8((p.paciHeaderFields & mask) >> (8 + 1)) // nolint: gosec // G115 false positive +// Payload is a single NALU or NALU-like struct, without its header. +func (p *H265PACIPacket) Payload() []byte { + return p.payload.serialize(make([]byte, 0))[2:] } -// PHSsize indicates the size of the PHES field. -func (p *H265PACIPacket) PHSsize() uint8 { - const mask = (0b00000001 << 8) | 0b11110000 +// TSCI returns the Temporal Scalability Control Information extension, if present. +func (p *H265PACIPacket) TSCI() *H265TSCI { + if !p.F0() || p.PHSsize() < 3 || len(p.phes) < 3 { + return nil + } - return uint8((p.paciHeaderFields & mask) >> 4) // nolint: gosec // G115 false positive + tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[2])) + + return &tsci } -// F0 indicates the presence of a Temporal Scalability support extension in the PHES. -func (p *H265PACIPacket) F0() bool { - const mask = 0b00001000 +func rebuildPACIHeader(header H265NALUHeader, paciFields paciHeaderFields) H265NALUHeader { + f := uint16(0) + if paciFields.A() { + f = 1 + } + pType := paciFields.CType() + layerID := header.LayerID() + tid := header.TID() - return (p.paciHeaderFields & mask) != 0 + return H265NALUHeader( + (f << 15) | + (uint16(pType) << 9) | + (uint16(layerID) << 3) | + (uint16(tid)), + ) } -// F1 must be zero, reserved for future extensions. -func (p *H265PACIPacket) F1() bool { - const mask = 0b00000100 +func parseH265PACIPacket(buf []byte, withDONL bool) (*H265PACIPacket, error) { // nolint: cyclop + minSize := h265NaluHeaderSize + 2 + if buf == nil { + return nil, errNilPacket + } + if len(buf) < minSize { + return nil, errShortPacket + } + header := H265NALUHeader(binary.BigEndian.Uint16(buf[0:2])) - return (p.paciHeaderFields & mask) != 0 -} + if header.Type() != h265NaluPACIPacketType { + return nil, errInvalidH265PacketType + } -// F2 must be zero, reserved for future extensions. -func (p *H265PACIPacket) F2() bool { - const mask = 0b00000010 + paciFields := paciHeaderFields(binary.BigEndian.Uint16(buf[2:4])) - return (p.paciHeaderFields & mask) != 0 -} + // a PACI packet cannot be inside another PACI packet + if paciFields.CType() == h265NaluPACIPacketType { + return nil, errInvalidH265PacketType + } -// Y must be zero, reserved for future extensions. -func (p *H265PACIPacket) Y() bool { - const mask = 0b00000001 + if len(buf) < minSize+int(paciFields.PHSize()) { + return nil, errShortPacket + } - return (p.paciHeaderFields & mask) != 0 -} + payloadStart := 4 + paciFields.PHSize() -// PHES contains header extensions. Its size is indicated by PHSsize. -func (p *H265PACIPacket) PHES() []byte { - return p.phes -} + phes := buf[4:payloadStart] -// Payload is a single NALU or NALU-like struct, not including the first two octets (header). -func (p *H265PACIPacket) Payload() []byte { - return p.payload -} + innerNalu := buf[payloadStart:] -// TSCI returns the Temporal Scalability Control Information extension, if present. -func (p *H265PACIPacket) TSCI() *H265TSCI { - if !p.F0() || p.PHSsize() < 3 { - return nil + var innerPacket isH265Packet + + switch paciFields.CType() { + case h265NaluAggregationPacketType: + minLength := h265NaluHeaderSize + h265AggregatedPacketLengthSize*2 + // DONL field + 1 DOND field + if withDONL { + minLength += h265NaluDonlSize + 1 + } + if len(innerNalu) < minLength { + return nil, errShortPacket + } + var donl *uint16 + innerPayloadStart := 0 + if withDONL { + donlVal := binary.BigEndian.Uint16(innerNalu[0:2]) + donl = &donlVal + innerPayloadStart += h265NaluDonlSize + } + + innerPacket = &h265AggregationPacket{ + payloadHeader: rebuildPACIHeader(header, paciFields), + donl: donl, + payload: innerNalu[innerPayloadStart:], + } + case h265NaluFragmentationUnitType: + // header + fuHeader + minLength := h265NaluHeaderSize + 1 + if withDONL { + minLength += h265NaluDonlSize + } + if len(innerNalu) < minLength { + return nil, errShortPacket + } + var donl *uint16 + innerPayloadStart := 1 + if withDONL { + donlVal := binary.BigEndian.Uint16(innerNalu[1:3]) + donl = &donlVal + innerPayloadStart += h265NaluDonlSize + } + innerPacket = &h265FragmentationPacket{ + payloadHeader: rebuildPACIHeader(header, paciFields), + fuHeader: H265FragmentationUnitHeader(innerNalu[0]), + donl: donl, + payload: innerNalu[innerPayloadStart:], + } + default: + // header + fuHeader + minLength := h265NaluHeaderSize + if withDONL { + minLength += h265NaluDonlSize + } + if len(innerNalu) < minLength { + return nil, errShortPacket + } + var donl *uint16 + innerPayloadStart := 0 + if withDONL { + donlVal := binary.BigEndian.Uint16(innerNalu[0:2]) + donl = &donlVal + innerPayloadStart += h265NaluDonlSize + } + innerPacket = &h265SingleNALUnitPacket{ + payloadHeader: rebuildPACIHeader(header, paciFields), + donl: donl, + payload: innerNalu[innerPayloadStart:], + } } - tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[0])) + packet := H265PACIPacket{ + header, + paciFields, + phes, + innerPacket, + } - return &tsci + return &packet, nil } -// 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) +func newH265PACIPacketHeaders(originalHeader H265NALUHeader, phes []byte) (*H265NALUHeader, *paciHeaderFields, error) { + if len(phes) >= 32 { + return nil, nil, errH265PACIPHESTooLong } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket + newHeader := H265NALUHeader( + uint16(h265NaluPACIPacketType)<<9 | + uint16(originalHeader.LayerID())<<3 | + uint16(originalHeader.TID()), + ) + a := uint16(0) + if originalHeader.F() { + a = 1 } - if !payloadHeader.IsPACIPacket() { - return nil, errInvalidH265PacketType + f0 := uint16(0) + if len(phes) > 0 { + f0 = 1 } + headerFields := paciHeaderFields( + (a << 15) | + (uint16(originalHeader.Type()) << 9) | + (uint16(len(phes)) << 4) | // nolint: gosec // G115 false positive + (f0 << 3), + ) - paciHeaderFields := (uint16(payload[2]) << 8) | uint16(payload[3]) - payload = payload[4:] + return &newHeader, &headerFields, nil +} - p.paciHeaderFields = paciHeaderFields - headerExtensionSize := p.PHSsize() +func newH265PACIPacket(inner isH265Packet) (*H265PACIPacket, error) { + _, ok := inner.(*H265PACIPacket) + if ok { + return nil, errInvalidH265PacketType + } - if len(payload) < int(headerExtensionSize)+1 { - p.paciHeaderFields = 0 + header, headerFields, err := newH265PACIPacketHeaders(inner.header(), nil) + if err != nil { + return nil, err + } - return nil, errShortPacket + packet := H265PACIPacket{ + payloadHeader: *header, + paciHeaderFields: *headerFields, + phes: nil, + payload: inner, } - p.payloadHeader = payloadHeader + return &packet, nil +} - if headerExtensionSize > 0 { - p.phes = payload[:headerExtensionSize] +// 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) { + // Bad behavior, no DONL parsing + packet, err := parseH265PACIPacket(payload, false) + if err != nil { + return nil, err } - payload = payload[headerExtensionSize:] - p.payload = payload + p.payloadHeader = packet.payloadHeader + p.paciHeaderFields = packet.paciHeaderFields + p.phes = packet.phes + p.payload = packet.payload return nil, nil } func (p *H265PACIPacket) isH265Packet() {} -func (p *H265PACIPacket) doPackaging(buf []byte) []byte { - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) +func (p *H265PACIPacket) header() H265NALUHeader { + return p.payloadHeader +} - buf = binary.BigEndian.AppendUint16(buf, p.paciHeaderFields) +func (p *H265PACIPacket) serialize(buf []byte) []byte { + buf = binary.BigEndian.AppendUint16(buf, uint16(p.payloadHeader)) + + buf = binary.BigEndian.AppendUint16(buf, uint16(p.paciHeaderFields)) if len(p.phes) > 0 { buf = append(buf, p.phes...) } - buf = append(buf, p.payload...) + fragment, ok := p.payload.(*h265FragmentationPacket) + if ok { + buf = append(buf, byte(fragment.fuHeader)) + if fragment.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *fragment.donl) + } + buf = append(buf, fragment.payload...) + } + + aggregation, ok := p.payload.(*h265AggregationPacket) + if ok { + if aggregation.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *aggregation.donl) + } + buf = append(buf, aggregation.payload...) + } + + single, ok := p.payload.(*h265SingleNALUnitPacket) + if ok { + if single.donl != nil { + buf = binary.BigEndian.AppendUint16(buf, *single.donl) + } + buf = append(buf, single.payload...) + } return buf } @@ -844,97 +1527,186 @@ func (h H265TSCI) RES() uint8 { type isH265Packet interface { isH265Packet() - doPackaging([]byte) []byte + header() H265NALUHeader + serialize([]byte) []byte } var ( - _ isH265Packet = (*H265FragmentationPacket)(nil) + _ isH265Packet = (*h265FragmentationPacket)(nil) _ isH265Packet = (*H265PACIPacket)(nil) - _ isH265Packet = (*H265SingleNALUnitPacket)(nil) - _ isH265Packet = (*H265AggregationPacket)(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 +// H265Depacketizer unmarshals an H265 RTP stream into an Annex-B one. +type H265Depacketizer struct { + hasDonl bool + partials []h265FragmentationPacket videoDepacketizer } -// 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 +func (d *H265Depacketizer) handleSingleUnit(output []byte, single h265SingleNALUnitPacket) []byte { + d.partials = d.partials[:0] + output = single.toAnnexB(output) + + return output } -// 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) { // nolint:cyclop - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= h265NaluHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), h265NaluHeaderSize) +func (d *H265Depacketizer) handleAggregationUnit(output []byte, aggregation h265AggregationPacket) ([]byte, error) { + d.partials = d.partials[:0] + aggregated, err := splitH265AggregationPacket(aggregation) + if err != nil { + return nil, err } - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket + for _, p := range aggregated { + output = p.toAnnexB(output) } - switch { - case payloadHeader.IsPACIPacket(): - decoded := &H265PACIPacket{} - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } + return output, nil +} - p.packet = decoded +func (d *H265Depacketizer) handleFragmentationUnit(output []byte, fragment h265FragmentationPacket) ([]byte, error) { + if fragment.fuHeader.E() { // nolint: nestif + if len(d.partials) == 0 { + return output, nil + } - case payloadHeader.IsFragmentationUnit(): - decoded := &H265FragmentationUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) + d.partials = append(d.partials, fragment) - if _, err := decoded.Unmarshal(payload); err != nil { + rebuilt, err := rebuildH265FragmentationPackets(d.partials) + if err != nil { return nil, err } + output = d.handleSingleUnit(output, *rebuilt) + d.partials = d.partials[:0] + + return output, nil + } else { + // discard lost partial fragments + if fragment.fuHeader.S() { + d.partials = d.partials[:0] + } else if len(d.partials) == 0 { + return nil, errExpectFragmentationStartUnit + } - if decoded.FuHeader().S() { - p.packet = NewH265FragmentationPacket(decoded) - } else { - fu, ok := p.packet.(*H265FragmentationPacket) - if !ok { - return nil, errExpectFragmentationStartUnit - } + d.partials = append(d.partials, fragment) - fu.appendUnit(decoded) - } + return nil, nil + } +} - case payloadHeader.IsAggregationPacket(): - decoded := &H265AggregationPacket{} - decoded.WithDONL(p.mightNeedDONL) +func (d *H265Depacketizer) Unmarshal(payload []byte) ([]byte, error) { // nolint:cyclop, gocognit + if len(payload) < h265NaluHeaderSize { + return nil, errShortPacket + } - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } + header := H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])) - p.packet = decoded + output := make([]byte, 0) + switch { + case header.IsFragmentationUnit(): + parseDonl := len(d.partials) == 0 && d.hasDonl + fragment, err := parseH265FragmentationPacket(payload, parseDonl) + if err != nil { + return nil, err + } + output, err = d.handleFragmentationUnit(output, *fragment) + if err != nil { + return nil, err + } + case header.IsAggregationPacket(): + aggregation, err := parseH265AggregationPacket(payload, d.hasDonl) + if err != nil { + return nil, err + } + output, err = d.handleAggregationUnit(output, *aggregation) + if err != nil { + return nil, err + } + case header.IsPACIPacket(): + paci, err := parseH265PACIPacket(payload, d.hasDonl) + if err != nil { + return nil, err + } + fragment, ok := paci.payload.(*h265FragmentationPacket) + if ok { + output, err = d.handleFragmentationUnit(output, *fragment) + if err != nil { + return nil, err + } + } + aggregation, ok := paci.payload.(*h265AggregationPacket) + if ok { + output, err = d.handleAggregationUnit(output, *aggregation) + if err != nil { + return nil, err + } + } + single, ok := paci.payload.(*h265SingleNALUnitPacket) + if ok { + output = d.handleSingleUnit(output, *single) + } default: - decoded := &H265SingleNALUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) - - if _, err := decoded.Unmarshal(payload); err != nil { + single, err := parseH265SingleNalUnitPacket(payload, d.hasDonl) + if err != nil { return nil, err } + output = d.handleSingleUnit(output, *single) + } - p.packet = decoded + return output, nil +} + +func (d *H265Depacketizer) IsPartitionHead(payload []byte) bool { + if len(payload) < 2 { + return false + } + header := H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])) + if header.IsFragmentationUnit() { + if len(payload) < 3 { + return false + } + fuHeader := H265FragmentationUnitHeader(payload[2]) + + return fuHeader.S() + } + + return true +} + +func (d *H265Depacketizer) IsPartitionTail(marker bool, payload []byte) bool { + if len(payload) < 3 { + return marker } + header := H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])) + if !header.IsFragmentationUnit() { + return marker + } + fuHeader := H265FragmentationUnitHeader(payload[2]) + + return fuHeader.E() +} + +// H265Packet represents a H265 packet, stored in the payload of an RTP packet. +// +// Deprecated: Use H265Depacketizer instead. +type H265Packet struct { + packet isH265Packet + + H265Depacketizer +} - return p.packet.doPackaging(nil), nil +// 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.H265Depacketizer.hasDonl = value } // Packet returns the populated packet. @@ -943,21 +1715,86 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { // nolint:cyclo // - *H265FragmentationUnitPacket // - *H265AggregationPacket // - *H265PACIPacket. +// +// Deprecated: will always return nil. func (p *H265Packet) Packet() isH265Packet { return p.packet } -// IsPartitionHead checks if this is the head of a packetized nalu stream. -func (*H265Packet) IsPartitionHead(payload []byte) bool { - if len(payload) < 3 { - return false - } +// h265Packetizer converts an Annex-B H265 stream into RFC7798 RTP packet payloads. +type h265Packetizer struct{} - if H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])).Type() == h265NaluFragmentationUnitType { - return H265FragmentationUnitHeader(payload[2]).S() +func (p *h265Packetizer) Payload(mtu uint16, payload []byte) [][]byte { // nolint:cyclop + var payloads [][]byte + naluBuffer := make([]h265SingleNALUnitPacket, 0) + + flushBuffer := func() { + switch len(naluBuffer) { + case 0: + return + case 1: + packetized := naluBuffer[0].serialize(make([]byte, 0, naluBuffer[0].wireSize())) + naluBuffer = naluBuffer[:0] + payloads = append(payloads, packetized) + default: + aggrPacket, err := newH265AggregationPacket(naluBuffer) + naluBuffer = naluBuffer[:0] + if err != nil { + return + } + packetized := aggrPacket.serialize(make([]byte, 0)) + payloads = append(payloads, packetized) + } } - return true + emitNalus(payload, func(nalu []byte) { + if len(nalu) < h265NaluHeaderSize { + return + } + + header := H265NALUHeader(binary.BigEndian.Uint16(nalu[0:2])) + + if header.IsAggregationPacket() || + header.IsFragmentationUnit() || + header.IsPACIPacket() { + return + } + + packet := h265SingleNALUnitPacket{ + header, + nil, + nalu[2:], + } + + if len(nalu) > int(mtu) { // nolint: nestif + flushBuffer() + fragments, err := newH265FragmentationPackets(mtu, &packet) + if err != nil { + return + } + for _, fragment := range fragments { + payloads = append(payloads, fragment.serialize(make([]byte, 0))) + } + } else { + if len(naluBuffer) == 0 { + if canAggregateH265(mtu, &packet) { + naluBuffer = append(naluBuffer, packet) + } else { + payloads = append(payloads, nalu) + } + } else { + // can't fit any more packets, just send what we have and make current first in buffer + if shouldAggregateH265Now(mtu, naluBuffer, packet) { + flushBuffer() + } + naluBuffer = append(naluBuffer, packet) + } + } + }) + + flushBuffer() + + return payloads } // H265Payloader payloads H265 packets. diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 7562d02..8ba0b8b 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -4,12 +4,23 @@ package codecs import ( + "encoding/binary" "errors" + "slices" "testing" "github.com/stretchr/testify/assert" ) +func createTestH265Header(pType, layerID, tid uint8, f bool) H265NALUHeader { + var fVal, zVal uint16 + if f { + fVal = 1 << 15 + } + + return H265NALUHeader(uint16(tid) | (uint16(pType) << 9) | (uint16(layerID) << 3) | fVal | zVal) +} + func TestH265_NALU_Header(t *testing.T) { tt := [...]struct { RawHeader []byte @@ -147,7 +158,7 @@ func TestH265_SingleNALUnitPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool - ExpectedPacket *H265SingleNALUnitPacket + ExpectedPacket *h265SingleNALUnitPacket ExpectedErr error }{ { @@ -183,7 +194,7 @@ func TestH265_SingleNALUnitPacket(t *testing.T) { }, { Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, - ExpectedPacket: &H265SingleNALUnitPacket{ + ExpectedPacket: &h265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), payload: []byte{0xab, 0xcd, 0xef}, }, @@ -196,7 +207,7 @@ func TestH265_SingleNALUnitPacket(t *testing.T) { }, { Raw: []byte{0x01, 0x01, 0xaa, 0xbb, 0xcc}, - ExpectedPacket: &H265SingleNALUnitPacket{ + ExpectedPacket: &h265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), donl: uint16ptr((uint16(0xaa) << 8) | uint16(0xbb)), payload: []byte{0xcc}, @@ -206,16 +217,11 @@ func TestH265_SingleNALUnitPacket(t *testing.T) { } for _, cur := range tt { - parsed := &H265SingleNALUnitPacket{} - if cur.WithDONL { - parsed.WithDONL(cur.WithDONL) - } + parsed, err := parseH265SingleNalUnitPacket(cur.Raw, cur.WithDONL) // Just for code coverage sake parsed.isH265Packet() - _, err := parsed.Unmarshal(cur.Raw) - if cur.ExpectedErr == nil { assert.NoError(t, err) } else { @@ -238,262 +244,6 @@ func TestH265_SingleNALUnitPacket(t *testing.T) { } } -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: errInvalidH265PacketType, - }, - // 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: errH265CorruptedPacket, - }, - // 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 { - assert.NoError(t, err) - } else { - assert.ErrorIs(t, err, cur.ExpectedErr) - } - - if cur.ExpectedPacket == nil { - continue - } - - if cur.ExpectedPacket.FirstUnit() != nil { - assert.Equal(t, cur.ExpectedPacket.FirstUnit().NALUSize(), parsed.FirstUnit().NALUSize()) - - if cur.ExpectedPacket.FirstUnit().DONL() != nil { - assert.Equal(t, *cur.ExpectedPacket.FirstUnit().DONL(), *parsed.FirstUnit().DONL()) - } else { - assert.Nil(t, parsed.FirstUnit().DONL()) - } - - assert.Equal( - t, cur.ExpectedPacket.FirstUnit().NalUnit(), parsed.FirstUnit().NalUnit(), - ) - } - - assert.Len(t, cur.ExpectedPacket.OtherUnits(), len(parsed.OtherUnits())) - - for ndx, unit := range cur.ExpectedPacket.OtherUnits() { - assert.Equal(t, unit.NALUSize(), parsed.OtherUnits()[ndx].NALUSize()) - - if unit.DOND() != nil { - assert.Equal(t, *unit.DOND(), *parsed.OtherUnits()[ndx].DOND()) - } else { - assert.Nil(t, parsed.OtherUnits()[ndx].DOND()) - } - - assert.Equal(t, unit.NalUnit(), parsed.OtherUnits()[ndx].NalUnit()) - } - - assert.Equal(t, cur.ExpectedPacket.OtherUnits(), parsed.OtherUnits()) - } -} - -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 { - assert.ErrorIs(t, err, cur.ExpectedErr) - } else { - assert.NoError(t, err) - } - - if cur.ExpectedFU == nil { - continue - } - - assert.Equal(t, cur.ExpectedFU.PayloadHeader(), parsed.PayloadHeader()) - assert.Equal(t, cur.ExpectedFU.FuHeader(), parsed.FuHeader()) - - if cur.ExpectedFU.DONL() != nil { - assert.Equal(t, *cur.ExpectedFU.DONL(), *parsed.DONL()) - } else { - assert.Nil(t, parsed.DONL()) - } - - assert.Equal(t, cur.ExpectedFU.Payload(), parsed.Payload()) - } -} - func TestH265_TemporalScalabilityControlInformation(t *testing.T) { tt := [...]struct { Value H265TSCI @@ -537,519 +287,560 @@ func TestH265_TemporalScalabilityControlInformation(t *testing.T) { } } -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: errShortPacket, - }, - // 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}, - }, - }, - } +func TestH265IsPartitionHead(t *testing.T) { + h265 := H265Depacketizer{} - for _, cur := range tt { - parsed := &H265PACIPacket{} - _, err := parsed.Unmarshal(cur.Raw) + assert.False(t, h265.IsPartitionHead(nil), "nil must not be a partition head") + assert.False(t, h265.IsPartitionHead([]byte{}), "empty nalu must not be a partition head") - // Just for code coverage sake - parsed.isH265Packet() + singleNalu := []byte{0x01, 0x01, 0xab, 0xcd, 0xef} + assert.True(t, h265.IsPartitionHead(singleNalu), "single nalu must be a partition head") - if cur.ExpectedErr != nil { - assert.ErrorIs(t, err, cur.ExpectedErr) - } else { - assert.NoError(t, err) - } + fbitNalu := []byte{0x80, 0x00, 0x00} + assert.True(t, h265.IsPartitionHead(fbitNalu), "fbit nalu must be a partition head") - if cur.ExpectedFU == nil { - continue - } + fuStartNalu := []byte{0x62, 0x01, 0x93} + assert.True(t, h265.IsPartitionHead(fuStartNalu), "fu start nalu must be a partition head") - assert.Equal(t, cur.ExpectedFU.PayloadHeader(), parsed.PayloadHeader()) - assert.Equal(t, cur.ExpectedFU.A(), parsed.A()) - assert.Equal(t, cur.ExpectedFU.CType(), parsed.CType()) - assert.Equal(t, cur.ExpectedFU.PHSsize(), parsed.PHSsize()) - assert.Equal(t, cur.ExpectedFU.F0(), parsed.F0()) - assert.Equal(t, cur.ExpectedFU.F1(), parsed.F1()) - assert.Equal(t, cur.ExpectedFU.F2(), parsed.F2()) - assert.Equal(t, cur.ExpectedFU.Y(), parsed.Y()) - assert.Equal(t, cur.ExpectedFU.PHES(), parsed.PHES()) - assert.Equal(t, cur.ExpectedFU.Payload(), parsed.Payload()) - if cur.ExpectedFU.TSCI() != nil { - assert.Equal(t, cur.ExpectedFU.TSCI(), parsed.TSCI()) - } else { - assert.Nil(t, parsed.TSCI()) - } + fuEndNalu := []byte{0x62, 0x01, 0x53} + assert.False(t, h265.IsPartitionHead(fuEndNalu), "fu end nalu must not be a partition head") +} + +func TestH265IsPartitionTail(t *testing.T) { + h265 := H265Depacketizer{} + + assert.False(t, h265.IsPartitionTail(false, nil), "nil must not be a partition tail") + assert.False(t, h265.IsPartitionTail(false, []byte{}), "empty nalu must not be a partition tail") + + singleNalu := []byte{0x01, 0x01, 0xab, 0xcd, 0xef} + assert.False(t, h265.IsPartitionTail(false, singleNalu), "single nalu must not be a partition tail") + + fbitNalu := []byte{0x80, 0x00, 0x00} + assert.False(t, h265.IsPartitionTail(false, fbitNalu), "fbit nalu must not be a partition tail") + + fuStartNalu := []byte{0x62, 0x01, 0x93} + assert.False(t, h265.IsPartitionTail(false, fuStartNalu), "fu start nalu must not be a partition tail") + + fuEndNalu := []byte{0x62, 0x01, 0x53} + assert.True(t, h265.IsPartitionTail(false, fuEndNalu), "fu end nalu mustw be a partition tail") +} + +func TestH265_Packet_Real(t *testing.T) { + // Tests decoding of real H265 payloads extracted from a Wireshark dump. + + // nolint: lll + 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)) + assert.True(t, err == nil || errors.Is(err, errNotEnoughPackets)) } } -func TestH265_Packet(t *testing.T) { - tt := [...]struct { - Raw []byte - WithDONL bool - ExpectedPacketType any - 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: &H265SingleNALUnitPacket{}, - }, - // 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: &H265PACIPacket{}, - }, - // Valid H265FragmentationUnitPacket - { - Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, - ExpectedPacketType: &H265FragmentationPacket{}, - WithDONL: true, - }, - // Valid H265AggregationPacket - { - Raw: []byte{0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 0x77, 0x00, 0x01, 0xaa}, - ExpectedPacketType: &H265AggregationPacket{}, - WithDONL: true, - }, - // Invalid H265AggregationPacket - { - Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, - ExpectedErr: errShortPacket, - WithDONL: true, - }, +func TestH265Payloader_Real(t *testing.T) { + // curl -LO "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h265/1080/Big_Buck_Bunny_1080_10s_1MB.mp4" + // ffmpeg -i Big_Buck_Bunny_1080_10s_1MB.mp4 -c:v copy Big_Buck_Bunny_1080_10s_1MB.h265 + // hexdump -v -e '1/1 "0x%02x, "' Big_Buck_Bunny_1080_10s_1MB.h265 > aaa + + // nolint: lll + payload := []byte{ + 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x95, 0x98, 0x09, + 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x10, 0xe5, 0x96, 0x56, 0x69, 0x24, 0xca, 0xf0, 0x10, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01, 0xe0, 0x80, + 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40, + 0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x05, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x71, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4, 0xfe, 0x7f, 0xc2, 0xfc, 0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x31, 0x35, 0x31, 0x29, 0x20, 0x2d, 0x20, 0x32, 0x2e, 0x36, 0x2b, 0x34, 0x39, 0x2d, 0x37, 0x32, 0x31, 0x39, 0x33, 0x37, 0x36, 0x64, 0x65, 0x34, 0x32, 0x61, 0x3a, 0x5b, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x5d, 0x5b, 0x47, 0x43, 0x43, 0x20, 0x37, 0x2e, 0x33, 0x2e, 0x30, 0x5d, 0x5b, 0x36, 0x34, 0x20, 0x62, 0x69, 0x74, 0x5d, 0x20, 0x38, 0x62, 0x69, 0x74, 0x2b, 0x31, 0x30, 0x62, 0x69, 0x74, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x35, 0x2f, 0x48, 0x45, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x32, 0x30, 0x31, 0x38, 0x20, 0x28, 0x63, 0x29, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x32, 0x36, 0x35, 0x2e, 0x6f, 0x72, 0x67, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x70, 0x75, 0x69, 0x64, 0x3d, 0x31, 0x30, 0x35, 0x30, 0x31, 0x31, 0x31, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x33, 0x20, 0x6e, 0x75, 0x6d, 0x61, 0x2d, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x3d, 0x38, 0x20, 0x77, 0x70, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x73, 0x6e, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x20, 0x6c, 0x6f, 0x67, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x62, 0x69, 0x74, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x38, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x63, 0x73, 0x70, 0x3d, 0x31, 0x20, 0x66, 0x70, 0x73, 0x3d, 0x33, 0x30, 0x2f, 0x31, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x72, 0x65, 0x73, 0x3d, 0x31, 0x39, 0x32, 0x30, 0x78, 0x31, 0x30, 0x38, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x3d, 0x30, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x63, 0x3d, 0x30, 0x20, 0x68, 0x69, 0x67, 0x68, 0x2d, 0x74, 0x69, 0x65, 0x72, 0x3d, 0x31, 0x20, 0x75, 0x68, 0x64, 0x2d, 0x62, 0x64, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x6e, 0x65, 0x78, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x75, 0x64, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x72, 0x64, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x70, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x67, 0x6f, 0x70, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x62, 0x2d, 0x61, 0x64, 0x61, 0x70, 0x74, 0x3d, 0x32, 0x20, 0x62, 0x2d, 0x70, 0x79, 0x72, 0x61, 0x6d, 0x69, 0x64, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x32, 0x35, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x72, 0x61, 0x64, 0x6c, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x20, 0x63, 0x74, 0x75, 0x3d, 0x36, 0x34, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x63, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x38, 0x20, 0x72, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6d, 0x70, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x74, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x74, 0x75, 0x3d, 0x30, 0x20, 0x72, 0x64, 0x6f, 0x71, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x2d, 0x72, 0x64, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x2d, 0x72, 0x64, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x68, 0x69, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x73, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x6d, 0x65, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x35, 0x37, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6d, 0x76, 0x70, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x2d, 0x73, 0x72, 0x63, 0x2d, 0x70, 0x69, 0x63, 0x73, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x30, 0x3a, 0x30, 0x20, 0x73, 0x61, 0x6f, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x61, 0x6f, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x72, 0x64, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x75, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x62, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x72, 0x64, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x64, 0x70, 0x65, 0x6e, 0x61, 0x6c, 0x74, 0x79, 0x3d, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x3d, 0x32, 0x2e, 0x30, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x6f, 0x71, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x64, 0x2d, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x63, 0x62, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x63, 0x72, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x38, 0x38, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3d, 0x30, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x69, 0x70, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x70, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x33, 0x30, 0x20, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x20, 0x61, 0x71, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x63, 0x75, 0x74, 0x72, 0x65, 0x65, 0x20, 0x7a, 0x6f, 0x6e, 0x65, 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x2d, 0x63, 0x62, 0x72, 0x20, 0x71, 0x67, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x63, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x2d, 0x76, 0x62, 0x76, 0x20, 0x73, 0x61, 0x72, 0x3d, 0x31, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x3d, 0x30, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x3d, 0x35, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x70, 0x72, 0x69, 0x6d, 0x3d, 0x32, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x3d, 0x32, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x6c, 0x6f, 0x63, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x63, 0x6c, 0x6c, 0x3d, 0x30, 0x2c, 0x30, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x32, 0x35, 0x35, 0x20, 0x6c, 0x6f, 0x67, 0x32, 0x2d, 0x6d, 0x61, 0x78, 0x2d, 0x70, 0x6f, 0x63, 0x2d, 0x6c, 0x73, 0x62, 0x3d, 0x38, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x68, 0x72, 0x64, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x71, 0x70, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x70, 0x73, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x2e, 0x30, 0x35, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x63, 0x75, 0x2d, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2d, 0x71, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x64, 0x68, 0x64, 0x72, 0x31, 0x30, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x35, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2d, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x73, 0x61, 0x6f, 0x20, 0x63, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x77, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x64, 0x63, 0x74, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2d, 0x70, 0x69, 0x63, 0x3d, 0x31, 0x80, } + pck := H265Payloader{} + res := pck.Payload(1400, payload) + // 1. Aggregating three NALUs into a single payload + // 2. Fragmented packets divided by MTU=1400 + // 3. Remaining fragment packets split by MTU + assert.Len(t, res, 3, "Generated payload should be 3") +} - for _, cur := range tt { - pck := &H265Packet{} - if cur.WithDONL { - pck.WithDONL(true) +func TestH265_FragmentationMtu(t *testing.T) { + payload := make([]byte, 2000) + + for i := 0; i < 2000; i++ { + payload[i] = uint8(i) //nolint: gosec // idc + } + + simplePacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 0, false), + nil, + payload, + } + + serializeBuf := make([]byte, 0, 4096) + + for mtu := uint16(800); mtu <= 1500; mtu++ { + fragments, err := newH265FragmentationPackets(mtu, &simplePacket) + + assert.Nil(t, err) + + for _, f := range fragments { + assert.LessOrEqual(t, len(f.serialize(serializeBuf)), int(mtu)) + serializeBuf = serializeBuf[0:] } + } - _, err := pck.Unmarshal(cur.Raw) - if cur.ExpectedErr == nil { - assert.NoError(t, err) - } else { - assert.ErrorIs(t, err, cur.ExpectedErr) + testDonl := uint16(100) + simplePacket.donl = &testDonl + + for mtu := uint16(800); mtu <= 1500; mtu++ { + fragments, err := newH265FragmentationPackets(mtu, &simplePacket) + + assert.Nil(t, err) + + for _, f := range fragments { + assert.LessOrEqual(t, len(f.serialize(serializeBuf)), int(mtu)) + serializeBuf = serializeBuf[0:] } + } +} - if cur.ExpectedErr != nil { - continue +func TestH265_FragmentationRoundtrip(t *testing.T) { + testFragmentation := func(packet h265SingleNALUnitPacket) { + for mtu := uint16(100); mtu < 1500; mtu++ { + fragments, err := newH265FragmentationPackets(100, &packet) + assert.Nil(t, err) + + rebuilt, err := rebuildH265FragmentationPackets(fragments) + assert.Nil(t, err) + + assert.Equal( + t, + packet, + *rebuilt, + "Expected packets to match after fragmentation", + ) } + } + + payload := make([]byte, 0) + testDonl := uint16(100) + + for i := 0; i < 200; i++ { + payload = append(payload, uint8(i)) //nolint: gosec // idc + } + + simplePacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 0, false), + nil, + payload, + } + + testFragmentation(simplePacket) - assert.IsType(t, cur.ExpectedPacketType, pck.Packet()) + packetWithDonl := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 0, false), + &testDonl, + payload, } + testFragmentation(packetWithDonl) + + everyFlagSet := h265SingleNALUnitPacket{ + createTestH265Header(1, 1, 1, true), + &testDonl, + payload, + } + + testFragmentation(everyFlagSet) } -func TestH265IsPartitionHead(t *testing.T) { - h265 := H265Packet{} +func TestH265_AggregationRoundtrip(t *testing.T) { + testAggregation := func(expected []h265SingleNALUnitPacket, withDonl bool) { + created, err := newH265AggregationPacket(expected) + assert.Nil(t, err) + packet := created.serialize(make([]byte, 0)) + aggr, err := parseH265AggregationPacket(packet, withDonl) + assert.Nil(t, err) + split, err := splitH265AggregationPacket(*aggr) + assert.Equal(t, len(expected), len(split)) + assert.Nil(t, err) + + assert.True(t, slices.EqualFunc(split, expected, func(a, b h265SingleNALUnitPacket) bool { + donlMatch := true + if a.donl != nil && b.donl != nil { + donlMatch = *a.donl == *b.donl + } else if b.donl != nil { + donlMatch = false + } - assert.False(t, h265.IsPartitionHead(nil), "nil must not be a partition head") - assert.False(t, h265.IsPartitionHead([]byte{}), "empty nalu must not be a partition head") + return slices.Equal(a.payload, b.payload) && a.payloadHeader == b.payloadHeader && donlMatch + })) + } - singleNalu := []byte{0x01, 0x01, 0xab, 0xcd, 0xef} - assert.True(t, h265.IsPartitionHead(singleNalu), "single nalu must be a partition head") + simplePacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 1, false), + nil, + []byte{0x00, 0x01, 0x02, 0x03}, + } + diffPacket := h265SingleNALUnitPacket{ + newH265NALUHeader(0b1000000, 0b00010000), + nil, + []byte{0x03, 0x02, 0x01, 0x00, 0x12}, + } - fbitNalu := []byte{0x80, 0x00, 0x00} - assert.True(t, h265.IsPartitionHead(fbitNalu), "fbit nalu must be a partition head") + testAggregation([]h265SingleNALUnitPacket{simplePacket, simplePacket, simplePacket}, false) + testAggregation([]h265SingleNALUnitPacket{diffPacket, simplePacket, simplePacket}, false) + testAggregation([]h265SingleNALUnitPacket{diffPacket, diffPacket, simplePacket}, false) + + withDonlCount := uint16(4) + withDonl := make([]h265SingleNALUnitPacket, withDonlCount) + for i := uint16(0); i < withDonlCount; i++ { + donlVal := i + withDonl[i] = h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 1, false), + &donlVal, + []byte{0x00, 0x01, 0x02, 0x03}, + } + } - fuStartNalu := []byte{0x62, 0x01, 0x93} - assert.True(t, h265.IsPartitionHead(fuStartNalu), "fu start nalu must be a partition head") + // With contiguous DONL values + testAggregation([]h265SingleNALUnitPacket{withDonl[0], withDonl[1], withDonl[2]}, true) + // With a gap in DONL values + testAggregation([]h265SingleNALUnitPacket{withDonl[0], withDonl[2], withDonl[3]}, true) +} - fuEndNalu := []byte{0x62, 0x01, 0x53} - assert.False(t, h265.IsPartitionHead(fuEndNalu), "fu end nalu must not be a partition head") +func TestH265_PACIRoundtrip(t *testing.T) { + testPACI := func(expected isH265Packet, withDonl bool) { + created, err := newH265PACIPacket(expected) + assert.Nil(t, err) + packet := created.serialize(make([]byte, 0)) + paci, err := parseH265PACIPacket(packet, withDonl) + assert.Nil(t, err) + + assert.Equal(t, expected, paci.payload) + } + + testDONL := uint16(100) + testDONL2 := uint16(101) + + simplePacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 1, false), + nil, + []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + } + + testPACI(&simplePacket, false) + + // With DONL + simplePacket.donl = &testDONL + + testPACI(&simplePacket, true) + + simplePacket.donl = nil + + // fragmentation packets + + fragments, err := newH265FragmentationPackets(10, &simplePacket) + assert.Nil(t, err) + + for i := range fragments { + testPACI(&fragments[i], false) + } + + // With DONL + + simplePacket.donl = &testDONL + + fragmentsWithDONL, err := newH265FragmentationPackets(10, &simplePacket) + assert.Nil(t, err) + + for i := range fragmentsWithDONL { + testPACI(&fragmentsWithDONL[i], fragmentsWithDONL[i].donl != nil) + } + + simplePacket.donl = nil + + // aggregation packet + + diffPacket := h265SingleNALUnitPacket{ + simplePacket.payloadHeader, + nil, + simplePacket.payload, + } + + aggregation, err := newH265AggregationPacket([]h265SingleNALUnitPacket{simplePacket, diffPacket}) + assert.Nil(t, err) + + testPACI(aggregation, false) + + simplePacket.donl = &testDONL + diffPacket.donl = &testDONL2 + + aggregationWithDONL, err := newH265AggregationPacket([]h265SingleNALUnitPacket{simplePacket, diffPacket}) + assert.Nil(t, err) + + testPACI(aggregationWithDONL, true) } -func TestH265_Packet_Real(t *testing.T) { - // Tests decoding of real H265 payloads extracted from a Wireshark dump. +func TestH265_SingleRoundtrip(t *testing.T) { + testSingle := func(expected h265SingleNALUnitPacket) { + packet := expected.serialize(make([]byte, 0)) + parsed, err := parseH265SingleNalUnitPacket(packet, expected.donl != nil) + assert.Nil(t, err) - // nolint: lll - 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", + assert.Equal(t, expected, *parsed) } - for _, cur := range tt { - pck := &H265Packet{} - _, err := pck.Unmarshal([]byte(cur)) - assert.True(t, err == nil || errors.Is(err, errExpectFragmentationStartUnit)) + simplePacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 1, false), + nil, + []byte{0x00, 0x01, 0x02, 0x03}, + } + diffPacket := h265SingleNALUnitPacket{ + newH265NALUHeader(0b1000000, 0b00010000), + nil, + []byte{0x03, 0x02, 0x01, 0x00, 0x12}, + } + + testSingle(simplePacket) + testSingle(diffPacket) + + withDonlCount := uint16(4) + withDonl := make([]h265SingleNALUnitPacket, withDonlCount) + for i := uint16(0); i < withDonlCount; i++ { + donlVal := i + withDonl[i] = h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 1, false), + &donlVal, + []byte{0x00, 0x01, 0x02, 0x03}, + } + testSingle(withDonl[i]) } } -func TestH265Payloader_Payload(t *testing.T) { - tt := []struct { - Name string - Data []byte - MTU uint16 - AddDONL bool - SkipAggregation bool - ExpectedLen int - ExpectedData *[][]byte - Msg string - }{ - { - Name: "Positive MTU, nil payload", - MTU: 1, - Data: nil, - ExpectedLen: 0, - Msg: "Generated payload must be empty", - }, - { - Name: "Positive MTU, empty NAL", - MTU: 1, - Data: []byte{}, - ExpectedLen: 0, - Msg: "Generated payload should be empty", - }, - { - Name: "Zero MTU, start code", - MTU: 0, - Data: []byte{0x00, 0x00, 0x01}, - ExpectedLen: 0, - Msg: "Generated payload should be empty", - }, - { - Name: "Positive MTU, 1 byte payload", - MTU: 1, - Data: []byte{0x90}, - ExpectedLen: 0, - Msg: "Generated payload should be empty. H.265 nal unit too small", - }, - { - Name: "MTU:1, 2 byte payload", - MTU: 1, - Data: []byte{0x46, 0x01}, - ExpectedLen: 0, - Msg: "Generated payload should be empty. H.265 nal unit too small", - }, - { - Name: "MTU:2, 2 byte payload.", - MTU: 2, - Data: []byte{0x46, 0x01}, - ExpectedLen: 0, - Msg: "Generated payload should be empty. min MTU is 4", - }, - { - Name: "MTU:4, 2 byte payload.", - MTU: 4, - Data: []byte{0x46, 0x01}, - ExpectedData: &[][]byte{{0x46, 0x01}}, - Msg: "AUD packetization failed", - }, - { - Name: "Negative MTU, small payload", - MTU: 0, - Data: []byte{0x90, 0x90, 0x90}, - ExpectedLen: 0, - }, - { - Name: "Negative MTU, small payload", - MTU: 0, - Data: []byte{0x90, 0x90, 0x90}, - ExpectedLen: 0, - }, - { - Name: "Negative MTU, small payload", - MTU: 1, - Data: []byte{0x90, 0x90, 0x90}, - ExpectedLen: 0, - }, - { - Name: "Negative MTU, small payload", - MTU: 5, - Data: []byte{0x90, 0x90, 0x90}, - ExpectedData: &[][]byte{{0x90, 0x90, 0x90}}, - }, - { - Name: "Large payload", - MTU: 5, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0x09, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, - }, - ExpectedData: &[][]byte{ - {0x62, 0x01, 0x80, 0x02, 0x03}, - {0x62, 0x01, 0x00, 0x04, 0x05}, - {0x62, 0x01, 0x00, 0x06, 0x07}, - {0x62, 0x01, 0x00, 0x08, 0x09}, - {0x62, 0x01, 0x00, 0x10, 0x11}, - {0x62, 0x01, 0x00, 0x12, 0x13}, - {0x62, 0x01, 0x40, 0x14, 0x15}, - }, - Msg: "Large payload split across fragmentation Packets", - }, - { - Name: "Short MTU, multiple NALUs flushed in single packet", - MTU: 5, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - }, - ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}}, - Msg: "multiple Single NALUs packetization should succeed", - }, - { - Name: "Enough MUT, multiple NALUs create Signle Packet", - MTU: 10, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - }, - ExpectedData: &[][]byte{{0x60, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x03}}, - Msg: "Aggregation packetization should succeed", - }, - { - Name: "Enough MUT, multiple NALUs flushed two Packets, don't aggregate", - MTU: 5, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x01, 0x04, 0x05, - }, - ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}, {0x04, 0x05}}, - Msg: "multiple Single NALUs packetization should succeed", - }, - { - Name: "Enough MUT, multiple NALUs flushed two Packets, aggregate", - MTU: 15, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x01, 0x04, 0x05, - }, - ExpectedData: &[][]byte{{0x60, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x03, 0x00, 0x02, 0x04, 0x05}}, - Msg: "Aggregation packetization should succeed", - }, - // Add DONL = true - { - Name: "DONL, invalid MTU:1", - MTU: 1, - Data: []byte{0x01}, - AddDONL: true, - ExpectedLen: 0, - Msg: "Generated payload must be empty", - }, - { - Name: "DONL MTU:4, 2 byte payload.", - MTU: 4, - Data: []byte{0x00, 0x01}, - AddDONL: true, - ExpectedLen: 0, - Msg: "Generated payload must be empty", - }, - { - Name: "DONL single NALU minimum payload", - MTU: 6, - Data: []byte{0x00, 0x01}, - AddDONL: true, - ExpectedData: &[][]byte{{0x00, 0x01, 0x00, 0x00}}, - Msg: "single NALU should be packetized", - }, - { - Name: "DONL multiple NALU", - MTU: 6, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x01, 0x04, 0x05, - }, - AddDONL: true, - ExpectedData: &[][]byte{ - {0x00, 0x01, 0x00, 0x00}, - {0x02, 0x03, 0x00, 0x01}, - {0x04, 0x05, 0x00, 0x02}, - }, - Msg: "DONL should be incremented", - }, - { - Name: "DONL aggregation minimum payload", - MTU: 18, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x01, 0x04, 0x05, - }, - AddDONL: true, - ExpectedData: &[][]byte{ - { - 0x60, 0x01, // NALU Header + Layer ID + TID - 0x00, 0x00, // DONL - 0x00, 0x02, 0x00, 0x01, - 0x00, // DONL - 0x00, 0x02, 0x02, 0x03, - 0x01, // DONL - 0x00, 0x02, 0x04, 0x05, - }, - }, - Msg: "DONL Aggregation packetization should succeed", - }, - { - Name: "DONL Large payload", - MTU: 7, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0x09, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, - }, - AddDONL: true, - ExpectedData: &[][]byte{ - {0x62, 0x01, 0x80, 0x00, 0x00, 0x02, 0x03}, - {0x62, 0x01, 0x00, 0x00, 0x01, 0x04, 0x05}, - {0x62, 0x01, 0x00, 0x00, 0x02, 0x06, 0x07}, - {0x62, 0x01, 0x00, 0x00, 0x03, 0x08, 0x09}, - {0x62, 0x01, 0x00, 0x00, 0x04, 0x10, 0x11}, - {0x62, 0x01, 0x00, 0x00, 0x05, 0x12, 0x13}, - {0x62, 0x01, 0x40, 0x00, 0x06, 0x14, 0x15}, - }, - Msg: "DONL Large payload split across fragmentation Packets", - }, - // SkipAggregation = true - { - Name: "SkipAggregation Enough MUT, multiple NALUs", - MTU: 4, - Data: []byte{ - 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x01, 0x04, 0x05, - }, - SkipAggregation: true, - ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}, {0x04, 0x05}}, - Msg: "Aggregation packetization should be skipped", - }, +func TestH265Packetizer_Single(t *testing.T) { + packetizer := h265Packetizer{} + + // type 1, 8 payload length NALU + basicPacket := make([]byte, 0) + basicPacket = append(basicPacket, annexbNALUStartCode...) + header := createTestH265Header(1, 0, 0, false) + basicPacket = binary.BigEndian.AppendUint16(basicPacket, uint16(header)) + payload := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + basicPacket = append(basicPacket, payload...) + + packets := packetizer.Payload(100, basicPacket) + assert.Equal(t, 1, len(packets), "Expected only 1 NALU to be generated") + assert.Equal(t, uint16(header), binary.BigEndian.Uint16(packets[0][0:2]), "Expected headers to match") + assert.Equal(t, payload, packets[0][2:], "Expected payloads to match") +} + +func TestH265Packetizer_Aggregated(t *testing.T) { + packetizer := h265Packetizer{} + // type 0, 8 payload length + basicPacket := make([]byte, 0) + basicPacket = append(basicPacket, annexbNALUStartCode...) + header := createTestH265Header(1, 0, 0, false) + basicPacket = binary.BigEndian.AppendUint16(basicPacket, uint16(header)) + payload := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + basicPacket = append(basicPacket, payload...) + + two := make([]byte, len(basicPacket)*2) + copy(two, basicPacket) + copy(two[len(basicPacket):], basicPacket) + + packets := packetizer.Payload(100, two) + + assert.Equal(t, 1, len(packets), "Expected only 1 NALU to be generated") + aggregated := packets[0] + parsedHeader := H265NALUHeader(binary.BigEndian.Uint16(aggregated[0:2])) + + assert.Equal(t, h265NaluAggregationPacketType, int(parsedHeader.Type()), "NALU header should be type 28") + + assert.Equal( + t, + h265NaluHeaderSize+len(payload), + int(binary.BigEndian.Uint16(aggregated[2:4])), + "Expected length to match", + ) + assert.Equal(t, uint16(header), binary.BigEndian.Uint16(aggregated[4:6]), "Expected headers to match") + assert.Equal(t, payload, aggregated[6:14], "Expected payloads to match") + + assert.Equal( + t, + h265NaluHeaderSize+len(payload), + int(binary.BigEndian.Uint16(aggregated[14:16])), + "Expected length to match", + ) + assert.Equal(t, uint16(header), binary.BigEndian.Uint16(aggregated[16:18]), "Expected headers to match") + assert.Equal(t, payload, aggregated[18:], "Expected payloads to match") +} + +func TestH265Packetizer_Fragmented(t *testing.T) { + initSequence := []byte{0x00, 0x00, 0x00, 0x01, 0x00} + + packetizer := h265Packetizer{} + // type 0, 50 payload length + bigPacket := make([]byte, 0) + bigPacket = append(bigPacket, initSequence...) + header := createTestH265Header(1, 0, 0, false) + + bigPacket = binary.BigEndian.AppendUint16(bigPacket, uint16(header)) + + payload := make([]byte, 0) + for i := 0; i < 50; i++ { + payload = append(payload, 0xff) } + bigPacket = append(bigPacket, payload...) - for _, cur := range tt { - t.Run(cur.Name, func(t *testing.T) { - pck := H265Payloader{AddDONL: cur.AddDONL, SkipAggregation: cur.SkipAggregation} - res := pck.Payload(cur.MTU, cur.Data) - if cur.ExpectedData != nil { - assert.Equal(t, *cur.ExpectedData, res) - } else { - assert.Len(t, res, cur.ExpectedLen) + packets := packetizer.Payload(50, bigPacket) + + assert.Equal(t, 2, len(packets), "Expected 2 NALUs to be generated") + parsedHeader := H265NALUHeader(binary.BigEndian.Uint16(packets[0][0:2])) + + assert.Equal(t, h265NaluFragmentationUnitType, int(parsedHeader.Type()), "NALU header should be type 28") + assert.True(t, H265FragmentationUnitHeader(packets[0][2]).S(), "First FU header should be S") + assert.True(t, H265FragmentationUnitHeader(packets[1][2]).E(), "Second FU header should be E") +} + +func TestH265Depacketizer_Roundtrip(t *testing.T) { + testDepacketizer := func(packets [][]byte, expected []h265SingleNALUnitPacket, withDonl bool) { + depacketizer := H265Depacketizer{ + hasDonl: withDonl, + } + output := make([]h265SingleNALUnitPacket, 0) + for _, packet := range packets { + p, err := depacketizer.Unmarshal(packet) + assert.Nil(t, err) + + if p == nil { + continue } + + emitNalus(p, func(b []byte) { + parsed, err := parseH265SingleNalUnitPacket(b, false) + assert.Nil(t, err) + if err != nil { + return + } + output = append(output, *parsed) + }) + } + same := slices.EqualFunc(expected, output, func(a h265SingleNALUnitPacket, b h265SingleNALUnitPacket) bool { + return slices.Equal(a.payload, b.payload) && + a.payloadHeader == b.payloadHeader }) + assert.True(t, same) } -} -func TestH265Payloader_Real(t *testing.T) { - // curl -LO "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h265/1080/Big_Buck_Bunny_1080_10s_1MB.mp4" - // ffmpeg -i Big_Buck_Bunny_1080_10s_1MB.mp4 -c:v copy Big_Buck_Bunny_1080_10s_1MB.h265 - // hexdump -v -e '1/1 "0x%02x, "' Big_Buck_Bunny_1080_10s_1MB.h265 > aaa + testDonl := uint16(100) + testDonl2 := uint16(101) - // nolint: lll - payload := []byte{ - 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x95, 0x98, 0x09, - 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x10, 0xe5, 0x96, 0x56, 0x69, 0x24, 0xca, 0xf0, 0x10, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01, 0xe0, 0x80, - 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40, - 0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x05, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x71, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4, 0xfe, 0x7f, 0xc2, 0xfc, 0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x31, 0x35, 0x31, 0x29, 0x20, 0x2d, 0x20, 0x32, 0x2e, 0x36, 0x2b, 0x34, 0x39, 0x2d, 0x37, 0x32, 0x31, 0x39, 0x33, 0x37, 0x36, 0x64, 0x65, 0x34, 0x32, 0x61, 0x3a, 0x5b, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x5d, 0x5b, 0x47, 0x43, 0x43, 0x20, 0x37, 0x2e, 0x33, 0x2e, 0x30, 0x5d, 0x5b, 0x36, 0x34, 0x20, 0x62, 0x69, 0x74, 0x5d, 0x20, 0x38, 0x62, 0x69, 0x74, 0x2b, 0x31, 0x30, 0x62, 0x69, 0x74, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x35, 0x2f, 0x48, 0x45, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x32, 0x30, 0x31, 0x38, 0x20, 0x28, 0x63, 0x29, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x32, 0x36, 0x35, 0x2e, 0x6f, 0x72, 0x67, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x70, 0x75, 0x69, 0x64, 0x3d, 0x31, 0x30, 0x35, 0x30, 0x31, 0x31, 0x31, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x33, 0x20, 0x6e, 0x75, 0x6d, 0x61, 0x2d, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x3d, 0x38, 0x20, 0x77, 0x70, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x73, 0x6e, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x20, 0x6c, 0x6f, 0x67, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x62, 0x69, 0x74, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x38, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x63, 0x73, 0x70, 0x3d, 0x31, 0x20, 0x66, 0x70, 0x73, 0x3d, 0x33, 0x30, 0x2f, 0x31, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x72, 0x65, 0x73, 0x3d, 0x31, 0x39, 0x32, 0x30, 0x78, 0x31, 0x30, 0x38, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x3d, 0x30, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x63, 0x3d, 0x30, 0x20, 0x68, 0x69, 0x67, 0x68, 0x2d, 0x74, 0x69, 0x65, 0x72, 0x3d, 0x31, 0x20, 0x75, 0x68, 0x64, 0x2d, 0x62, 0x64, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x6e, 0x65, 0x78, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x75, 0x64, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x72, 0x64, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x70, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x67, 0x6f, 0x70, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x62, 0x2d, 0x61, 0x64, 0x61, 0x70, 0x74, 0x3d, 0x32, 0x20, 0x62, 0x2d, 0x70, 0x79, 0x72, 0x61, 0x6d, 0x69, 0x64, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x32, 0x35, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x72, 0x61, 0x64, 0x6c, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x20, 0x63, 0x74, 0x75, 0x3d, 0x36, 0x34, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x63, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x38, 0x20, 0x72, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6d, 0x70, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x74, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x74, 0x75, 0x3d, 0x30, 0x20, 0x72, 0x64, 0x6f, 0x71, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x2d, 0x72, 0x64, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x2d, 0x72, 0x64, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x68, 0x69, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x73, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x6d, 0x65, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x35, 0x37, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6d, 0x76, 0x70, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x2d, 0x73, 0x72, 0x63, 0x2d, 0x70, 0x69, 0x63, 0x73, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x30, 0x3a, 0x30, 0x20, 0x73, 0x61, 0x6f, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x61, 0x6f, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x72, 0x64, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x75, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x62, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x72, 0x64, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x64, 0x70, 0x65, 0x6e, 0x61, 0x6c, 0x74, 0x79, 0x3d, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x3d, 0x32, 0x2e, 0x30, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x6f, 0x71, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x64, 0x2d, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x63, 0x62, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x63, 0x72, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x38, 0x38, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3d, 0x30, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x69, 0x70, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x70, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x33, 0x30, 0x20, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x20, 0x61, 0x71, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x63, 0x75, 0x74, 0x72, 0x65, 0x65, 0x20, 0x7a, 0x6f, 0x6e, 0x65, 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x2d, 0x63, 0x62, 0x72, 0x20, 0x71, 0x67, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x63, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x2d, 0x76, 0x62, 0x76, 0x20, 0x73, 0x61, 0x72, 0x3d, 0x31, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x3d, 0x30, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x3d, 0x35, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x70, 0x72, 0x69, 0x6d, 0x3d, 0x32, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x3d, 0x32, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x6c, 0x6f, 0x63, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x63, 0x6c, 0x6c, 0x3d, 0x30, 0x2c, 0x30, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x32, 0x35, 0x35, 0x20, 0x6c, 0x6f, 0x67, 0x32, 0x2d, 0x6d, 0x61, 0x78, 0x2d, 0x70, 0x6f, 0x63, 0x2d, 0x6c, 0x73, 0x62, 0x3d, 0x38, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x68, 0x72, 0x64, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x71, 0x70, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x70, 0x73, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x2e, 0x30, 0x35, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x63, 0x75, 0x2d, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2d, 0x71, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x64, 0x68, 0x64, 0x72, 0x31, 0x30, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x35, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2d, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x73, 0x61, 0x6f, 0x20, 0x63, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x77, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x64, 0x63, 0x74, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2d, 0x70, 0x69, 0x63, 0x3d, 0x31, 0x80, + // Single NAL + + basicPacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 0, false), + nil, + []byte{0xff, 0xff, 0xff}, } - pck := H265Payloader{} - res := pck.Payload(1400, payload) - // 1. Aggregating three NALUs into a single payload - // 2. Fragmented packets divided by MTU=1400 - // 3. Remaining fragment packets split by MTU - assert.Len(t, res, 3, "Generated payload should be 3") -} -func uint8ptr(v uint8) *uint8 { - return &v + testDepacketizer([][]byte{basicPacket.serialize(make([]byte, 0))}, []h265SingleNALUnitPacket{basicPacket}, false) + + // with DONL + + basicPacket.donl = &testDonl + + testDepacketizer([][]byte{basicPacket.serialize(make([]byte, 0))}, []h265SingleNALUnitPacket{basicPacket}, true) + + // Multiple NALs aggregated + + firstPacket := h265SingleNALUnitPacket{ + createTestH265Header(0, 0, 0, false), + nil, + []byte{0xff, 0xff, 0xff}, + } + + secondPacket := h265SingleNALUnitPacket{ + createTestH265Header(1, 2, 3, false), + nil, + []byte{0x67, 0x67, 0x67}, + } + + aggregation, err := newH265AggregationPacket([]h265SingleNALUnitPacket{firstPacket, secondPacket}) + assert.Nil(t, err) + aggregationPacketized := aggregation.serialize(make([]byte, 0)) + + testDepacketizer([][]byte{aggregationPacketized}, []h265SingleNALUnitPacket{firstPacket, secondPacket}, false) + + // with DONL + + firstPacket.donl = &testDonl + secondPacket.donl = &testDonl2 + + donlAggregation, err := newH265AggregationPacket([]h265SingleNALUnitPacket{firstPacket, secondPacket}) + assert.Nil(t, err) + donlAggregationPacketized := donlAggregation.serialize(make([]byte, 0)) + + testDepacketizer([][]byte{donlAggregationPacketized}, []h265SingleNALUnitPacket{firstPacket, secondPacket}, true) + + // Large NAL that gets fragmented + + largePacket := &h265SingleNALUnitPacket{ + createTestH265Header(1, 1, 1, false), + nil, + make([]byte, 0), + } + for i := 0; i < 512; i++ { + largePacket.payload = append(largePacket.payload, uint8(i)) // nolint:gosec + } + + fragments, err := newH265FragmentationPackets(100, largePacket) + assert.Nil(t, err) + + fragmentsPacketized := make([][]byte, 0) + + for _, f := range fragments { + fragmentsPacketized = append(fragmentsPacketized, f.serialize(make([]byte, 0))) + } + + testDepacketizer(fragmentsPacketized, []h265SingleNALUnitPacket{*largePacket}, false) + + // with DONL + + largePacket.donl = &testDonl + + donlFragments, err := newH265FragmentationPackets(100, largePacket) + assert.Nil(t, err) + + donlFragmentsPacketized := make([][]byte, 0) + + for _, f := range donlFragments { + donlFragmentsPacketized = append(donlFragmentsPacketized, f.serialize(make([]byte, 0))) + } + + testDepacketizer(donlFragmentsPacketized, []h265SingleNALUnitPacket{*largePacket}, true) + + testPACIPayload := h265SingleNALUnitPacket{ + createTestH265Header(16, 1, 1, false), + nil, + []byte{0xff, 0xff, 0xff}, + } + + paci := &H265PACIPacket{ + payloadHeader: createTestH265Header(h265NaluPACIPacketType, 1, 1, false), + paciHeaderFields: paciHeaderFields(uint16(16) << 9), + phes: []byte{}, + payload: &testPACIPayload, + } + + testDepacketizer([][]byte{paci.serialize(make([]byte, 0))}, []h265SingleNALUnitPacket{testPACIPayload}, false) + + testPACIPayload.donl = &testDonl + + testDepacketizer([][]byte{paci.serialize(make([]byte, 0))}, []h265SingleNALUnitPacket{testPACIPayload}, true) } func uint16ptr(v uint16) *uint16 {