From a2457a56ecfdacfd43963fa6355a054dae3542ee Mon Sep 17 00:00:00 2001 From: pigeatgarlic <64737125+pigeatgarlic@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:05:46 +0000 Subject: [PATCH] rm codecs --- codec/av1/av1/obu/leb128.go | 85 --------- codec/av1/av1/obu/leb128_test.go | 75 -------- codec/av1/av1_packet.go | 203 --------------------- codec/codec.go | 7 - codec/h264/README.md | 16 -- codec/h264/annexb/annexb.go | 160 ---------------- codec/h264/avc.go | 122 ------------- codec/h264/avcc.go | 111 ------------ codec/h264/h264.go | 145 --------------- codec/h264/h264_test.go | 95 ---------- codec/h264/mpeg4.go | 101 ----------- codec/h264/payloader.go | 191 -------------------- codec/h264/rtp.go | 134 -------------- codec/h264/sps.go | 231 ------------------------ codec/h265/README.md | 8 - codec/h265/avc.go | 54 ------ codec/h265/avcc.go | 61 ------- codec/h265/helper.go | 75 -------- codec/h265/mpeg4.go | 98 ---------- codec/h265/payloader.go | 301 ------------------------------- codec/h265/rtp.go | 195 -------------------- codec/h265/sps.go | 126 ------------- codec/opus/opus_packet.go | 73 -------- 23 files changed, 2667 deletions(-) delete mode 100644 codec/av1/av1/obu/leb128.go delete mode 100644 codec/av1/av1/obu/leb128_test.go delete mode 100644 codec/av1/av1_packet.go delete mode 100644 codec/codec.go delete mode 100644 codec/h264/README.md delete mode 100644 codec/h264/annexb/annexb.go delete mode 100644 codec/h264/avc.go delete mode 100644 codec/h264/avcc.go delete mode 100644 codec/h264/h264.go delete mode 100644 codec/h264/h264_test.go delete mode 100644 codec/h264/mpeg4.go delete mode 100644 codec/h264/payloader.go delete mode 100644 codec/h264/rtp.go delete mode 100644 codec/h264/sps.go delete mode 100644 codec/h265/README.md delete mode 100644 codec/h265/avc.go delete mode 100644 codec/h265/avcc.go delete mode 100644 codec/h265/helper.go delete mode 100644 codec/h265/mpeg4.go delete mode 100644 codec/h265/payloader.go delete mode 100644 codec/h265/rtp.go delete mode 100644 codec/h265/sps.go delete mode 100644 codec/opus/opus_packet.go diff --git a/codec/av1/av1/obu/leb128.go b/codec/av1/av1/obu/leb128.go deleted file mode 100644 index f5fcbf65..00000000 --- a/codec/av1/av1/obu/leb128.go +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -// Package obu implements tools for working with the Open Bitstream Unit. -package obu - -import "errors" - -const ( - sevenLsbBitmask = uint(0b01111111) - msbBitmask = uint(0b10000000) -) - -// ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read -var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished") - -// EncodeLEB128 encodes a uint as LEB128 -func EncodeLEB128(in uint) (out uint) { - for { - // Copy seven bits from in and discard - // what we have copied from in - out |= (in & sevenLsbBitmask) - in >>= 7 - - // If we have more bits to encode set MSB - // otherwise we are done - if in != 0 { - out |= msbBitmask - out <<= 8 - } else { - return out - } - } -} - -func decodeLEB128(in uint) (out uint) { - for { - // Take 7 LSB from in - out |= (in & sevenLsbBitmask) - - // Discard the MSB - in >>= 8 - if in == 0 { - return out - } - - out <<= 7 - } -} - -// ReadLeb128 scans an buffer and decodes a Leb128 value. -// If the end of the buffer is reached and all MSB are set -// an error is returned -func ReadLeb128(in []byte) (uint, uint, error) { - var encodedLength uint - - for i := range in { - encodedLength |= uint(in[i]) - - if in[i]&byte(msbBitmask) == 0 { - return decodeLEB128(encodedLength), uint(i + 1), nil - } - - // Make more room for next read - encodedLength <<= 8 - } - - return 0, 0, ErrFailedToReadLEB128 -} - -// WriteToLeb128 writes a uint to a LEB128 encoded byte slice. -func WriteToLeb128(in uint) []byte { - b := make([]byte, 10) - - for i := 0; i < len(b); i++ { - b[i] = byte(in & 0x7f) - in >>= 7 - if in == 0 { - return b[:i+1] - } - b[i] |= 0x80 - } - - return b // unreachable -} diff --git a/codec/av1/av1/obu/leb128_test.go b/codec/av1/av1/obu/leb128_test.go deleted file mode 100644 index 2b2336a4..00000000 --- a/codec/av1/av1/obu/leb128_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package obu - -import ( - "encoding/hex" - "errors" - "fmt" - "math" - "testing" -) - -func TestLEB128(t *testing.T) { - for _, test := range []struct { - Value uint - Encoded uint - }{ - {0, 0}, - {5, 5}, - {999999, 0xBF843D}, - } { - test := test - - encoded := EncodeLEB128(test.Value) - if encoded != test.Encoded { - t.Fatalf("Actual(%d) did not equal expected(%d)", encoded, test.Encoded) - } - - decoded := decodeLEB128(encoded) - if decoded != test.Value { - t.Fatalf("Actual(%d) did not equal expected(%d)", decoded, test.Value) - } - } -} - -func TestReadLeb128(t *testing.T) { - if _, _, err := ReadLeb128(nil); !errors.Is(err, ErrFailedToReadLEB128) { - t.Fatal("ReadLeb128 on a nil buffer should return an error") - } - - if _, _, err := ReadLeb128([]byte{0xFF}); !errors.Is(err, ErrFailedToReadLEB128) { - t.Fatal("ReadLeb128 on a buffer with all MSB set should fail") - } -} - -func TestWriteToLeb128(t *testing.T) { - type testVector struct { - value uint - leb128 string - } - testVectors := []testVector{ - {150, "9601"}, - {240, "f001"}, - {400, "9003"}, - {720, "d005"}, - {1200, "b009"}, - {999999, "bf843d"}, - {0, "00"}, - {math.MaxUint32, "ffffffff0f"}, - } - - runTest := func(t *testing.T, v testVector) { - b := WriteToLeb128(v.value) - if v.leb128 != hex.EncodeToString(b) { - t.Errorf("Expected %s, got %s", v.leb128, hex.EncodeToString(b)) - } - } - - for _, v := range testVectors { - t.Run(fmt.Sprintf("encode %d", v.value), func(t *testing.T) { - runTest(t, v) - }) - } -} diff --git a/codec/av1/av1_packet.go b/codec/av1/av1_packet.go deleted file mode 100644 index 65901c60..00000000 --- a/codec/av1/av1_packet.go +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package av1 - -import ( - "errors" - - "github.com/thinkonmay/sunshine-sdk/codec/av1/av1/obu" -) - - -var ( - errShortPacket = errors.New("packet is not large enough") - errNilPacket = errors.New("invalid nil packet") - errTooManyPDiff = errors.New("too many PDiff") - errTooManySpatialLayers = errors.New("too many spatial layers") - errUnhandledNALUType = errors.New("NALU Type is unhandled") - - // AV1 Errors - errIsKeyframeAndFragment = errors.New("bits Z and N are set. Not possible to have OBU be tail fragment and be keyframe") -) - - -const ( - zMask = byte(0b10000000) - zBitshift = 7 - - yMask = byte(0b01000000) - yBitshift = 6 - - wMask = byte(0b00110000) - wBitshift = 4 - - nMask = byte(0b00001000) - nBitshift = 3 - - obuFrameTypeMask = byte(0b01111000) - obuFrameTypeBitshift = 3 - - obuFameTypeSequenceHeader = 1 - - av1PayloaderHeadersize = 1 - - leb128Size = 1 -) - -// Payloader payloads AV1 packets -type Payloader struct { - sequenceHeader []byte -} - -// Payload fragments a AV1 packet across one or more byte arrays -// See AV1Packet for description of AV1 Payload Header -func (p *Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { - payloadDataIndex := 0 - payloadDataRemaining := len(payload) - - // Payload Data and MTU is non-zero - if mtu <= 0 || payloadDataRemaining <= 0 { - return payloads - } - - // Cache Sequence Header and packetize with next payload - frameType := (payload[0] & obuFrameTypeMask) >> obuFrameTypeBitshift - if frameType == obuFameTypeSequenceHeader { - p.sequenceHeader = payload - return - } - - for payloadDataRemaining > 0 { - obuCount := byte(1) - metadataSize := av1PayloaderHeadersize - if len(p.sequenceHeader) != 0 { - obuCount++ - metadataSize += leb128Size + len(p.sequenceHeader) - } - - out := make([]byte, min(int(mtu), payloadDataRemaining+metadataSize)) - outOffset := av1PayloaderHeadersize - out[0] = obuCount << wBitshift - - if obuCount == 2 { - // This Payload contain the start of a Coded Video Sequence - out[0] ^= nMask - - out[1] = byte(obu.EncodeLEB128(uint(len(p.sequenceHeader)))) - copy(out[2:], p.sequenceHeader) - - outOffset += leb128Size + len(p.sequenceHeader) - - p.sequenceHeader = nil - } - - outBufferRemaining := len(out) - outOffset - copy(out[outOffset:], payload[payloadDataIndex:payloadDataIndex+outBufferRemaining]) - payloadDataRemaining -= outBufferRemaining - payloadDataIndex += outBufferRemaining - - // Does this Fragment contain an OBU that started in a previous payload - if len(payloads) > 0 { - out[0] ^= zMask - } - - // This OBU will be continued in next Payload - if payloadDataRemaining != 0 { - out[0] ^= yMask - } - - payloads = append(payloads, out) - } - - return payloads -} - -// AV1Packet represents a depacketized AV1 RTP Packet -/* -* 0 1 2 3 4 5 6 7 -* +-+-+-+-+-+-+-+-+ -* |Z|Y| W |N|-|-|-| -* +-+-+-+-+-+-+-+-+ -**/ -// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header -type AV1Packet struct { - // Z: MUST be set to 1 if the first OBU element is an - // OBU fragment that is a continuation of an OBU fragment - // from the previous packet, and MUST be set to 0 otherwise. - Z bool - - // Y: MUST be set to 1 if the last OBU element is an OBU fragment - // that will continue in the next packet, and MUST be set to 0 otherwise. - Y bool - - // W: two bit field that describes the number of OBU elements in the packet. - // This field MUST be set equal to 0 or equal to the number of OBU elements - // contained in the packet. If set to 0, each OBU element MUST be preceded by - // a length field. If not set to 0 (i.e., W = 1, 2 or 3) the last OBU element - // MUST NOT be preceded by a length field. Instead, the length of the last OBU - // element contained in the packet can be calculated as follows: - // Length of the last OBU element = - // length of the RTP payload - // - length of aggregation header - // - length of previous OBU elements including length fields - W byte - - // N: MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set to 0 otherwise. - N bool - - // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. - // AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements - OBUElements [][]byte -} - -// Unmarshal parses the passed byte slice and stores the result in the AV1Packet this method is called upon -func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) { - if payload == nil { - return nil, errNilPacket - } else if len(payload) < 2 { - return nil, errShortPacket - } - - p.Z = ((payload[0] & zMask) >> zBitshift) != 0 - p.Y = ((payload[0] & yMask) >> yBitshift) != 0 - p.N = ((payload[0] & nMask) >> nBitshift) != 0 - p.W = (payload[0] & wMask) >> wBitshift - - if p.Z && p.N { - return nil, errIsKeyframeAndFragment - } - - currentIndex := uint(1) - p.OBUElements = [][]byte{} - - var ( - obuElementLength, bytesRead uint - err error - ) - for i := 1; ; i++ { - if currentIndex == uint(len(payload)) { - break - } - - // If W bit is set the last OBU Element will have no length header - if byte(i) == p.W { - bytesRead = 0 - obuElementLength = uint(len(payload)) - currentIndex - } else { - obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:]) - if err != nil { - return nil, err - } - } - - currentIndex += bytesRead - if uint(len(payload)) < currentIndex+obuElementLength { - return nil, errShortPacket - } - p.OBUElements = append(p.OBUElements, payload[currentIndex:currentIndex+obuElementLength]) - currentIndex += obuElementLength - } - - return payload[1:], nil -} diff --git a/codec/codec.go b/codec/codec.go deleted file mode 100644 index e8b11f0a..00000000 --- a/codec/codec.go +++ /dev/null @@ -1,7 +0,0 @@ -package codec - - - -type Payloader interface { - Payload(mtu uint16, payload []byte) [][]byte -} \ No newline at end of file diff --git a/codec/h264/README.md b/codec/h264/README.md deleted file mode 100644 index 1cad2067..00000000 --- a/codec/h264/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# H264 - -Payloader code taken from [pion](https://github.com/pion/rtp) library. And changed to AVC packets support. - -## Useful Links - -- [RTP Payload Format for H.264 Video](https://datatracker.ietf.org/doc/html/rfc6184) -- [The H264 Sequence parameter set](https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set) -- [H.264 Video Types (Microsoft)](https://docs.microsoft.com/en-us/windows/win32/directshow/h-264-video-types) -- [Automatic Generation of H.264 Parameter Sets to Recover Video File Fragments](https://arxiv.org/pdf/2104.14522.pdf) -- [Chromium sources](https://chromium.googlesource.com/external/webrtc/+/HEAD/common_video/h264) -- [AVC levels](https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels) -- [AVC profiles table](https://developer.mozilla.org/ru/docs/Web/Media/Formats/codecs_parameter) -- [Supported Media for Google Cast](https://developers.google.com/cast/docs/media) -- [Two stream formats, Annex-B, AVCC (H.264) and HVCC (H.265)](https://www.programmersought.com/article/3901815022/) -- https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/producer-reference-nal.html diff --git a/codec/h264/annexb/annexb.go b/codec/h264/annexb/annexb.go deleted file mode 100644 index 13f06622..00000000 --- a/codec/h264/annexb/annexb.go +++ /dev/null @@ -1,160 +0,0 @@ -// Package annexb - universal for H264 and H265 -package annexb - -import ( - "bytes" - "encoding/binary" -) - -const StartCode = "\x00\x00\x00\x01" -const startAUD = StartCode + "\x09\xF0" -const startAUDstart = startAUD + StartCode - -// EncodeToAVCC -// will change original slice data! -// safeAppend should be used if original slice has useful data after end (part of other slice) -// -// FFmpeg MPEG-TS: 00000001 AUD 00000001 SPS 00000001 PPS 000001 IFrame -// FFmpeg H264: 00000001 SPS 00000001 PPS 000001 IFrame 00000001 PFrame -func EncodeToAVCC(b []byte, safeAppend bool) []byte { - const minSize = len(StartCode) + 1 - - // 1. Check frist "start code" - if len(b) < len(startAUDstart) || string(b[:len(StartCode)]) != StartCode { - return nil - } - - // 2. Skip Access unit delimiter (AUD) from FFmpeg - if string(b[:len(startAUDstart)]) == startAUDstart { - b = b[6:] - } - - var start int - - for i, n := minSize, len(b)-minSize; i < n; { - // 3. Check "start code" (first 2 bytes) - if b[i] != 0 || b[i+1] != 0 { - i++ - continue - } - - // 4. Check "start code" (3 bytes size or 4 bytes size) - if b[i+2] == 1 { - if safeAppend { - // protect original slice from "damage" - b = bytes.Clone(b) - safeAppend = false - } - - // convert start code from 3 bytes to 4 bytes - b = append(b, 0) - copy(b[i+1:], b[i:]) - n++ - } else if b[i+2] != 0 || b[i+3] != 1 { - i++ - continue - } - - // 5. Set size for previous AU - size := uint32(i - start - len(StartCode)) - binary.BigEndian.PutUint32(b[start:], size) - - start = i - - i += minSize - } - - // 6. Set size for last AU - size := uint32(len(b) - start - len(StartCode)) - binary.BigEndian.PutUint32(b[start:], size) - - return b -} - -func DecodeAVCC(b []byte, safeClone bool) []byte { - if safeClone { - b = bytes.Clone(b) - } - for i := 0; i < len(b); { - size := int(binary.BigEndian.Uint32(b[i:])) - b[i] = 0 - b[i+1] = 0 - b[i+2] = 0 - b[i+3] = 1 - i += 4 + size - } - return b -} - -// DecodeAVCCWithAUD - AUD doesn't important for FFmpeg, but important for Safari -func DecodeAVCCWithAUD(src []byte) []byte { - dst := make([]byte, len(startAUD)+len(src)) - copy(dst, startAUD) - copy(dst[len(startAUD):], src) - DecodeAVCC(dst[len(startAUD):], false) - return dst -} - -const ( - h264PFrame = 1 - h264IFrame = 5 - h264SPS = 7 - h264PPS = 8 - - h265VPS = 32 - h265PFrame = 1 -) - -// IndexFrame - get new frame start position in the AnnexB stream -func IndexFrame(b []byte) int { - if len(b) < len(startAUDstart) { - return -1 - } - - for i := len(startAUDstart); ; { - if di := bytes.Index(b[i:], []byte(StartCode)); di < 0 { - break - } else { - i += di + 4 // move to NALU start - } - - if i >= len(b) { - break - } - - h264Type := b[i] & 0b1_1111 - switch h264Type { - case h264PFrame, h264SPS: - return i - 4 // move to start code - case h264IFrame, h264PPS: - continue - } - - h265Type := (b[i] >> 1) & 0b11_1111 - switch h265Type { - case h265PFrame, h265VPS: - return i - 4 // move to start code - } - } - - return -1 -} - -func FixAnnexBInAVCC(b []byte) []byte { - for i := 0; i < len(b); { - if i+4 >= len(b) { - break - } - - size := bytes.Index(b[i+4:], []byte{0, 0, 0, 1}) - if size < 0 { - size = len(b) - (i + 4) - } - - binary.BigEndian.PutUint32(b[i:], uint32(size)) - - i += size + 4 - } - - return b -} diff --git a/codec/h264/avc.go b/codec/h264/avc.go deleted file mode 100644 index e6a294c8..00000000 --- a/codec/h264/avc.go +++ /dev/null @@ -1,122 +0,0 @@ -package h264 - -import ( - "bytes" - "encoding/binary" -) - -const forbiddenZeroBit = 0x80 -const nalUnitType = 0x1F - -// Deprecated: DecodeStream - find and return first AU in AVC format -// useful for processing live streams with unknown separator size -func DecodeStream(annexb []byte) ([]byte, int) { - startPos := -1 - - i := 0 - for { - // search next separator - if i = IndexFrom(annexb, []byte{0, 0, 1}, i); i < 0 { - break - } - - // move i to next AU - if i += 3; i >= len(annexb) { - break - } - - // check if AU type valid - octet := annexb[i] - if octet&forbiddenZeroBit != 0 { - continue - } - - // 0 => AUD => SPS/IF/PF => AUD - // 0 => SPS/PF => SPS/PF - nalType := octet & nalUnitType - if startPos >= 0 { - switch nalType { - case NALUTypeAUD, NALUTypeSPS, NALUTypePFrame: - if annexb[i-4] == 0 { - return DecodeAnnexB(annexb[startPos : i-4]), i - 4 - } else { - return DecodeAnnexB(annexb[startPos : i-3]), i - 3 - } - } - } else { - switch nalType { - case NALUTypeSPS, NALUTypePFrame: - if i >= 4 && annexb[i-4] == 0 { - startPos = i - 4 - } else { - startPos = i - 3 - } - } - } - } - - return nil, 0 -} - -// DecodeAnnexB - convert AnnexB to AVC format -// support unknown separator size -func DecodeAnnexB(b []byte) []byte { - if b[2] == 1 { - // convert: 0 0 1 => 0 0 0 1 - b = append([]byte{0}, b...) - } - - startPos := 0 - - i := 4 - for { - // search next separato - if i = IndexFrom(b, []byte{0, 0, 1}, i); i < 0 { - break - } - - // move i to next AU - if i += 3; i >= len(b) { - break - } - - // check if AU type valid - octet := b[i] - if octet&forbiddenZeroBit != 0 { - continue - } - - switch octet & nalUnitType { - case NALUTypePFrame, NALUTypeIFrame, NALUTypeSPS, NALUTypePPS: - if b[i-4] != 0 { - // prefix: 0 0 1 - binary.BigEndian.PutUint32(b[startPos:], uint32(i-startPos-7)) - tmp := make([]byte, 0, len(b)+1) - tmp = append(tmp, b[:i]...) - tmp = append(tmp, 0) - b = append(tmp, b[i:]...) - startPos = i - 3 - } else { - // prefix: 0 0 0 1 - binary.BigEndian.PutUint32(b[startPos:], uint32(i-startPos-8)) - startPos = i - 4 - } - } - } - - binary.BigEndian.PutUint32(b[startPos:], uint32(len(b)-startPos-4)) - return b -} - -func IndexFrom(b []byte, sep []byte, from int) int { - if from > 0 { - if from < len(b) { - if i := bytes.Index(b[from:], sep); i >= 0 { - return from + i - } - } - return -1 - } - - return bytes.Index(b, sep) -} diff --git a/codec/h264/avcc.go b/codec/h264/avcc.go deleted file mode 100644 index c80ea083..00000000 --- a/codec/h264/avcc.go +++ /dev/null @@ -1,111 +0,0 @@ -// Package h264 - AVCC format related functions -package h264 - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - "encoding/hex" - - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/rtp" -) - -func RepairAVCC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { - sps, pps := GetParameterSet(codec.FmtpLine) - ps := JoinNALU(sps, pps) - - return func(packet *rtp.Packet) { - if NALUType(packet.Payload) == NALUTypeIFrame { - packet.Payload = Join(ps, packet.Payload) - } - handler(packet) - } -} - -func JoinNALU(nalus ...[]byte) (avcc []byte) { - var i, n int - - for _, nalu := range nalus { - if i = len(nalu); i > 0 { - n += 4 + i - } - } - - avcc = make([]byte, n) - - n = 0 - for _, nal := range nalus { - if i = len(nal); i > 0 { - binary.BigEndian.PutUint32(avcc[n:], uint32(i)) - n += 4 + copy(avcc[n+4:], nal) - } - } - - return -} - -func SplitNALU(avcc []byte) [][]byte { - var nals [][]byte - for { - // get AVC length - size := int(binary.BigEndian.Uint32(avcc)) + 4 - - // check if multiple items in one packet - if size < len(avcc) { - nals = append(nals, avcc[:size]) - avcc = avcc[size:] - } else { - nals = append(nals, avcc) - break - } - } - return nals -} - -func NALUTypes(avcc []byte) []byte { - var types []byte - for { - types = append(types, NALUType(avcc)) - - size := 4 + int(binary.BigEndian.Uint32(avcc)) - if size < len(avcc) { - avcc = avcc[size:] - } else { - break - } - } - return types -} - -func AVCCToCodec(avcc []byte) *core.Codec { - buf := bytes.NewBufferString("packetization-mode=1") - - for { - size := 4 + int(binary.BigEndian.Uint32(avcc)) - - switch NALUType(avcc) { - case NALUTypeSPS: - buf.WriteString(";profile-level-id=") - buf.WriteString(hex.EncodeToString(avcc[5:8])) - buf.WriteString(";sprop-parameter-sets=") - buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) - case NALUTypePPS: - buf.WriteString(",") - buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) - } - - if size < len(avcc) { - avcc = avcc[size:] - } else { - break - } - } - - return &core.Codec{ - Name: core.CodecH264, - ClockRate: 90000, - FmtpLine: buf.String(), - PayloadType: core.PayloadTypeRAW, - } -} diff --git a/codec/h264/h264.go b/codec/h264/h264.go deleted file mode 100644 index 12239536..00000000 --- a/codec/h264/h264.go +++ /dev/null @@ -1,145 +0,0 @@ -package h264 - -import ( - "encoding/base64" - "encoding/binary" - "encoding/hex" - "fmt" - "strings" - - "github.com/AlexxIT/go2rtc/pkg/core" -) - -const ( - NALUTypePFrame = 1 // Coded slice of a non-IDR picture - NALUTypeIFrame = 5 // Coded slice of an IDR picture - NALUTypeSEI = 6 // Supplemental enhancement information (SEI) - NALUTypeSPS = 7 // Sequence parameter set - NALUTypePPS = 8 // Picture parameter set - NALUTypeAUD = 9 // Access unit delimiter -) - -func NALUType(b []byte) byte { - return b[4] & 0x1F -} - -// IsKeyframe - check if any NALU in one AU is Keyframe -func IsKeyframe(b []byte) bool { - for { - switch NALUType(b) { - case NALUTypePFrame: - return false - case NALUTypeIFrame: - return true - } - - size := int(binary.BigEndian.Uint32(b)) + 4 - if size < len(b) { - b = b[size:] - continue - } else { - return false - } - } -} - -func Join(ps, iframe []byte) []byte { - b := make([]byte, len(ps)+len(iframe)) - i := copy(b, ps) - copy(b[i:], iframe) - return b -} - -// https://developers.google.com/cast/docs/media -const ( - ProfileBaseline = 0x42 - ProfileMain = 0x4D - ProfileHigh = 0x64 - CapabilityBaseline = 0xE0 - CapabilityMain = 0x40 -) - -// GetProfileLevelID - get profile from fmtp line -// Some devices won't play video with high level, so limit max profile and max level. -// And return some profile even if fmtp line is empty. -func GetProfileLevelID(fmtp string) string { - // avc1.640029 - H.264 high 4.1 (Chromecast 1st and 2nd Gen) - profile := byte(ProfileHigh) - capab := byte(0) - level := byte(41) - - if fmtp != "" { - var conf []byte - // some cameras has wrong profile-level-id - // https://github.com/AlexxIT/go2rtc/issues/155 - if s := core.Between(fmtp, "sprop-parameter-sets=", ","); s != "" { - if sps, _ := base64.StdEncoding.DecodeString(s); len(sps) >= 4 { - conf = sps[1:4] - } - } else if s = core.Between(fmtp, "profile-level-id=", ";"); s != "" { - conf, _ = hex.DecodeString(s) - } - - if len(conf) == 3 { - // sanitize profile, capab and level to supported values - switch conf[0] { - case ProfileBaseline, ProfileMain: - profile = conf[0] - } - switch conf[1] { - case CapabilityBaseline, CapabilityMain: - capab = conf[1] - } - switch conf[2] { - case 30, 31, 40: - level = conf[2] - } - } - } - - return fmt.Sprintf("%02X%02X%02X", profile, capab, level) -} - -func GetParameterSet(fmtp string) (sps, pps []byte) { - if fmtp == "" { - return - } - - s := core.Between(fmtp, "sprop-parameter-sets=", ";") - if s == "" { - return - } - - i := strings.IndexByte(s, ',') - if i < 0 { - return - } - - sps, _ = base64.StdEncoding.DecodeString(s[:i]) - pps, _ = base64.StdEncoding.DecodeString(s[i+1:]) - - return -} - -// GetFmtpLine from SPS+PPS+IFrame in AVC format -func GetFmtpLine(avc []byte) string { - s := "packetization-mode=1" - - for { - size := 4 + int(binary.BigEndian.Uint32(avc)) - - switch NALUType(avc) { - case NALUTypeSPS: - s += ";profile-level-id=" + hex.EncodeToString(avc[5:8]) - s += ";sprop-parameter-sets=" + base64.StdEncoding.EncodeToString(avc[4:size]) - case NALUTypePPS: - s += "," + base64.StdEncoding.EncodeToString(avc[4:size]) - } - - if size < len(avc) { - avc = avc[size:] - } else { - return s - } - } -} diff --git a/codec/h264/h264_test.go b/codec/h264/h264_test.go deleted file mode 100644 index 8b9fb737..00000000 --- a/codec/h264/h264_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package h264 - -import ( - "encoding/base64" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDecodeConfig(t *testing.T) { - s := "01640033ffe1000c67640033ac1514a02800f19001000468ee3cb0" - src, err := hex.DecodeString(s) - require.Nil(t, err) - - profile, sps, pps := DecodeConfig(src) - require.NotNil(t, profile) - require.NotNil(t, sps) - require.NotNil(t, pps) - - dst := EncodeConfig(sps, pps) - require.Equal(t, src, dst) -} - -func TestDecodeSPS(t *testing.T) { - s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410 - b, err := base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps := DecodeSPS(b) - require.Equal(t, uint16(2560), sps.Width()) - require.Equal(t, uint16(1920), sps.Height()) - - s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff - b, err = base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps = DecodeSPS(b) - require.Equal(t, uint16(1920), sps.Width()) - require.Equal(t, uint16(1080), sps.Height()) - - s = "Z01AMqaAKAC1kAA=" // Dahua - b, err = base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps = DecodeSPS(b) - require.Equal(t, uint16(2560), sps.Width()) - require.Equal(t, uint16(1440), sps.Height()) - - s = "Z2QAM6wVFKAoAPGQ" // Reolink - b, err = base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps = DecodeSPS(b) - require.Equal(t, uint16(2560), sps.Width()) - require.Equal(t, uint16(1920), sps.Height()) - - s = "Z2QAKKwa0AoAt03AQEBQAAADABAAAAMB6PFCKg==" // TP-Link - b, err = base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps = DecodeSPS(b) - require.Equal(t, uint16(1280), sps.Width()) - require.Equal(t, uint16(720), sps.Height()) - - s = "Z2QAFqwa0BQF/yzcBAQFAAADAAEAAAMAHo8UIqA=" // TP-Link sub - b, err = base64.StdEncoding.DecodeString(s) - require.Nil(t, err) - - sps = DecodeSPS(b) - require.Equal(t, uint16(640), sps.Width()) - require.Equal(t, uint16(360), sps.Height()) -} - -func TestGetProfileLevelID(t *testing.T) { - // OpenIPC https://github.com/OpenIPC - s := "profile-level-id=0033e7; packetization-mode=1; " - profile := GetProfileLevelID(s) - require.Equal(t, "640029", profile) - - // Eufy T8400 https://github.com/AlexxIT/go2rtc/issues/155 - s = "packetization-mode=1;profile-level-id=276400" - profile = GetProfileLevelID(s) - require.Equal(t, "640029", profile) -} - -func TestDecodeSPS2(t *testing.T) { - s := "6764001fad84010c20086100430802184010c200843b50740932" - b, err := hex.DecodeString(s) - require.Nil(t, err) - - sps := DecodeSPS(b) - assert.Nil(t, sps) // broken SPS? -} diff --git a/codec/h264/mpeg4.go b/codec/h264/mpeg4.go deleted file mode 100644 index c49e0e8a..00000000 --- a/codec/h264/mpeg4.go +++ /dev/null @@ -1,101 +0,0 @@ -// Package h264 - MPEG4 format related functions -package h264 - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - "encoding/hex" - - "github.com/AlexxIT/go2rtc/pkg/core" -) - -// DecodeConfig - extract profile, SPS and PPS from MPEG4 config -func DecodeConfig(conf []byte) (profile []byte, sps []byte, pps []byte) { - if len(conf) < 6 || conf[0] != 1 { - return - } - - profile = conf[1:4] - - count := conf[5] & 0x1F - conf = conf[6:] - for i := byte(0); i < count; i++ { - if len(conf) < 2 { - return - } - size := 2 + int(binary.BigEndian.Uint16(conf)) - if len(conf) < size { - return - } - if sps == nil { - sps = conf[2:size] - } - conf = conf[size:] - } - - count = conf[0] - conf = conf[1:] - for i := byte(0); i < count; i++ { - if len(conf) < 2 { - return - } - size := 2 + int(binary.BigEndian.Uint16(conf)) - if len(conf) < size { - return - } - if pps == nil { - pps = conf[2:size] - } - conf = conf[size:] - } - - return -} - -func EncodeConfig(sps, pps []byte) []byte { - spsSize := uint16(len(sps)) - ppsSize := uint16(len(pps)) - - buf := make([]byte, 5+3+spsSize+3+ppsSize) - buf[0] = 1 - copy(buf[1:], sps[1:4]) // profile - buf[4] = 3 | 0xFC // ? LengthSizeMinusOne - - b := buf[5:] - _ = b[3] - b[0] = 1 | 0xE0 // ? sps count - binary.BigEndian.PutUint16(b[1:], spsSize) - copy(b[3:], sps) - - b = buf[5+3+spsSize:] - _ = b[3] - b[0] = 1 // pps count - binary.BigEndian.PutUint16(b[1:], ppsSize) - copy(b[3:], pps) - - return buf -} - -func ConfigToCodec(conf []byte) *core.Codec { - buf := bytes.NewBufferString("packetization-mode=1") - - profile, sps, pps := DecodeConfig(conf) - if profile != nil { - buf.WriteString(";profile-level-id=") - buf.WriteString(hex.EncodeToString(profile)) - } - if sps != nil && pps != nil { - buf.WriteString(";sprop-parameter-sets=") - buf.WriteString(base64.StdEncoding.EncodeToString(sps)) - buf.WriteString(",") - buf.WriteString(base64.StdEncoding.EncodeToString(pps)) - } - - return &core.Codec{ - Name: core.CodecH264, - ClockRate: 90000, - FmtpLine: buf.String(), - PayloadType: core.PayloadTypeRAW, - } -} diff --git a/codec/h264/payloader.go b/codec/h264/payloader.go deleted file mode 100644 index cebaaf7c..00000000 --- a/codec/h264/payloader.go +++ /dev/null @@ -1,191 +0,0 @@ -package h264 - -import "encoding/binary" - -// Payloader payloads H264 packets -type Payloader struct { - IsAVC bool - stapANalu []byte -} - -const ( - stapaNALUType = 24 - fuaNALUType = 28 - fubNALUType = 29 - spsNALUType = 7 - ppsNALUType = 8 - audNALUType = 9 - fillerNALUType = 12 - - fuaHeaderSize = 2 - //stapaHeaderSize = 1 - //stapaNALULengthSize = 2 - - naluTypeBitmask = 0x1F - naluRefIdcBitmask = 0x60 - //fuStartBitmask = 0x80 - //fuEndBitmask = 0x40 - - outputStapAHeader = 0x78 -) - -//func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } - -func EmitNalus(nals []byte, isAVC bool, emit func([]byte)) { - if !isAVC { - nextInd := func(nalu []byte, start int) (indStart int, indLen int) { - zeroCount := 0 - - for i, b := range nalu[start:] { - if b == 0 { - zeroCount++ - continue - } else if b == 1 { - if zeroCount >= 2 { - return start + i - zeroCount, zeroCount + 1 - } - } - zeroCount = 0 - } - return -1, -1 - } - - nextIndStart, nextIndLen := nextInd(nals, 0) - if nextIndStart == -1 { - emit(nals) - } else { - for nextIndStart != -1 { - prevStart := nextIndStart + nextIndLen - nextIndStart, nextIndLen = nextInd(nals, prevStart) - if nextIndStart != -1 { - emit(nals[prevStart:nextIndStart]) - } else { - // Emit until end of stream, no end indicator found - emit(nals[prevStart:]) - } - } - } - } else { - for { - end := 4 + binary.BigEndian.Uint32(nals) - emit(nals[4:end]) - if int(end) >= len(nals) { - break - } - nals = nals[end:] - } - } -} - -// Payload fragments a H264 packet across one or more byte arrays -func (p *Payloader) Payload(mtu uint16, payload []byte) [][]byte { - var payloads [][]byte - if len(payload) == 0 { - return payloads - } - - EmitNalus(payload, p.IsAVC, func(nalu []byte) { - if len(nalu) == 0 { - return - } - - naluType := nalu[0] & naluTypeBitmask - naluRefIdc := nalu[0] & naluRefIdcBitmask - - switch naluType { - case audNALUType, fillerNALUType: - return - case spsNALUType, ppsNALUType: - if p.stapANalu == nil { - p.stapANalu = []byte{outputStapAHeader} - } - p.stapANalu = append(p.stapANalu, byte(len(nalu)>>8), byte(len(nalu))) - p.stapANalu = append(p.stapANalu, nalu...) - return - } - - if p.stapANalu != nil { - // Pack current NALU with SPS and PPS as STAP-A - // Supports multiple PPS in a row - if len(p.stapANalu) <= int(mtu) { - payloads = append(payloads, p.stapANalu) - } - p.stapANalu = nil - } - - // Single NALU - if len(nalu) <= int(mtu) { - out := make([]byte, len(nalu)) - copy(out, nalu) - payloads = append(payloads, out) - return - } - - // FU-A - maxFragmentSize := int(mtu) - fuaHeaderSize - - // The FU payload consists of fragments of the payload of the fragmented - // NAL unit so that if the fragmentation unit payloads of consecutive - // FUs are sequentially concatenated, the payload of the fragmented NAL - // unit can be reconstructed. The NAL unit type octet of the fragmented - // NAL unit is not included as such in the fragmentation unit payload, - // but rather the information of the NAL unit type octet of the - // fragmented NAL unit is conveyed in the F and NRI fields of the FU - // indicator octet of the fragmentation unit and in the type field of - // the FU header. An FU payload MAY have any number of octets and MAY - // be empty. - - naluData := nalu - // According to the RFC, the first octet is skipped due to redundant information - naluDataIndex := 1 - naluDataLength := len(nalu) - naluDataIndex - naluDataRemaining := naluDataLength - - if min(maxFragmentSize, naluDataRemaining) <= 0 { - return - } - - for naluDataRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, naluDataRemaining) - out := make([]byte, fuaHeaderSize+currentFragmentSize) - - // +---------------+ - // |0|1|2|3|4|5|6|7| - // +-+-+-+-+-+-+-+-+ - // |F|NRI| Type | - // +---------------+ - out[0] = fuaNALUType - out[0] |= naluRefIdc - - // +---------------+ - // |0|1|2|3|4|5|6|7| - // +-+-+-+-+-+-+-+-+ - // |S|E|R| Type | - // +---------------+ - - out[1] = naluType - if naluDataRemaining == naluDataLength { - // Set start bit - out[1] |= 1 << 7 - } else if naluDataRemaining-currentFragmentSize == 0 { - // Set end bit - out[1] |= 1 << 6 - } - - copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) - payloads = append(payloads, out) - - naluDataRemaining -= currentFragmentSize - naluDataIndex += currentFragmentSize - } - }) - - return payloads -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/codec/h264/rtp.go b/codec/h264/rtp.go deleted file mode 100644 index d2dda81a..00000000 --- a/codec/h264/rtp.go +++ /dev/null @@ -1,134 +0,0 @@ -package h264 - -import ( - "encoding/binary" - - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/rtp" - "github.com/pion/rtp/codecs" - "github.com/thinkonmay/sunshine-sdk/codec/h264/annexb" -) - -const RTPPacketVersionAVC = 0 - -const PSMaxSize = 128 // the biggest SPS I've seen is 48 (EZVIZ CS-CV210) - -func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { - depack := &codecs.H264Packet{IsAVC: true} - - sps, pps := GetParameterSet(codec.FmtpLine) - ps := JoinNALU(sps, pps) - - buf := make([]byte, 0, 512*1024) // 512K - - return func(packet *rtp.Packet) { - //log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", track.Codec.Name, packet.Payload[0]&0x1F, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker) - - payload, err := depack.Unmarshal(packet.Payload) - if len(payload) == 0 || err != nil { - return - } - - // Memory overflow protection. Can happen if we miss a lot of packets with the marker. - // https://github.com/AlexxIT/go2rtc/issues/675 - if len(buf) > 5*1024*1024 { - buf = buf[: 0 : 512*1024] - } - - // Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true - // Reolink Duo 2: sends SPS with Marker and PPS without - if packet.Marker && len(payload) < PSMaxSize { - switch NALUType(payload) { - case NALUTypeSPS, NALUTypePPS: - buf = append(buf, payload...) - return - case NALUTypeSEI: - // RtspServer https://github.com/AlexxIT/go2rtc/issues/244 - // sends, marked SPS, marked PPS, marked SEI, marked IFrame - return - } - } - - if len(buf) == 0 { - for { - // Amcrest IP4M-1051: 9, 7, 8, 6, 28... - // Amcrest IP4M-1051: 9, 6, 1 - switch NALUType(payload) { - case NALUTypeIFrame: - // fix IFrame without SPS,PPS - buf = append(buf, ps...) - case NALUTypeSEI, NALUTypeAUD: - // fix ffmpeg with transcoding first frame - i := int(4 + binary.BigEndian.Uint32(payload)) - - // check if only one NAL (fix ffmpeg transcoding for Reolink RLC-510A) - if i == len(payload) { - return - } - - payload = payload[i:] - continue - } - break - } - } - - // collect all NALs for Access Unit - if !packet.Marker { - buf = append(buf, payload...) - return - } - - if len(buf) > 0 { - payload = append(buf, payload...) - buf = buf[:0] - } - - // should not be that huge SPS - if NALUType(payload) == NALUTypeSPS && binary.BigEndian.Uint32(payload) >= PSMaxSize { - // some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01 - // https://github.com/AlexxIT/WebRTC/issues/391 - // https://github.com/AlexxIT/WebRTC/issues/392 - payload = annexb.FixAnnexBInAVCC(payload) - } - - //log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", NALUTypes(payload), len(payload), packet.Timestamp, packet.SequenceNumber) - - clone := *packet - clone.Version = RTPPacketVersionAVC - clone.Payload = payload - handler(&clone) - } -} - -func RTPPay(mtu uint16, handler core.HandlerFunc) core.HandlerFunc { - if mtu == 0 { - mtu = 1472 - } - - payloader := &Payloader{IsAVC: true} - sequencer := rtp.NewRandomSequencer() - mtu -= 12 // rtp.Header size - - return func(packet *rtp.Packet) { - if packet.Version != RTPPacketVersionAVC { - handler(packet) - return - } - - payloads := payloader.Payload(mtu, packet.Payload) - last := len(payloads) - 1 - for i, payload := range payloads { - clone := rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: i == last, - SequenceNumber: sequencer.NextSequenceNumber(), - Timestamp: packet.Timestamp, - }, - Payload: payload, - } - handler(&clone) - } - } -} diff --git a/codec/h264/sps.go b/codec/h264/sps.go deleted file mode 100644 index 71ab5b45..00000000 --- a/codec/h264/sps.go +++ /dev/null @@ -1,231 +0,0 @@ -package h264 - -import "github.com/AlexxIT/go2rtc/pkg/bits" - -// http://www.itu.int/rec/T-REC-H.264 -// https://webrtc.googlesource.com/src/+/refs/heads/main/common_video/h264/sps_parser.cc - -//goland:noinspection GoSnakeCaseUsage -type SPS struct { - profile_idc uint8 - profile_iop uint8 - level_idc uint8 - - seq_parameter_set_id uint32 - - chroma_format_idc uint32 - separate_colour_plane_flag byte - bit_depth_luma_minus8 uint32 - bit_depth_chroma_minus8 uint32 - qpprime_y_zero_transform_bypass_flag byte - seq_scaling_matrix_present_flag byte - - log2_max_frame_num_minus4 uint32 - pic_order_cnt_type uint32 - log2_max_pic_order_cnt_lsb_minus4 uint32 - delta_pic_order_always_zero_flag byte - offset_for_non_ref_pic int32 - offset_for_top_to_bottom_field int32 - num_ref_frames_in_pic_order_cnt_cycle uint32 - num_ref_frames uint32 - gaps_in_frame_num_value_allowed_flag byte - - pic_width_in_mbs_minus_1 uint32 - pic_height_in_map_units_minus_1 uint32 - frame_mbs_only_flag byte - mb_adaptive_frame_field_flag byte - direct_8x8_inference_flag byte - - frame_cropping_flag byte - frame_crop_left_offset uint32 - frame_crop_right_offset uint32 - frame_crop_top_offset uint32 - frame_crop_bottom_offset uint32 - - vui_parameters_present_flag byte - aspect_ratio_info_present_flag byte - aspect_ratio_idc byte - sar_width uint16 - sar_height uint16 - - overscan_info_present_flag byte - overscan_appropriate_flag byte - - video_signal_type_present_flag byte - video_format uint8 - video_full_range_flag byte - - colour_description_present_flag byte - colour_description uint32 - - chroma_loc_info_present_flag byte - chroma_sample_loc_type_top_field uint32 - chroma_sample_loc_type_bottom_field uint32 - - timing_info_present_flag byte - num_units_in_tick uint32 - time_scale uint32 - fixed_frame_rate_flag byte -} - -func (s *SPS) Width() uint16 { - width := 16 * (s.pic_width_in_mbs_minus_1 + 1) - crop := 2 * (s.frame_crop_left_offset + s.frame_crop_right_offset) - return uint16(width - crop) -} - -func (s *SPS) Height() uint16 { - height := 16 * (s.pic_height_in_map_units_minus_1 + 1) - crop := 2 * (s.frame_crop_top_offset + s.frame_crop_bottom_offset) - if s.frame_mbs_only_flag == 0 { - height *= 2 - } - return uint16(height - crop) -} - -func DecodeSPS(sps []byte) *SPS { - r := bits.NewReader(sps) - - hdr := r.ReadByte() - if hdr&0x1F != NALUTypeSPS { - return nil - } - - s := &SPS{ - profile_idc: r.ReadByte(), - profile_iop: r.ReadByte(), - level_idc: r.ReadByte(), - seq_parameter_set_id: r.ReadUEGolomb(), - } - - switch s.profile_idc { - case 100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135: - n := byte(8) - - s.chroma_format_idc = r.ReadUEGolomb() - if s.chroma_format_idc == 3 { - s.separate_colour_plane_flag = r.ReadBit() - n = 12 - } - - s.bit_depth_luma_minus8 = r.ReadUEGolomb() - s.bit_depth_chroma_minus8 = r.ReadUEGolomb() - s.qpprime_y_zero_transform_bypass_flag = r.ReadBit() - - s.seq_scaling_matrix_present_flag = r.ReadBit() - if s.seq_scaling_matrix_present_flag != 0 { - for i := byte(0); i < n; i++ { - //goland:noinspection GoSnakeCaseUsage - seq_scaling_list_present_flag := r.ReadBit() - if seq_scaling_list_present_flag != 0 { - if i < 6 { - s.scaling_list(r, 16) - } else { - s.scaling_list(r, 64) - } - } - } - } - } - - s.log2_max_frame_num_minus4 = r.ReadUEGolomb() - - s.pic_order_cnt_type = r.ReadUEGolomb() - switch s.pic_order_cnt_type { - case 0: - s.log2_max_pic_order_cnt_lsb_minus4 = r.ReadUEGolomb() - case 1: - s.delta_pic_order_always_zero_flag = r.ReadBit() - s.offset_for_non_ref_pic = r.ReadSEGolomb() - s.offset_for_top_to_bottom_field = r.ReadSEGolomb() - - s.num_ref_frames_in_pic_order_cnt_cycle = r.ReadUEGolomb() - for i := uint32(0); i < s.num_ref_frames_in_pic_order_cnt_cycle; i++ { - _ = r.ReadSEGolomb() // offset_for_ref_frame[i] - } - } - - s.num_ref_frames = r.ReadUEGolomb() - s.gaps_in_frame_num_value_allowed_flag = r.ReadBit() - - s.pic_width_in_mbs_minus_1 = r.ReadUEGolomb() - s.pic_height_in_map_units_minus_1 = r.ReadUEGolomb() - - s.frame_mbs_only_flag = r.ReadBit() - if s.frame_mbs_only_flag == 0 { - s.mb_adaptive_frame_field_flag = r.ReadBit() - } - - s.direct_8x8_inference_flag = r.ReadBit() - - s.frame_cropping_flag = r.ReadBit() - if s.frame_cropping_flag != 0 { - s.frame_crop_left_offset = r.ReadUEGolomb() - s.frame_crop_right_offset = r.ReadUEGolomb() - s.frame_crop_top_offset = r.ReadUEGolomb() - s.frame_crop_bottom_offset = r.ReadUEGolomb() - } - - s.vui_parameters_present_flag = r.ReadBit() - if s.vui_parameters_present_flag != 0 { - s.aspect_ratio_info_present_flag = r.ReadBit() - if s.aspect_ratio_info_present_flag != 0 { - s.aspect_ratio_idc = r.ReadByte() - if s.aspect_ratio_idc == 255 { - s.sar_width = r.ReadUint16() - s.sar_height = r.ReadUint16() - } - } - - s.overscan_info_present_flag = r.ReadBit() - if s.overscan_info_present_flag != 0 { - s.overscan_appropriate_flag = r.ReadBit() - } - - s.video_signal_type_present_flag = r.ReadBit() - if s.video_signal_type_present_flag != 0 { - s.video_format = r.ReadBits8(3) - s.video_full_range_flag = r.ReadBit() - - s.colour_description_present_flag = r.ReadBit() - if s.colour_description_present_flag != 0 { - s.colour_description = r.ReadUint24() - } - } - - s.chroma_loc_info_present_flag = r.ReadBit() - if s.chroma_loc_info_present_flag != 0 { - s.chroma_sample_loc_type_top_field = r.ReadUEGolomb() - s.chroma_sample_loc_type_bottom_field = r.ReadUEGolomb() - } - - s.timing_info_present_flag = r.ReadBit() - if s.timing_info_present_flag != 0 { - s.num_units_in_tick = r.ReadUint32() - s.time_scale = r.ReadUint32() - s.fixed_frame_rate_flag = r.ReadBit() - } - //... - } - - if r.EOF { - return nil - } - - return s -} - -//goland:noinspection GoSnakeCaseUsage -func (s *SPS) scaling_list(r *bits.Reader, sizeOfScalingList int) { - lastScale := int32(8) - nextScale := int32(8) - for j := 0; j < sizeOfScalingList; j++ { - if nextScale != 0 { - delta_scale := r.ReadSEGolomb() - nextScale = (lastScale + delta_scale + 256) % 256 - } - if nextScale != 0 { - lastScale = nextScale - } - } -} diff --git a/codec/h265/README.md b/codec/h265/README.md deleted file mode 100644 index 78b2826f..00000000 --- a/codec/h265/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# H265 - -Payloader code taken from [pion](https://github.com/pion/rtp) library branch [h265](https://github.com/pion/rtp/tree/h265). Because it's still not in release. Thanks to [@kevmo314](https://github.com/kevmo314). - -## Useful links - -- https://datatracker.ietf.org/doc/html/rfc7798 -- [Add initial support for WebRTC HEVC](https://trac.webkit.org/changeset/259452/webkit) diff --git a/codec/h265/avc.go b/codec/h265/avc.go deleted file mode 100644 index dd0faf15..00000000 --- a/codec/h265/avc.go +++ /dev/null @@ -1,54 +0,0 @@ -package h265 - -import "github.com/thinkonmay/sunshine-sdk/codec/h264" - -const forbiddenZeroBit = 0x80 -const nalUnitType = 0x3F - -// Deprecated: DecodeStream - find and return first AU in AVC format -// useful for processing live streams with unknown separator size -func DecodeStream(annexb []byte) ([]byte, int) { - startPos := -1 - - i := 0 - for { - // search next separator - if i = h264.IndexFrom(annexb, []byte{0, 0, 1}, i); i < 0 { - break - } - - // move i to next AU - if i += 3; i >= len(annexb) { - break - } - - // check if AU type valid - octet := annexb[i] - if octet&forbiddenZeroBit != 0 { - continue - } - - nalType := (octet >> 1) & nalUnitType - if startPos >= 0 { - switch nalType { - case NALUTypeVPS, NALUTypePFrame: - if annexb[i-4] == 0 { - return h264.DecodeAnnexB(annexb[startPos : i-4]), i - 4 - } else { - return h264.DecodeAnnexB(annexb[startPos : i-3]), i - 3 - } - } - } else { - switch nalType { - case NALUTypeVPS, NALUTypePFrame: - if i >= 4 && annexb[i-4] == 0 { - startPos = i - 4 - } else { - startPos = i - 3 - } - } - } - } - - return nil, 0 -} diff --git a/codec/h265/avcc.go b/codec/h265/avcc.go deleted file mode 100644 index 6ba84f18..00000000 --- a/codec/h265/avcc.go +++ /dev/null @@ -1,61 +0,0 @@ -// Package h265 - AVCC format related functions -package h265 - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/rtp" - "github.com/thinkonmay/sunshine-sdk/codec/h264" -) - -func RepairAVCC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { - vds, sps, pps := GetParameterSet(codec.FmtpLine) - ps := h264.JoinNALU(vds, sps, pps) - - return func(packet *rtp.Packet) { - switch NALUType(packet.Payload) { - case NALUTypeIFrame, NALUTypeIFrame2, NALUTypeIFrame3: - clone := *packet - clone.Payload = h264.Join(ps, packet.Payload) - handler(&clone) - default: - handler(packet) - } - } -} - -func AVCCToCodec(avcc []byte) *core.Codec { - buf := bytes.NewBufferString("profile-id=1") - - for { - size := 4 + int(binary.BigEndian.Uint32(avcc)) - - switch NALUType(avcc) { - case NALUTypeVPS: - buf.WriteString(";sprop-vps=") - buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) - case NALUTypeSPS: - buf.WriteString(";sprop-sps=") - buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) - case NALUTypePPS: - buf.WriteString(";sprop-pps=") - buf.WriteString(base64.StdEncoding.EncodeToString(avcc[4:size])) - } - - if size < len(avcc) { - avcc = avcc[size:] - } else { - break - } - } - - return &core.Codec{ - Name: core.CodecH265, - ClockRate: 90000, - FmtpLine: buf.String(), - PayloadType: core.PayloadTypeRAW, - } -} diff --git a/codec/h265/helper.go b/codec/h265/helper.go deleted file mode 100644 index 44605bde..00000000 --- a/codec/h265/helper.go +++ /dev/null @@ -1,75 +0,0 @@ -package h265 - -import ( - "encoding/base64" - "encoding/binary" - "github.com/AlexxIT/go2rtc/pkg/core" -) - -const ( - NALUTypePFrame = 1 - NALUTypeIFrame = 19 - NALUTypeIFrame2 = 20 - NALUTypeIFrame3 = 21 - NALUTypeVPS = 32 - NALUTypeSPS = 33 - NALUTypePPS = 34 - NALUTypePrefixSEI = 39 - NALUTypeSuffixSEI = 40 - NALUTypeFU = 49 -) - -func NALUType(b []byte) byte { - return (b[4] >> 1) & 0x3F -} - -func IsKeyframe(b []byte) bool { - for { - switch NALUType(b) { - case NALUTypePFrame: - return false - case NALUTypeIFrame, NALUTypeIFrame2, NALUTypeIFrame3: - return true - } - - size := int(binary.BigEndian.Uint32(b)) + 4 - if size < len(b) { - b = b[size:] - continue - } else { - return false - } - } -} - -func Types(data []byte) []byte { - var types []byte - for { - types = append(types, NALUType(data)) - - size := 4 + int(binary.BigEndian.Uint32(data)) - if size < len(data) { - data = data[size:] - } else { - break - } - } - return types -} - -func GetParameterSet(fmtp string) (vps, sps, pps []byte) { - if fmtp == "" { - return - } - - s := core.Between(fmtp, "sprop-vps=", ";") - vps, _ = base64.StdEncoding.DecodeString(s) - - s = core.Between(fmtp, "sprop-sps=", ";") - sps, _ = base64.StdEncoding.DecodeString(s) - - s = core.Between(fmtp, "sprop-pps=", ";") - pps, _ = base64.StdEncoding.DecodeString(s) - - return -} diff --git a/codec/h265/mpeg4.go b/codec/h265/mpeg4.go deleted file mode 100644 index e06d0836..00000000 --- a/codec/h265/mpeg4.go +++ /dev/null @@ -1,98 +0,0 @@ -// Package h265 - MPEG4 format related functions -package h265 - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - - "github.com/AlexxIT/go2rtc/pkg/core" -) - -func DecodeConfig(conf []byte) (profile, vps, sps, pps []byte) { - profile = conf[1:4] - - b := conf[23:] - if binary.BigEndian.Uint16(b[1:]) != 1 { - return - } - vpsSize := binary.BigEndian.Uint16(b[3:]) - vps = b[5 : 5+vpsSize] - - b = conf[23+5+vpsSize:] - if binary.BigEndian.Uint16(b[1:]) != 1 { - return - } - spsSize := binary.BigEndian.Uint16(b[3:]) - sps = b[5 : 5+spsSize] - - b = conf[23+5+vpsSize+5+spsSize:] - if binary.BigEndian.Uint16(b[1:]) != 1 { - return - } - ppsSize := binary.BigEndian.Uint16(b[3:]) - pps = b[5 : 5+ppsSize] - - return -} - -func EncodeConfig(vps, sps, pps []byte) []byte { - vpsSize := uint16(len(vps)) - spsSize := uint16(len(sps)) - ppsSize := uint16(len(pps)) - - buf := make([]byte, 23+5+vpsSize+5+spsSize+5+ppsSize) - - buf[0] = 1 - copy(buf[1:], sps[3:6]) // profile - buf[21] = 3 // ? - buf[22] = 3 // ? - - b := buf[23:] - _ = b[5] - b[0] = (vps[0] >> 1) & 0x3F - binary.BigEndian.PutUint16(b[1:], 1) // VPS count - binary.BigEndian.PutUint16(b[3:], vpsSize) - copy(b[5:], vps) - - b = buf[23+5+vpsSize:] - _ = b[5] - b[0] = (sps[0] >> 1) & 0x3F - binary.BigEndian.PutUint16(b[1:], 1) // SPS count - binary.BigEndian.PutUint16(b[3:], spsSize) - copy(b[5:], sps) - - b = buf[23+5+vpsSize+5+spsSize:] - _ = b[5] - b[0] = (pps[0] >> 1) & 0x3F - binary.BigEndian.PutUint16(b[1:], 1) // PPS count - binary.BigEndian.PutUint16(b[3:], ppsSize) - copy(b[5:], pps) - - return buf -} - -func ConfigToCodec(conf []byte) *core.Codec { - buf := bytes.NewBufferString("profile-id=1") - - _, vps, sps, pps := DecodeConfig(conf) - if vps != nil { - buf.WriteString(";sprop-vps=") - buf.WriteString(base64.StdEncoding.EncodeToString(vps)) - } - if sps != nil { - buf.WriteString(";sprop-sps=") - buf.WriteString(base64.StdEncoding.EncodeToString(sps)) - } - if pps != nil { - buf.WriteString(";sprop-pps=") - buf.WriteString(base64.StdEncoding.EncodeToString(pps)) - } - - return &core.Codec{ - Name: core.CodecH265, - ClockRate: 90000, - FmtpLine: buf.String(), - PayloadType: core.PayloadTypeRAW, - } -} diff --git a/codec/h265/payloader.go b/codec/h265/payloader.go deleted file mode 100644 index 936e9032..00000000 --- a/codec/h265/payloader.go +++ /dev/null @@ -1,301 +0,0 @@ -package h265 - -import ( - "encoding/binary" - "math" - - "github.com/thinkonmay/sunshine-sdk/codec/h264" -) - -// -// Network Abstraction Unit Header implementation -// - -const ( - // sizeof(uint16) - h265NaluHeaderSize = 2 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 - h265NaluAggregationPacketType = 48 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 - h265NaluFragmentationUnitType = 49 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 - h265NaluPACIPacketType = 50 -) - -// H265NALUHeader is a H265 NAL Unit Header -// https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 -// +---------------+---------------+ -// -// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |F| Type | LayerID | TID | -// +-------------+-----------------+ -type H265NALUHeader uint16 - -func newH265NALUHeader(highByte, lowByte uint8) H265NALUHeader { - return H265NALUHeader((uint16(highByte) << 8) | uint16(lowByte)) -} - -// F is the forbidden bit, should always be 0. -func (h H265NALUHeader) F() bool { - return (uint16(h) >> 15) != 0 -} - -// Type of NAL Unit. -func (h H265NALUHeader) Type() uint8 { - // 01111110 00000000 - const mask = 0b01111110 << 8 - return uint8((uint16(h) & mask) >> (8 + 1)) -} - -// IsTypeVCLUnit returns whether or not the NAL Unit type is a VCL NAL unit. -func (h H265NALUHeader) IsTypeVCLUnit() bool { - // Type is coded on 6 bits - const msbMask = 0b00100000 - return (h.Type() & msbMask) == 0 -} - -// LayerID should always be 0 in non-3D HEVC context. -func (h H265NALUHeader) LayerID() uint8 { - // 00000001 11111000 - const mask = (0b00000001 << 8) | 0b11111000 - return uint8((uint16(h) & mask) >> 3) -} - -// TID is the temporal identifier of the NAL unit +1. -func (h H265NALUHeader) TID() uint8 { - const mask = 0b00000111 - return uint8(uint16(h) & mask) -} - -// IsAggregationPacket returns whether or not the packet is an Aggregation packet. -func (h H265NALUHeader) IsAggregationPacket() bool { - return h.Type() == h265NaluAggregationPacketType -} - -// IsFragmentationUnit returns whether or not the packet is a Fragmentation Unit packet. -func (h H265NALUHeader) IsFragmentationUnit() bool { - return h.Type() == h265NaluFragmentationUnitType -} - -// IsPACIPacket returns whether or not the packet is a PACI packet. -func (h H265NALUHeader) IsPACIPacket() bool { - return h.Type() == h265NaluPACIPacketType -} - -// -// Fragmentation Unit implementation -// - -const ( - // sizeof(uint8) - h265FragmentationUnitHeaderSize = 1 -) - -// H265FragmentationUnitHeader is a H265 FU Header -// +---------------+ -// |0|1|2|3|4|5|6|7| -// +-+-+-+-+-+-+-+-+ -// |S|E| FuType | -// +---------------+ -type H265FragmentationUnitHeader uint8 - -// S represents the start of a fragmented NAL unit. -func (h H265FragmentationUnitHeader) S() bool { - const mask = 0b10000000 - return ((h & mask) >> 7) != 0 -} - -// E represents the end of a fragmented NAL unit. -func (h H265FragmentationUnitHeader) E() bool { - const mask = 0b01000000 - return ((h & mask) >> 6) != 0 -} - -// FuType MUST be equal to the field Type of the fragmented NAL unit. -func (h H265FragmentationUnitHeader) FuType() uint8 { - const mask = 0b00111111 - return uint8(h) & mask -} - -// Payloader payloads H265 packets -type Payloader struct { - AddDONL bool - SkipAggregation bool - donl uint16 -} - -// Payload fragments a H265 packet across one or more byte arrays -func (p *Payloader) Payload(mtu uint16, payload []byte) [][]byte { - 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 - } - - h264.EmitNalus(payload, false, 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 += 1 - } - - 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/codec/h265/rtp.go b/codec/h265/rtp.go deleted file mode 100644 index ce6f915d..00000000 --- a/codec/h265/rtp.go +++ /dev/null @@ -1,195 +0,0 @@ -package h265 - -import ( - "encoding/binary" - - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/rtp" - "github.com/thinkonmay/sunshine-sdk/codec/h264" -) - -func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { - //vps, sps, pps := GetParameterSet(codec.FmtpLine) - //ps := h264.EncodeAVC(vps, sps, pps) - - buf := make([]byte, 0, 512*1024) // 512K - var nuStart int - - return func(packet *rtp.Packet) { - data := packet.Payload - if len(data) < 3 { - return - } - - nuType := (data[0] >> 1) & 0x3F - //log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", track.Codec.Name, nuType, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker) - - // Fix for RtspServer https://github.com/AlexxIT/go2rtc/issues/244 - if packet.Marker && len(data) < h264.PSMaxSize { - switch nuType { - case NALUTypeVPS, NALUTypeSPS, NALUTypePPS: - packet.Marker = false - case NALUTypePrefixSEI, NALUTypeSuffixSEI: - return - } - } - - if nuType == NALUTypeFU { - switch data[2] >> 6 { - case 2: // begin - nuType = data[2] & 0x3F - - // push PS data before keyframe - //if len(buf) == 0 && nuType >= 19 && nuType <= 21 { - // buf = append(buf, ps...) - //} - - nuStart = len(buf) - buf = append(buf, 0, 0, 0, 0) // NAL unit size - buf = append(buf, (data[0]&0x81)|(nuType<<1), data[1]) - buf = append(buf, data[3:]...) - return - case 0: // continue - buf = append(buf, data[3:]...) - return - case 1: // end - buf = append(buf, data[3:]...) - binary.BigEndian.PutUint32(buf[nuStart:], uint32(len(buf)-nuStart-4)) - case 3: // wrong RFC 7798 realisation from OpenIPC project - // A non-fragmented NAL unit MUST NOT be transmitted in one FU; i.e., - // the Start bit and End bit must not both be set to 1 in the same FU - // header. - nuType = data[2] & 0x3F - buf = binary.BigEndian.AppendUint32(buf, uint32(len(data))-1) // NAL unit size - buf = append(buf, (data[0]&0x81)|(nuType<<1), data[1]) - buf = append(buf, data[3:]...) - } - } else { - nuStart = len(buf) - buf = append(buf, 0, 0, 0, 0) // NAL unit size - buf = append(buf, data...) - binary.BigEndian.PutUint32(buf[nuStart:], uint32(len(data))) - } - - // collect all NAL Units for Access Unit - if !packet.Marker { - return - } - - //log.Printf("[HEVC] %v, len: %d", Types(buf), len(buf)) - - clone := *packet - clone.Version = h264.RTPPacketVersionAVC - clone.Payload = buf - - buf = buf[:0] - - handler(&clone) - } -} - -func RTPPay(mtu uint16, handler core.HandlerFunc) core.HandlerFunc { - if mtu == 0 { - mtu = 1472 - } - - payloader := &Payloader{} - sequencer := rtp.NewRandomSequencer() - mtu -= 12 // rtp.Header size - - return func(packet *rtp.Packet) { - if packet.Version != h264.RTPPacketVersionAVC { - handler(packet) - return - } - - payloads := payloader.Payload(mtu, packet.Payload) - last := len(payloads) - 1 - for i, payload := range payloads { - clone := rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: i == last, - SequenceNumber: sequencer.NextSequenceNumber(), - Timestamp: packet.Timestamp, - }, - Payload: payload, - } - handler(&clone) - } - } -} - -// SafariPay - generate Safari friendly payload for H265 -// https://github.com/AlexxIT/Blog/issues/5 -func SafariPay(mtu uint16, handler core.HandlerFunc) core.HandlerFunc { - sequencer := rtp.NewRandomSequencer() - size := int(mtu - 12) // rtp.Header size - - return func(packet *rtp.Packet) { - if packet.Version != h264.RTPPacketVersionAVC { - handler(packet) - return - } - - // protect original packets from modification - au := make([]byte, len(packet.Payload)) - copy(au, packet.Payload) - - var start byte - - for i := 0; i < len(au); { - size := int(binary.BigEndian.Uint32(au[i:])) + 4 - - // convert AVC to Annex-B - au[i] = 0 - au[i+1] = 0 - au[i+2] = 0 - au[i+3] = 1 - - switch NALUType(au[i:]) { - case NALUTypeIFrame, NALUTypeIFrame2, NALUTypeIFrame3: - start = 3 - default: - if start == 0 { - start = 2 - } - } - - i += size - } - - // rtp.Packet payload - b := make([]byte, 1, size) - size-- // minus header byte - - for au != nil { - b[0] = start - - if start > 1 { - start -= 2 - } - - if len(au) > size { - b = append(b, au[:size]...) - au = au[size:] - } else { - b = append(b, au...) - au = nil - } - - clone := rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: au == nil, - SequenceNumber: sequencer.NextSequenceNumber(), - Timestamp: packet.Timestamp, - }, - Payload: b, - } - handler(&clone) - - b = b[:1] // clear buffer - } - } -} diff --git a/codec/h265/sps.go b/codec/h265/sps.go deleted file mode 100644 index 5f61363b..00000000 --- a/codec/h265/sps.go +++ /dev/null @@ -1,126 +0,0 @@ -package h265 - -import ( - "bytes" - - "github.com/AlexxIT/go2rtc/pkg/bits" -) - -// http://www.itu.int/rec/T-REC-H.265 - -//goland:noinspection GoSnakeCaseUsage -type SPS struct { - sps_video_parameter_set_id uint8 - sps_max_sub_layers_minus1 uint8 - sps_temporal_id_nesting_flag byte - - general_profile_space uint8 - general_tier_flag byte - general_profile_idc uint8 - general_profile_compatibility_flags uint32 - - general_level_idc uint8 - sub_layer_profile_present_flag []byte - sub_layer_level_present_flag []byte - - sps_seq_parameter_set_id uint32 - chroma_format_idc uint32 - separate_colour_plane_flag byte - - pic_width_in_luma_samples uint32 - pic_height_in_luma_samples uint32 -} - -func (s *SPS) Width() uint16 { - return uint16(s.pic_width_in_luma_samples) -} - -func (s *SPS) Height() uint16 { - return uint16(s.pic_height_in_luma_samples) -} - -func DecodeSPS(nalu []byte) *SPS { - rbsp := bytes.ReplaceAll(nalu[2:], []byte{0, 0, 3}, []byte{0, 0}) - - r := bits.NewReader(rbsp) - s := &SPS{} - - s.sps_video_parameter_set_id = r.ReadBits8(4) - s.sps_max_sub_layers_minus1 = r.ReadBits8(3) - s.sps_temporal_id_nesting_flag = r.ReadBit() - - if !s.profile_tier_level(r) { - return nil - } - - s.sps_seq_parameter_set_id = r.ReadUEGolomb() - s.chroma_format_idc = r.ReadUEGolomb() - if s.chroma_format_idc == 3 { - s.separate_colour_plane_flag = r.ReadBit() - } - - s.pic_width_in_luma_samples = r.ReadUEGolomb() - s.pic_height_in_luma_samples = r.ReadUEGolomb() - - //... - - if r.EOF { - return nil - } - - return s -} - -// profile_tier_level supports ONLY general_profile_idc == 1 -// over variants very complicated... -// -//goland:noinspection GoSnakeCaseUsage -func (s *SPS) profile_tier_level(r *bits.Reader) bool { - s.general_profile_space = r.ReadBits8(2) - s.general_tier_flag = r.ReadBit() - s.general_profile_idc = r.ReadBits8(5) - - s.general_profile_compatibility_flags = r.ReadBits(32) - _ = r.ReadBits64(48) // other flags - - if s.general_profile_idc != 1 { - return false - } - - s.general_level_idc = r.ReadBits8(8) - - s.sub_layer_profile_present_flag = make([]byte, s.sps_max_sub_layers_minus1) - s.sub_layer_level_present_flag = make([]byte, s.sps_max_sub_layers_minus1) - - for i := byte(0); i < s.sps_max_sub_layers_minus1; i++ { - s.sub_layer_profile_present_flag[i] = r.ReadBit() - s.sub_layer_level_present_flag[i] = r.ReadBit() - } - - if s.sps_max_sub_layers_minus1 > 0 { - for i := s.sps_max_sub_layers_minus1; i < 8; i++ { - _ = r.ReadBits8(2) // reserved_zero_2bits - } - } - - for i := byte(0); i < s.sps_max_sub_layers_minus1; i++ { - if s.sub_layer_profile_present_flag[i] != 0 { - _ = r.ReadBits8(2) // sub_layer_profile_space - _ = r.ReadBit() // sub_layer_tier_flag - sub_layer_profile_idc := r.ReadBits8(5) // sub_layer_profile_idc - - _ = r.ReadBits(32) // sub_layer_profile_compatibility_flag - _ = r.ReadBits64(48) // other flags - - if sub_layer_profile_idc != 1 { - return false - } - } - - if s.sub_layer_level_present_flag[i] != 0 { - _ = r.ReadBits8(8) - } - } - - return true -} diff --git a/codec/opus/opus_packet.go b/codec/opus/opus_packet.go deleted file mode 100644 index d3e53d59..00000000 --- a/codec/opus/opus_packet.go +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package opus - -import "errors" - -var ( - errShortPacket = errors.New("packet is not large enough") - errNilPacket = errors.New("invalid nil packet") - errTooManyPDiff = errors.New("too many PDiff") - errTooManySpatialLayers = errors.New("too many spatial layers") - errUnhandledNALUType = errors.New("NALU Type is unhandled") - - // AV1 Errors - errIsKeyframeAndFragment = errors.New("bits Z and N are set. Not possible to have OBU be tail fragment and be keyframe") -) - -// audioDepacketizer is a mixin for audio codec depacketizers -type audioDepacketizer struct{} - -func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool { - return true -} - -func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { - return true -} - -// Payloader payloads Opus packets -type Payloader struct{} - -// Payload fragments an Opus packet across one or more byte arrays -func (p *Payloader) Payload(_ uint16, payload []byte) [][]byte { - if payload == nil { - return [][]byte{} - } - - out := make([]byte, len(payload)) - copy(out, payload) - return [][]byte{out} -} - -// OpusPacket represents the Opus header that is stored in the payload of an RTP Packet -type OpusPacket struct { - Payload []byte - - audioDepacketizer -} - -// Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon -func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) { - if packet == nil { - return nil, errNilPacket - } else if len(packet) == 0 { - return nil, errShortPacket - } - - p.Payload = packet - return packet, nil -} - -// OpusPartitionHeadChecker checks Opus partition head. -// -// Deprecated: replaced by OpusPacket.IsPartitionHead() -type OpusPartitionHeadChecker struct{} - -// IsPartitionHead checks whether if this is a head of the Opus partition. -// -// Deprecated: replaced by OpusPacket.IsPartitionHead() -func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool { - return (&OpusPacket{}).IsPartitionHead(packet) -}