install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,54 @@
|
||||
package h265
|
||||
|
||||
import "github.com/AlexxIT/go2rtc/pkg/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
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Package h265 - AVCC format related functions
|
||||
package h265
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package h265
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeSPS(t *testing.T) {
|
||||
s := "QgEBAWAAAAMAAAMAAAMAAAMAmaAAoAgBaH+KrTuiS7/8AAQABbAgApMuADN/mAE="
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
sps := DecodeSPS(b)
|
||||
require.NotNil(t, sps)
|
||||
require.Equal(t, uint16(5120), sps.Width())
|
||||
require.Equal(t, uint16(1440), sps.Height())
|
||||
}
|
||||
|
||||
func TestDecodeSPS2(t *testing.T) {
|
||||
s := "QgEBIUAAAAMAkAAAAwAAAwCWoAUCAWlnpbkShc1AQIC4QAAAAwBAAAAFFEn/eEAOpgAV+V8IBBA="
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
sps := DecodeSPS(b)
|
||||
require.NotNil(t, sps)
|
||||
require.Equal(t, uint16(640), sps.Width())
|
||||
require.Equal(t, uint16(360), sps.Height())
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package h265
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/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, true, 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
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package h265
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
vps, sps, pps := GetParameterSet(codec.FmtpLine)
|
||||
ps := h264.JoinNALU(vps, sps, pps)
|
||||
|
||||
buf := make([]byte, 0, 512*1024) // 512K
|
||||
var nuStart int
|
||||
var seqNum uint16
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// when we collect data into one buffer, we need to make sure
|
||||
// that all of it falls into the same sequence
|
||||
if len(buf) > 0 && packet.SequenceNumber-seqNum != 1 {
|
||||
//log.Printf("broken H265 sequence")
|
||||
buf = buf[:0] // drop data
|
||||
return
|
||||
}
|
||||
|
||||
seqNum = packet.SequenceNumber
|
||||
|
||||
if nuType == NALUTypeFU {
|
||||
switch data[2] >> 6 {
|
||||
case 0b10: // 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 0b00: // continue
|
||||
if len(buf) == 0 {
|
||||
//log.Printf("broken H265 fragment")
|
||||
return
|
||||
}
|
||||
|
||||
buf = append(buf, data[3:]...)
|
||||
return
|
||||
case 0b01: // end
|
||||
if len(buf) == 0 {
|
||||
//log.Printf("broken H265 fragment")
|
||||
return
|
||||
}
|
||||
|
||||
buf = append(buf, data[3:]...)
|
||||
|
||||
if nuStart > len(buf)+4 {
|
||||
//log.Printf("broken H265 fragment")
|
||||
buf = buf[:0] // drop data
|
||||
return
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(buf[nuStart:], uint32(len(buf)-nuStart-4))
|
||||
case 0b11: // 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 {
|
||||
buf = binary.BigEndian.AppendUint32(buf, uint32(len(data))) // NAL unit size
|
||||
buf = append(buf, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user