mirror of
https://github.com/thinkonmay/sunshine-sdk.git
synced 2026-01-01 04:11:19 +00:00
rm codecs
This commit is contained in:
parent
bedb1572d8
commit
a2457a56ec
@ -1,85 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// 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
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,203 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// 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
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package codec
|
||||
|
||||
|
||||
|
||||
type Payloader interface {
|
||||
Payload(mtu uint16, payload []byte) [][]byte
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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?
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// 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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user