Rewrite AnnexB/AVCC parsers
This commit is contained in:
@@ -82,6 +82,13 @@ func (r *Reader) ReadBits16(n byte) (res uint16) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBits64(n byte) (res uint64) {
|
||||||
|
for i := n - 1; i != 255; i-- {
|
||||||
|
res |= uint64(r.ReadBit()) << i
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// ReadUEGolomb - ReadExponentialGolomb (unsigned)
|
// ReadUEGolomb - ReadExponentialGolomb (unsigned)
|
||||||
func (r *Reader) ReadUEGolomb() uint32 {
|
func (r *Reader) ReadUEGolomb() uint32 {
|
||||||
var size byte
|
var size byte
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
@@ -226,7 +226,7 @@ func (c *Client) Handle() error {
|
|||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Timestamp: core.Now90000(),
|
Timestamp: core.Now90000(),
|
||||||
},
|
},
|
||||||
Payload: h264.AnnexB2AVC(b[6:]),
|
Payload: annexb.EncodeToAVCC(b[6:], false),
|
||||||
}
|
}
|
||||||
c.videoTrack.WriteRTP(pkt)
|
c.videoTrack.WriteRTP(pkt)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+8
-6
@@ -8,14 +8,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
|
||||||
"github.com/pion/rtp"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@@ -173,7 +175,7 @@ func (c *Client) Handle() error {
|
|||||||
|
|
||||||
switch dataType {
|
switch dataType {
|
||||||
case 0x1FC, 0x1FE: // video IFrame
|
case 0x1FC, 0x1FE: // video IFrame
|
||||||
payload := h264.AnnexB2AVC(b[16:])
|
payload := annexb.EncodeToAVCC(b[16:], false)
|
||||||
|
|
||||||
if c.videoTrack == nil {
|
if c.videoTrack == nil {
|
||||||
fps := b[5]
|
fps := b[5]
|
||||||
@@ -208,7 +210,7 @@ func (c *Client) Handle() error {
|
|||||||
|
|
||||||
packet := &rtp.Packet{
|
packet := &rtp.Packet{
|
||||||
Header: rtp.Header{Timestamp: c.videoTS},
|
Header: rtp.Header{Timestamp: c.videoTS},
|
||||||
Payload: h264.AnnexB2AVC(b[8:]),
|
Payload: annexb.EncodeToAVCC(b[8:], false),
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("[DVR] %v, len: %d, ts: %10d", h265.Types(packet.Payload), len(packet.Payload), packet.Timestamp)
|
//log.Printf("[DVR] %v, len: %d, ts: %10d", h265.Types(packet.Payload), len(packet.Payload), packet.Timestamp)
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/aac"
|
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ func (c *Client) Describe() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
codec := avc.ConfigToCodec(b[5:])
|
codec := h264.ConfigToCodec(b[5:])
|
||||||
media := &core.Media{
|
media := &core.Media{
|
||||||
Kind: core.KindVideo,
|
Kind: core.KindVideo,
|
||||||
Direction: core.DirectionRecvonly,
|
Direction: core.DirectionRecvonly,
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ Payloader code taken from [pion](https://github.com/pion/rtp) library. And chang
|
|||||||
- [AVC profiles table](https://developer.mozilla.org/ru/docs/Web/Media/Formats/codecs_parameter)
|
- [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)
|
- [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/)
|
- [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
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// Package annexb - universal for H264 and H265
|
||||||
|
package annexb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
const StartCode = "\x00\x00\x00\x01"
|
||||||
|
const startAUD = StartCode + "\x09\xF0" + 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(startAUD) || string(b[:len(StartCode)]) != StartCode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Skip Access unit delimiter (AUD) from FFmpeg
|
||||||
|
if string(b[:len(startAUD)]) == startAUD {
|
||||||
|
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) []byte {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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(startAUD) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(startAUD); ; {
|
||||||
|
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
|
||||||
|
}
|
||||||
+1
-102
@@ -3,46 +3,12 @@ package h264
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
||||||
"github.com/pion/rtp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func AnnexB2AVC(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
|
|
||||||
}
|
|
||||||
|
|
||||||
func AVCtoAnnexB(b []byte) []byte {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const forbiddenZeroBit = 0x80
|
const forbiddenZeroBit = 0x80
|
||||||
const nalUnitType = 0x1F
|
const nalUnitType = 0x1F
|
||||||
|
|
||||||
// DecodeStream - find and return first AU in AVC format
|
// Deprecated: DecodeStream - find and return first AU in AVC format
|
||||||
// useful for processing live streams with unknown separator size
|
// useful for processing live streams with unknown separator size
|
||||||
func DecodeStream(annexb []byte) ([]byte, int) {
|
func DecodeStream(annexb []byte) ([]byte, int) {
|
||||||
startPos := -1
|
startPos := -1
|
||||||
@@ -154,70 +120,3 @@ func IndexFrom(b []byte, sep []byte, from int) int {
|
|||||||
|
|
||||||
return bytes.Index(b, sep)
|
return bytes.Index(b, sep)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncodeAVC(nals ...[]byte) (avc []byte) {
|
|
||||||
var i, n int
|
|
||||||
|
|
||||||
for _, nal := range nals {
|
|
||||||
if i = len(nal); i > 0 {
|
|
||||||
n += 4 + i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avc = make([]byte, n)
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
for _, nal := range nals {
|
|
||||||
if i = len(nal); i > 0 {
|
|
||||||
binary.BigEndian.PutUint32(avc[n:], uint32(i))
|
|
||||||
n += 4 + copy(avc[n+4:], nal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func RepairAVC(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|
||||||
sps, pps := GetParameterSet(codec.FmtpLine)
|
|
||||||
ps := EncodeAVC(sps, pps)
|
|
||||||
|
|
||||||
return func(packet *rtp.Packet) {
|
|
||||||
if NALUType(packet.Payload) == NALUTypeIFrame {
|
|
||||||
packet.Payload = Join(ps, packet.Payload)
|
|
||||||
}
|
|
||||||
handler(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SplitAVC(data []byte) [][]byte {
|
|
||||||
var nals [][]byte
|
|
||||||
for {
|
|
||||||
// get AVC length
|
|
||||||
size := int(binary.BigEndian.Uint32(data)) + 4
|
|
||||||
|
|
||||||
// check if multiple items in one packet
|
|
||||||
if size < len(data) {
|
|
||||||
nals = append(nals, data[:size])
|
|
||||||
data = data[size:]
|
|
||||||
} else {
|
|
||||||
nals = append(nals, data)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nals
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package avc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// 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,12 +1,27 @@
|
|||||||
package avc
|
package h264
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestDecodeSPS(t *testing.T) {
|
||||||
s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410
|
s := "Z0IAMukAUAHjQgAAB9IAAOqcCAA=" // Amcrest AD410
|
||||||
b, err := base64.StdEncoding.DecodeString(s)
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
@@ -14,7 +29,7 @@ func TestDecodeSPS(t *testing.T) {
|
|||||||
|
|
||||||
sps := DecodeSPS(b)
|
sps := DecodeSPS(b)
|
||||||
require.Equal(t, uint16(2560), sps.Width())
|
require.Equal(t, uint16(2560), sps.Width())
|
||||||
require.Equal(t, uint16(1920), sps.Heigth())
|
require.Equal(t, uint16(1920), sps.Height())
|
||||||
|
|
||||||
s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff
|
s = "R00AKZmgHgCJ+WEAAAMD6AAATiCE" // Sonoff
|
||||||
b, err = base64.StdEncoding.DecodeString(s)
|
b, err = base64.StdEncoding.DecodeString(s)
|
||||||
@@ -22,5 +37,5 @@ func TestDecodeSPS(t *testing.T) {
|
|||||||
|
|
||||||
sps = DecodeSPS(b)
|
sps = DecodeSPS(b)
|
||||||
require.Equal(t, uint16(1920), sps.Width())
|
require.Equal(t, uint16(1920), sps.Width())
|
||||||
require.Equal(t, uint16(1080), sps.Heigth())
|
require.Equal(t, uint16(1080), sps.Height())
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
package avc
|
// Package h264 - MPEG4 format related functions
|
||||||
|
package h264
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"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) {
|
func DecodeConfig(conf []byte) (profile []byte, sps []byte, pps []byte) {
|
||||||
if len(conf) < 6 || conf[0] != 1 {
|
if len(conf) < 6 || conf[0] != 1 {
|
||||||
return
|
return
|
||||||
+4
-2
@@ -2,7 +2,9 @@ package h264
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/rtp/codecs"
|
"github.com/pion/rtp/codecs"
|
||||||
)
|
)
|
||||||
@@ -15,7 +17,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|||||||
depack := &codecs.H264Packet{IsAVC: true}
|
depack := &codecs.H264Packet{IsAVC: true}
|
||||||
|
|
||||||
sps, pps := GetParameterSet(codec.FmtpLine)
|
sps, pps := GetParameterSet(codec.FmtpLine)
|
||||||
ps := EncodeAVC(sps, pps)
|
ps := JoinNALU(sps, pps)
|
||||||
|
|
||||||
buf := make([]byte, 0, 512*1024) // 512K
|
buf := make([]byte, 0, 512*1024) // 512K
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|||||||
// some Chinese buggy cameras has single packet with SPS+PPS+IFrame separated by 00 00 00 01
|
// 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/391
|
||||||
// https://github.com/AlexxIT/WebRTC/issues/392
|
// https://github.com/AlexxIT/WebRTC/issues/392
|
||||||
AnnexB2AVC(payload)
|
payload = annexb.EncodeToAVCC(payload, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber)
|
//log.Printf("[AVC] %v, len: %d, ts: %10d, seq: %d", Types(payload), len(payload), packet.Timestamp, packet.SequenceNumber)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package avc
|
package h264
|
||||||
|
|
||||||
import "github.com/AlexxIT/go2rtc/pkg/bits"
|
import "github.com/AlexxIT/go2rtc/pkg/bits"
|
||||||
|
|
||||||
@@ -49,11 +49,26 @@ type SPS struct {
|
|||||||
sar_height uint32
|
sar_height uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func DecodeSPS(sps []byte) *SPS {
|
||||||
r := bits.NewReader(sps)
|
r := bits.NewReader(sps)
|
||||||
|
|
||||||
hdr := r.ReadByte()
|
hdr := r.ReadByte()
|
||||||
if hdr&0x1F != 7 {
|
if hdr&0x1F != NALUTypeSPS {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,18 +162,3 @@ func DecodeSPS(sps []byte) *SPS {
|
|||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
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) Heigth() 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)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import "github.com/AlexxIT/go2rtc/pkg/h264"
|
|||||||
const forbiddenZeroBit = 0x80
|
const forbiddenZeroBit = 0x80
|
||||||
const nalUnitType = 0x3F
|
const nalUnitType = 0x3F
|
||||||
|
|
||||||
// DecodeStream - find and return first AU in AVC format
|
// Deprecated: DecodeStream - find and return first AU in AVC format
|
||||||
// useful for processing live streams with unknown separator size
|
// useful for processing live streams with unknown separator size
|
||||||
func DecodeStream(annexb []byte) ([]byte, int) {
|
func DecodeStream(annexb []byte) ([]byte, int) {
|
||||||
startPos := -1
|
startPos := -1
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Package h265 - AVCC format related functions
|
||||||
|
package h265
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,19 @@
|
|||||||
|
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())
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
package hvc
|
// Package h265 - MPEG4 format related functions
|
||||||
|
package h265
|
||||||
|
|
||||||
import "encoding/binary"
|
import "encoding/binary"
|
||||||
|
|
||||||
+126
@@ -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
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/iso"
|
"github.com/AlexxIT/go2rtc/pkg/iso"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
@@ -205,7 +205,7 @@ func (c *Client) getTracks() error {
|
|||||||
avccLen := binary.BigEndian.Uint32(msg.Data[i:])
|
avccLen := binary.BigEndian.Uint32(msg.Data[i:])
|
||||||
data = msg.Data[i+8 : i+int(avccLen)]
|
data = msg.Data[i+8 : i+int(avccLen)]
|
||||||
|
|
||||||
codec := avc.ConfigToCodec(data)
|
codec := h264.ConfigToCodec(data)
|
||||||
|
|
||||||
media := &core.Media{
|
media := &core.Media{
|
||||||
Kind: core.KindVideo,
|
Kind: core.KindVideo,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package magic
|
|||||||
import (
|
import (
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
|
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
@@ -42,7 +43,7 @@ func (k *Keyframe) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv
|
|||||||
if !h264.IsKeyframe(packet.Payload) {
|
if !h264.IsKeyframe(packet.Payload) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b := h264.AVCtoAnnexB(packet.Payload)
|
b := annexb.DecodeAVCC(packet.Payload)
|
||||||
k.Fire(b)
|
k.Fire(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv
|
|||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
||||||
} else {
|
} else {
|
||||||
handler.Handler = h264.RepairAVC(track.Codec, handler.Handler)
|
handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
case core.CodecH265:
|
case core.CodecH265:
|
||||||
|
|||||||
+4
-6
@@ -6,9 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265/hvc"
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/iso"
|
"github.com/AlexxIT/go2rtc/pkg/iso"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
@@ -74,13 +72,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
|
|||||||
pps = []byte{0x68, 0xce, 0x38, 0x80}
|
pps = []byte{0x68, 0xce, 0x38, 0x80}
|
||||||
}
|
}
|
||||||
|
|
||||||
s := avc.DecodeSPS(sps)
|
s := h264.DecodeSPS(sps)
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, errors.New("mp4: can't parse SPS")
|
return nil, errors.New("mp4: can't parse SPS")
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.WriteVideoTrack(
|
mv.WriteVideoTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), avc.EncodeConfig(sps, pps),
|
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Height(), h264.EncodeConfig(sps, pps),
|
||||||
)
|
)
|
||||||
|
|
||||||
case core.CodecH265:
|
case core.CodecH265:
|
||||||
@@ -96,13 +94,13 @@ func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
|
|||||||
pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90}
|
pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90}
|
||||||
}
|
}
|
||||||
|
|
||||||
s := avc.DecodeSPS(sps)
|
s := h265.DecodeSPS(sps)
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, errors.New("mp4: can't parse SPS")
|
return nil, errors.New("mp4: can't parse SPS")
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.WriteVideoTrack(
|
mv.WriteVideoTrack(
|
||||||
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Heigth(), hvc.EncodeConfig(vps, sps, pps),
|
uint32(i+1), codec.Name, codec.ClockRate, s.Width(), s.Height(), h265.EncodeConfig(vps, sps, pps),
|
||||||
)
|
)
|
||||||
|
|
||||||
case core.CodecAAC:
|
case core.CodecAAC:
|
||||||
|
|||||||
+2
-1
@@ -2,6 +2,7 @@ package mp4
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
@@ -101,7 +102,7 @@ func (c *Segment) AddTrack(media *core.Media, _ *core.Codec, track *core.Receive
|
|||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
||||||
} else {
|
} else {
|
||||||
handler.Handler = h264.RepairAVC(track.Codec, handler.Handler)
|
handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
case core.CodecH265:
|
case core.CodecH265:
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package mpegts
|
package mpegts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h265"
|
"github.com/AlexxIT/go2rtc/pkg/h265"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -113,7 +115,7 @@ func (p *PES) GetPacket() (pkt *rtp.Packet) {
|
|||||||
PayloadType: p.StreamType,
|
PayloadType: p.StreamType,
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
},
|
},
|
||||||
Payload: h264.AnnexB2AVC(payload),
|
Payload: annexb.EncodeToAVCC(payload, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
case StreamTypePCMATapo:
|
case StreamTypePCMATapo:
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ func (c *Consumer) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiv
|
|||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
handler.Handler = h264.RTPDepay(track.Codec, handler.Handler)
|
||||||
} else {
|
} else {
|
||||||
handler.Handler = h264.RepairAVC(track.Codec, handler.Handler)
|
handler.Handler = h264.RepairAVCC(track.Codec, handler.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
case core.CodecAAC:
|
case core.CodecAAC:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264"
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ func (c *Client) Handle() error {
|
|||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Timestamp: uint32(ts * 90000),
|
Timestamp: uint32(ts * 90000),
|
||||||
},
|
},
|
||||||
Payload: h264.AnnexB2AVC(body),
|
Payload: annexb.EncodeToAVCC(body, false),
|
||||||
}
|
}
|
||||||
video.WriteRTP(pkt)
|
video.WriteRTP(pkt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (c *Conn) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiv
|
|||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
sender.Handler = h264.RTPDepay(track.Codec, sender.Handler)
|
sender.Handler = h264.RTPDepay(track.Codec, sender.Handler)
|
||||||
} else {
|
} else {
|
||||||
sender.Handler = h264.RepairAVC(track.Codec, sender.Handler)
|
sender.Handler = h264.RepairAVCC(track.Codec, sender.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
case core.CodecH265:
|
case core.CodecH265:
|
||||||
|
|||||||
Reference in New Issue
Block a user