From dd05d229f6fb79743c9c5b18bdf450ee3c36530b Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 9 Feb 2022 10:38:38 -0500 Subject: [PATCH 01/10] Add H265 payloader and depacketizer This change completes the H265 implementation. --- codecs/h265_packet.go | 329 +++++++++++++++++++++++++++++++++++-- codecs/h265_packet_test.go | 2 +- 2 files changed, 319 insertions(+), 12 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 2a194fd..076f631 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -7,6 +7,8 @@ import ( "encoding/binary" "errors" "fmt" + "math" + "sort" ) // @@ -733,22 +735,60 @@ var ( // Packet implementation // +type donKeyedNALU struct { + DON int + NALU []byte +} + // H265Packet represents a H265 packet, stored in the payload of an RTP packet. type H265Packet struct { - packet isH265Packet - mightNeedDONL bool + packet isH265Packet + maxDONDiff uint16 + depackBufNALUs uint16 + + prevDON *uint16 + prevAbsDON *int + + naluBuffer []donKeyedNALU + fuBuffer []byte 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 toAbsDON(don uint16, prevDON *uint16, prevAbsDON *int) int { + if prevDON == nil || prevAbsDON == nil { + return int(don) + } + if don == *prevDON { + return *prevAbsDON + } + if don > *prevDON && don-*prevDON < 32768 { + return *prevAbsDON + int(don-*prevDON) + } + if don < *prevDON && *prevDON-don >= 32768 { + return *prevAbsDON + 65536 + int(*prevDON-don) + } + if don > *prevDON && don-*prevDON >= 32768 { + return *prevAbsDON - (int(*prevDON) + 65536 - int(don)) + } + if don < *prevDON && *prevDON-don < 32768 { + return *prevAbsDON - int(*prevDON-don) + } + return 0 +} + +// WithMaxDONDiff sets the maximum difference between DON values before being emitted. +func (p *H265Packet) WithMaxDONDiff(value uint16) { + p.maxDONDiff = value +} + +// WithDepackBufNALUs sets the maximum number of NALUs to be buffered. +func (p *H265Packet) WithDepackBufNALUs(value uint16) { + p.depackBufNALUs = value } // Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon -func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { +func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocognit if payload == nil { return nil, errNilPacket } else if len(payload) <= h265NaluHeaderSize { @@ -771,7 +811,7 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { case payloadHeader.IsFragmentationUnit(): decoded := &H265FragmentationUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) + decoded.WithDONL(p.maxDONDiff > 0) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err @@ -779,9 +819,32 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { p.packet = decoded + if decoded.FuHeader().S() { + // push the nalu header + header := decoded.PayloadHeader() + p.fuBuffer = []byte{ + (uint8(header>>8) & 0b10000001) | (decoded.FuHeader().FuType() << 1), + uint8(header), + } + } + p.fuBuffer = append(p.fuBuffer, decoded.Payload()...) + if decoded.FuHeader().E() { + var absDON int + if p.maxDONDiff > 0 { + absDON = toAbsDON(*decoded.DONL(), p.prevDON, p.prevAbsDON) + p.prevDON = decoded.DONL() + p.prevAbsDON = &absDON + } + p.naluBuffer = append(p.naluBuffer, donKeyedNALU{ + DON: absDON, + NALU: p.fuBuffer, + }) + p.fuBuffer = nil + } + case payloadHeader.IsAggregationPacket(): decoded := &H265AggregationPacket{} - decoded.WithDONL(p.mightNeedDONL) + decoded.WithDONL(p.maxDONDiff > 0) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err @@ -789,18 +852,80 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { p.packet = decoded + var absDON int + if p.maxDONDiff > 0 { + absDON = toAbsDON(*decoded.FirstUnit().DONL(), p.prevDON, p.prevAbsDON) + p.prevDON = decoded.FirstUnit().DONL() + p.prevAbsDON = &absDON + } + p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: decoded.FirstUnit().NalUnit()}) + for _, unit := range decoded.OtherUnits() { + if p.maxDONDiff > 0 { + donl := uint16(*unit.DOND()) + 1 + *decoded.FirstUnit().DONL() + absDON = toAbsDON(donl, p.prevDON, p.prevAbsDON) + p.prevDON = &donl + p.prevAbsDON = &absDON + } + p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: unit.NalUnit()}) + } + default: decoded := &H265SingleNALUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) + decoded.WithDONL(p.maxDONDiff > 0) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } p.packet = decoded + + buf := make([]byte, 2+len(decoded.payload)) + binary.BigEndian.PutUint16(buf[0:2], uint16(decoded.payloadHeader)) + copy(buf[2:], decoded.payload) + + var absDON int + if p.maxDONDiff > 0 { + absDON = toAbsDON(*decoded.DONL(), p.prevDON, p.prevAbsDON) + p.prevDON = decoded.DONL() + p.prevAbsDON = &absDON + } + p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: buf}) } - return nil, nil + buf := []byte{} + if p.maxDONDiff > 0 { + // https://datatracker.ietf.org/doc/html/rfc7798#section-6 + // sort by AbsDON + sort.Slice(p.naluBuffer, func(i, j int) bool { + return p.naluBuffer[i].DON < p.naluBuffer[j].DON + }) + // find the max DONL value + var maxDONL int + for _, nalu := range p.naluBuffer { + if nalu.DON > maxDONL { + maxDONL = nalu.DON + } + } + minDONL := maxDONL - int(p.maxDONDiff) + // merge all NALUs while condition A or condition B are true + for len(p.naluBuffer) > 0 && (p.naluBuffer[0].DON < minDONL || len(p.naluBuffer) > int(p.depackBufNALUs)) { + // nolint + // TODO: this is not actually correct following B.2.2, not all NALUs have a 4-byte start code. + buf = append(buf, annexbNALUStartCode...) + buf = append(buf, p.naluBuffer[0].NALU...) + p.naluBuffer = p.naluBuffer[1:] + } + } else { + // return the nalu buffer joined together + for _, val := range p.naluBuffer { + // nolint + // TODO: this is not actually correct following B.2.2, not all NALUs have a 4-byte start code. + buf = append(buf, annexbNALUStartCode...) + buf = append(buf, val.NALU...) + } + p.naluBuffer = nil + } + return buf, nil } // Packet returns the populated packet. @@ -826,3 +951,185 @@ func (*H265Packet) IsPartitionHead(payload []byte) bool { return true } + +// H265Payloader payloads H265 packets +type H265Payloader struct { + AddDONL bool + SkipAggregation bool + donl uint16 +} + +// Payload fragments a H265 packet across one or more byte arrays +func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: gocognit + var payloads [][]byte + if len(payload) == 0 { + return payloads + } + + bufferedNALUs := make([][]byte, 0) + aggregationBufferSize := 0 + + flushBufferedNals := func() { + if len(bufferedNALUs) == 0 { + return + } + if len(bufferedNALUs) == 1 { + // emit this as a single NALU packet + nalu := bufferedNALUs[0] + + if p.AddDONL { + buf := make([]byte, len(nalu)+2) + + // copy the NALU header to the payload header + copy(buf[0:h265NaluHeaderSize], nalu[0:h265NaluHeaderSize]) + + // copy the DONL into the header + binary.BigEndian.PutUint16(buf[h265NaluHeaderSize:h265NaluHeaderSize+2], p.donl) + + // write the payload + copy(buf[h265NaluHeaderSize+2:], nalu[h265NaluHeaderSize:]) + + p.donl++ + + payloads = append(payloads, buf) + } else { + // write the nalu directly to the payload + payloads = append(payloads, nalu) + } + } else { + // construct an aggregation packet + aggregationPacketSize := aggregationBufferSize + 2 + buf := make([]byte, aggregationPacketSize) + + layerID := uint8(math.MaxUint8) + tid := uint8(math.MaxUint8) + for _, nalu := range bufferedNALUs { + header := newH265NALUHeader(nalu[0], nalu[1]) + headerLayerID := header.LayerID() + headerTID := header.TID() + if headerLayerID < layerID { + layerID = headerLayerID + } + if headerTID < tid { + tid = headerTID + } + } + + binary.BigEndian.PutUint16(buf[0:2], (uint16(h265NaluAggregationPacketType)<<9)|(uint16(layerID)<<3)|uint16(tid)) + + index := 2 + for i, nalu := range bufferedNALUs { + if p.AddDONL { + if i == 0 { + binary.BigEndian.PutUint16(buf[index:index+2], p.donl) + index += 2 + } else { + buf[index] = byte(i - 1) + index++ + } + } + binary.BigEndian.PutUint16(buf[index:index+2], uint16(len(nalu))) + index += 2 + index += copy(buf[index:], nalu) + } + payloads = append(payloads, buf) + } + // clear the buffered NALUs + bufferedNALUs = make([][]byte, 0) + aggregationBufferSize = 0 + } + + emitNalus(payload, func(nalu []byte) { + if len(nalu) == 0 { + return + } + + if len(nalu) <= int(mtu) { + // this nalu fits into a single packet, either it can be emitted as + // a single nalu or appended to the previous aggregation packet + + marginalAggregationSize := len(nalu) + 2 + if p.AddDONL { + marginalAggregationSize++ + } + + if aggregationBufferSize+marginalAggregationSize > int(mtu) { + flushBufferedNals() + } + bufferedNALUs = append(bufferedNALUs, nalu) + aggregationBufferSize += marginalAggregationSize + if p.SkipAggregation { + // emit this immediately. + flushBufferedNals() + } + } else { + // if this nalu doesn't fit in the current mtu, it needs to be fragmented + fuPacketHeaderSize := h265FragmentationUnitHeaderSize + 2 /* payload header size */ + if p.AddDONL { + fuPacketHeaderSize += 2 + } + + // then, fragment the nalu + maxFUPayloadSize := int(mtu) - fuPacketHeaderSize + + naluHeader := newH265NALUHeader(nalu[0], nalu[1]) + + // the nalu header is omitted from the fragmentation packet payload + nalu = nalu[h265NaluHeaderSize:] + + if maxFUPayloadSize == 0 || len(nalu) == 0 { + return + } + + // flush any buffered aggregation packets. + flushBufferedNals() + + fullNALUSize := len(nalu) + for len(nalu) > 0 { + curentFUPayloadSize := len(nalu) + if curentFUPayloadSize > maxFUPayloadSize { + curentFUPayloadSize = maxFUPayloadSize + } + + out := make([]byte, fuPacketHeaderSize+curentFUPayloadSize) + + // write the payload header + binary.BigEndian.PutUint16(out[0:2], uint16(naluHeader)) + out[0] = (out[0] & 0b10000001) | h265NaluFragmentationUnitType<<1 + + // write the fragment header + out[2] = byte(H265FragmentationUnitHeader(naluHeader.Type())) + if len(nalu) == fullNALUSize { + // Set start bit + out[2] |= 1 << 7 + } else if len(nalu)-curentFUPayloadSize == 0 { + // Set end bit + out[2] |= 1 << 6 + } + + if p.AddDONL { + // write the DONL header + binary.BigEndian.PutUint16(out[3:5], p.donl) + + p.donl++ + + // copy the fragment payload + copy(out[5:], nalu[0:curentFUPayloadSize]) + } else { + // copy the fragment payload + copy(out[3:], nalu[0:curentFUPayloadSize]) + } + + // append the fragment to the payload + payloads = append(payloads, out) + + // advance the nalu data pointer + nalu = nalu[curentFUPayloadSize:] + } + } + }) + + flushBufferedNals() + + return payloads +} diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 1c5f96a..0279d36 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -794,7 +794,7 @@ func TestH265_Packet(t *testing.T) { for _, cur := range tt { pck := &H265Packet{} if cur.WithDONL { - pck.WithDONL(true) + pck.WithMaxDONDiff(1) } _, err := pck.Unmarshal(cur.Raw) From ca36e35220dcfbfee7974ee496c95b8bf10954d8 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 4 Jan 2025 15:16:25 +0900 Subject: [PATCH 02/10] Fix segv --- codecs/h265_packet.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 076f631..02e4827 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -962,7 +962,7 @@ type H265Payloader struct { // Payload fragments a H265 packet across one or more byte arrays func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: gocognit var payloads [][]byte - if len(payload) == 0 { + if len(payload) == 0 || mtu == 0 { return payloads } @@ -1040,7 +1040,8 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: } emitNalus(payload, func(nalu []byte) { - if len(nalu) == 0 { + if len(nalu) < 2 { + // NALU header is 2 bytes return } @@ -1077,7 +1078,7 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: // the nalu header is omitted from the fragmentation packet payload nalu = nalu[h265NaluHeaderSize:] - if maxFUPayloadSize == 0 || len(nalu) == 0 { + if maxFUPayloadSize <= 0 || len(nalu) == 0 { return } From 73c0f7ee8e2307e22a290834797724a983a0f1ac Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 4 Jan 2025 16:05:22 +0900 Subject: [PATCH 03/10] Fix mut exceedance in aggregation packet --- codecs/h265_packet.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 02e4827..c5b8c88 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -998,7 +998,7 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: } } else { // construct an aggregation packet - aggregationPacketSize := aggregationBufferSize + 2 + aggregationPacketSize := aggregationBufferSize buf := make([]byte, aggregationPacketSize) layerID := uint8(math.MaxUint8) @@ -1039,23 +1039,32 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: aggregationBufferSize = 0 } + calcMarginalAggregationSize := func(nalu []byte) int { + marginalAggregationSize := len(nalu) + 2 // +2 is NALU size Field size + if len(bufferedNALUs) == 1 { + marginalAggregationSize = len(nalu) + 4 // +4 are Aggregation header + NALU size Field size + } + if p.AddDONL { + marginalAggregationSize++ + } + return marginalAggregationSize + } + emitNalus(payload, func(nalu []byte) { if len(nalu) < 2 { // NALU header is 2 bytes return } - if len(nalu) <= int(mtu) { + naluLen := len(nalu) + 2 + if naluLen <= int(mtu) { // this nalu fits into a single packet, either it can be emitted as // a single nalu or appended to the previous aggregation packet - - marginalAggregationSize := len(nalu) + 2 - if p.AddDONL { - marginalAggregationSize++ - } + marginalAggregationSize := calcMarginalAggregationSize(nalu) if aggregationBufferSize+marginalAggregationSize > int(mtu) { flushBufferedNals() + marginalAggregationSize = calcMarginalAggregationSize(nalu) } bufferedNALUs = append(bufferedNALUs, nalu) aggregationBufferSize += marginalAggregationSize From 4d72a25217852aec0000e8f9c3af3dc644a624cd Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 4 Jan 2025 16:06:36 +0900 Subject: [PATCH 04/10] Corrected donl margin calculations DONL is increased by 2 the first time and 1 thereafter. --- codecs/h265_packet.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index c5b8c88..50a2a3e 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -1045,7 +1045,11 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: marginalAggregationSize = len(nalu) + 4 // +4 are Aggregation header + NALU size Field size } if p.AddDONL { - marginalAggregationSize++ + if len(bufferedNALUs) == 0 { + marginalAggregationSize += 2 + } else { + marginalAggregationSize++ + } } return marginalAggregationSize } @@ -1057,6 +1061,9 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: } naluLen := len(nalu) + 2 + if p.AddDONL { + naluLen += 2 + } if naluLen <= int(mtu) { // this nalu fits into a single packet, either it can be emitted as // a single nalu or appended to the previous aggregation packet From 71b84ade43185ecf6aaa8cc2c96eb922e97aa6f9 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 4 Jan 2025 16:08:05 +0900 Subject: [PATCH 05/10] Add h.265 payloader test --- codecs/h265_packet_test.go | 284 +++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 0279d36..b3c2079 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -871,6 +871,290 @@ func TestH265_Packet_Real(t *testing.T) { } } +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", + }, + } + + 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 { + if !reflect.DeepEqual(res, *cur.ExpectedData) { + t.Fatal(cur.Msg) + } + } else { + if len(res) != cur.ExpectedLen { + t.Fatal(cur.Msg) + } + } + }) + } +} + +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 + 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) + if len(res) != 3 { + // 1. Aggregating three NALUs into a single payload + // 2. Fragmented packets divided by MTU=1400 + // 3. Remaining fragment packets split by MTU + t.Fatal("Generated payload should be 3") + } +} + func uint8ptr(v uint8) *uint8 { return &v } From c525417f0fc97c4dc1818251ec30b57c6cd25658 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 11 Jan 2025 05:07:38 +0900 Subject: [PATCH 06/10] Fix linter --- codecs/h265_packet.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 50a2a3e..00e247e 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -774,6 +774,7 @@ func toAbsDON(don uint16, prevDON *uint16, prevAbsDON *int) int { if don < *prevDON && *prevDON-don < 32768 { return *prevAbsDON - int(*prevDON-don) } + return 0 } @@ -925,6 +926,7 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocog } p.naluBuffer = nil } + return buf, nil } @@ -952,14 +954,14 @@ func (*H265Packet) IsPartitionHead(payload []byte) bool { return true } -// H265Payloader payloads H265 packets +// H265Payloader payloads H265 packets. type H265Payloader struct { AddDONL bool SkipAggregation bool donl uint16 } -// Payload fragments a H265 packet across one or more byte arrays +// Payload fragments a H265 packet across one or more byte arrays. func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: gocognit var payloads [][]byte if len(payload) == 0 || mtu == 0 { @@ -1051,6 +1053,7 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: marginalAggregationSize++ } } + return marginalAggregationSize } From 76759435dd19b5ae1394119f597ab979224455e5 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 11 Jan 2025 05:28:12 +0900 Subject: [PATCH 07/10] Suppress linter nestif --- codecs/h265_packet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 00e247e..6a62927 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -975,7 +975,7 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: if len(bufferedNALUs) == 0 { return } - if len(bufferedNALUs) == 1 { + if len(bufferedNALUs) == 1 { //nolint:nestif // emit this as a single NALU packet nalu := bufferedNALUs[0] @@ -1067,7 +1067,7 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: if p.AddDONL { naluLen += 2 } - if naluLen <= int(mtu) { + if naluLen <= int(mtu) { //nolint:nestif // this nalu fits into a single packet, either it can be emitted as // a single nalu or appended to the previous aggregation packet marginalAggregationSize := calcMarginalAggregationSize(nalu) From ff6206e5aa31163349b596bdd11494d63d5f6e41 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 11 Jan 2025 05:48:36 +0900 Subject: [PATCH 08/10] Revert withdonl for compatibility --- codecs/h265_packet.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 6a62927..c824538 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -778,6 +778,16 @@ func toAbsDON(don uint16, prevDON *uint16, prevAbsDON *int) int { return 0 } +// 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. +// +// Deprecated: replaced by WithMaxDONDiff. +func (p *H265Packet) WithDONL(value bool) { + if value { + p.maxDONDiff = 1 + } +} + // WithMaxDONDiff sets the maximum difference between DON values before being emitted. func (p *H265Packet) WithMaxDONDiff(value uint16) { p.maxDONDiff = value From 41d84a9ada9bdb19c9fa0dec7694a4ca96c10175 Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 11 Jan 2025 07:11:05 +0900 Subject: [PATCH 09/10] Temporary revert of H265Packet H265Packet is no longer comparable due to the inclusion of nalBuffer, etc. of slices in H265Packet. Once reversed because it falls under the check for fixing incompatibilities. --- codecs/h265_packet.go | 150 ++----------------------------------- codecs/h265_packet_test.go | 2 +- 2 files changed, 8 insertions(+), 144 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index c824538..4a6f035 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "math" - "sort" ) // @@ -735,67 +734,18 @@ var ( // Packet implementation // -type donKeyedNALU struct { - DON int - NALU []byte -} - // H265Packet represents a H265 packet, stored in the payload of an RTP packet. type H265Packet struct { - packet isH265Packet - maxDONDiff uint16 - depackBufNALUs uint16 - - prevDON *uint16 - prevAbsDON *int - - naluBuffer []donKeyedNALU - fuBuffer []byte + packet isH265Packet + mightNeedDONL bool videoDepacketizer } -func toAbsDON(don uint16, prevDON *uint16, prevAbsDON *int) int { - if prevDON == nil || prevAbsDON == nil { - return int(don) - } - if don == *prevDON { - return *prevAbsDON - } - if don > *prevDON && don-*prevDON < 32768 { - return *prevAbsDON + int(don-*prevDON) - } - if don < *prevDON && *prevDON-don >= 32768 { - return *prevAbsDON + 65536 + int(*prevDON-don) - } - if don > *prevDON && don-*prevDON >= 32768 { - return *prevAbsDON - (int(*prevDON) + 65536 - int(don)) - } - if don < *prevDON && *prevDON-don < 32768 { - return *prevAbsDON - int(*prevDON-don) - } - - return 0 -} - // 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. -// -// Deprecated: replaced by WithMaxDONDiff. func (p *H265Packet) WithDONL(value bool) { - if value { - p.maxDONDiff = 1 - } -} - -// WithMaxDONDiff sets the maximum difference between DON values before being emitted. -func (p *H265Packet) WithMaxDONDiff(value uint16) { - p.maxDONDiff = value -} - -// WithDepackBufNALUs sets the maximum number of NALUs to be buffered. -func (p *H265Packet) WithDepackBufNALUs(value uint16) { - p.depackBufNALUs = value + p.mightNeedDONL = value } // Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon @@ -822,7 +772,7 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocog case payloadHeader.IsFragmentationUnit(): decoded := &H265FragmentationUnitPacket{} - decoded.WithDONL(p.maxDONDiff > 0) + decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err @@ -830,32 +780,9 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocog p.packet = decoded - if decoded.FuHeader().S() { - // push the nalu header - header := decoded.PayloadHeader() - p.fuBuffer = []byte{ - (uint8(header>>8) & 0b10000001) | (decoded.FuHeader().FuType() << 1), - uint8(header), - } - } - p.fuBuffer = append(p.fuBuffer, decoded.Payload()...) - if decoded.FuHeader().E() { - var absDON int - if p.maxDONDiff > 0 { - absDON = toAbsDON(*decoded.DONL(), p.prevDON, p.prevAbsDON) - p.prevDON = decoded.DONL() - p.prevAbsDON = &absDON - } - p.naluBuffer = append(p.naluBuffer, donKeyedNALU{ - DON: absDON, - NALU: p.fuBuffer, - }) - p.fuBuffer = nil - } - case payloadHeader.IsAggregationPacket(): decoded := &H265AggregationPacket{} - decoded.WithDONL(p.maxDONDiff > 0) + decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err @@ -863,81 +790,18 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocog p.packet = decoded - var absDON int - if p.maxDONDiff > 0 { - absDON = toAbsDON(*decoded.FirstUnit().DONL(), p.prevDON, p.prevAbsDON) - p.prevDON = decoded.FirstUnit().DONL() - p.prevAbsDON = &absDON - } - p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: decoded.FirstUnit().NalUnit()}) - for _, unit := range decoded.OtherUnits() { - if p.maxDONDiff > 0 { - donl := uint16(*unit.DOND()) + 1 + *decoded.FirstUnit().DONL() - absDON = toAbsDON(donl, p.prevDON, p.prevAbsDON) - p.prevDON = &donl - p.prevAbsDON = &absDON - } - p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: unit.NalUnit()}) - } - default: decoded := &H265SingleNALUnitPacket{} - decoded.WithDONL(p.maxDONDiff > 0) + decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } p.packet = decoded - - buf := make([]byte, 2+len(decoded.payload)) - binary.BigEndian.PutUint16(buf[0:2], uint16(decoded.payloadHeader)) - copy(buf[2:], decoded.payload) - - var absDON int - if p.maxDONDiff > 0 { - absDON = toAbsDON(*decoded.DONL(), p.prevDON, p.prevAbsDON) - p.prevDON = decoded.DONL() - p.prevAbsDON = &absDON - } - p.naluBuffer = append(p.naluBuffer, donKeyedNALU{DON: absDON, NALU: buf}) - } - - buf := []byte{} - if p.maxDONDiff > 0 { - // https://datatracker.ietf.org/doc/html/rfc7798#section-6 - // sort by AbsDON - sort.Slice(p.naluBuffer, func(i, j int) bool { - return p.naluBuffer[i].DON < p.naluBuffer[j].DON - }) - // find the max DONL value - var maxDONL int - for _, nalu := range p.naluBuffer { - if nalu.DON > maxDONL { - maxDONL = nalu.DON - } - } - minDONL := maxDONL - int(p.maxDONDiff) - // merge all NALUs while condition A or condition B are true - for len(p.naluBuffer) > 0 && (p.naluBuffer[0].DON < minDONL || len(p.naluBuffer) > int(p.depackBufNALUs)) { - // nolint - // TODO: this is not actually correct following B.2.2, not all NALUs have a 4-byte start code. - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, p.naluBuffer[0].NALU...) - p.naluBuffer = p.naluBuffer[1:] - } - } else { - // return the nalu buffer joined together - for _, val := range p.naluBuffer { - // nolint - // TODO: this is not actually correct following B.2.2, not all NALUs have a 4-byte start code. - buf = append(buf, annexbNALUStartCode...) - buf = append(buf, val.NALU...) - } - p.naluBuffer = nil } - return buf, nil + return nil, nil } // Packet returns the populated packet. diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index b3c2079..342555e 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -794,7 +794,7 @@ func TestH265_Packet(t *testing.T) { for _, cur := range tt { pck := &H265Packet{} if cur.WithDONL { - pck.WithMaxDONDiff(1) + pck.WithDONL(true) } _, err := pck.Unmarshal(cur.Raw) From ab77ba5e0ffc1194fe9c6068a2326f913995380c Mon Sep 17 00:00:00 2001 From: y-kawawa Date: Sat, 11 Jan 2025 14:53:49 +0900 Subject: [PATCH 10/10] Suppress linter warning --- codecs/h265_packet.go | 5 ++++- codecs/h265_packet_test.go | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 707b6a7..591566d 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -860,7 +860,7 @@ type H265Payloader struct { } // Payload fragments a H265 packet across one or more byte arrays. -func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: gocognit +func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: gocognit,cyclop,funlen var payloads [][]byte if len(payload) == 0 || mtu == 0 { return payloads @@ -928,6 +928,9 @@ func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint: index++ } } + + // Since the type of mtu is uint16, len(nalu) fits in as well, so it is safe. + // #nosec binary.BigEndian.PutUint16(buf[index:index+2], uint16(len(nalu))) index += 2 index += copy(buf[index:], nalu) diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 49adaff..e756627 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -873,7 +873,7 @@ func TestH265_Packet_Real(t *testing.T) { } } -func TestH265Payloader_Payload(t *testing.T) { +func TestH265Payloader_Payload(t *testing.T) { // nolint: funlen tt := []struct { Name string Data []byte @@ -1141,6 +1141,8 @@ 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,