rm codecs

This commit is contained in:
pigeatgarlic 2025-01-09 05:05:46 +00:00
parent bedb1572d8
commit a2457a56ec
23 changed files with 0 additions and 2667 deletions

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -1,7 +0,0 @@
package codec
type Payloader interface {
Payload(mtu uint16, payload []byte) [][]byte
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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
}
}
}

View File

@ -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?
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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)
}