Skip to content

Commit acdac0c

Browse files
committed
refactor: Pi HW fixes
1 parent beb8204 commit acdac0c

File tree

5 files changed

+241
-124
lines changed

5 files changed

+241
-124
lines changed

pkg/flv/muxer.go

Lines changed: 136 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package flv
22

33
import (
4-
"encoding/binary"
5-
"encoding/hex"
4+
"encoding/binary"
5+
"encoding/hex"
66

7-
"github.com/AlexxIT/go2rtc/pkg/core"
8-
"github.com/AlexxIT/go2rtc/pkg/flv/amf"
9-
"github.com/AlexxIT/go2rtc/pkg/h264"
10-
"github.com/pion/rtp"
7+
"github.com/AlexxIT/go2rtc/pkg/core"
8+
"github.com/AlexxIT/go2rtc/pkg/flv/amf"
9+
"github.com/AlexxIT/go2rtc/pkg/h264"
10+
"github.com/pion/rtp"
1111
)
1212

1313
type Muxer struct {
@@ -20,50 +20,69 @@ const (
2020
)
2121

2222
func (m *Muxer) GetInit() []byte {
23-
b := []byte{
24-
'F', 'L', 'V', // signature
25-
1, // version
26-
0, // flags (has video/audio)
27-
0, 0, 0, 9, // header size
28-
0, 0, 0, 0, // tag 0 size
29-
}
30-
31-
obj := map[string]any{}
32-
33-
for _, codec := range m.codecs {
34-
switch codec.Name {
35-
case core.CodecH264:
36-
b[4] |= FlagsVideo
37-
obj["videocodecid"] = CodecAVC
38-
39-
case core.CodecAAC:
40-
b[4] |= FlagsAudio
41-
obj["audiocodecid"] = CodecAAC
42-
obj["audiosamplerate"] = codec.ClockRate
43-
obj["audiosamplesize"] = 16
44-
obj["stereo"] = codec.Channels == 2
45-
}
46-
}
47-
48-
data := amf.EncodeItems("@setDataFrame", "onMetaData", obj)
49-
b = append(b, EncodeTag(TagData, 0, data)...)
50-
51-
for _, codec := range m.codecs {
52-
switch codec.Name {
53-
case core.CodecH264:
54-
sps, pps := h264.GetParameterSet(codec.FmtpLine)
55-
if len(sps) == 0 {
56-
sps = []byte{0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2}
57-
} else {
58-
h264.FixPixFmt(sps)
59-
}
60-
if len(pps) == 0 {
61-
pps = []byte{0x68, 0xce, 0x38, 0x80}
62-
}
63-
64-
config := h264.EncodeConfig(sps, pps)
65-
video := append(encodeAVData(codec, 0), config...)
66-
b = append(b, EncodeTag(TagVideo, 0, video)...)
23+
b := []byte{
24+
'F', 'L', 'V', // signature
25+
1, // version
26+
0, // flags (has video/audio)
27+
0, 0, 0, 9, // header size
28+
0, 0, 0, 0, // tag 0 size
29+
}
30+
31+
obj := map[string]any{}
32+
33+
var metaWidth, metaHeight uint16
34+
var metaFPS float64
35+
36+
for _, codec := range m.codecs {
37+
switch codec.Name {
38+
case core.CodecH264:
39+
b[4] |= FlagsVideo
40+
obj["videocodecid"] = CodecAVC
41+
42+
// Try to extract width/height and optional FPS from SPS
43+
if sps, _ := h264.GetParameterSet(codec.FmtpLine); len(sps) > 0 {
44+
if s := h264.DecodeSPS(sps); s != nil {
45+
if metaWidth == 0 || metaHeight == 0 {
46+
metaWidth = s.Width()
47+
metaHeight = s.Height()
48+
}
49+
if f := s.FPS(); f > 0 {
50+
metaFPS = f
51+
}
52+
}
53+
}
54+
55+
case core.CodecAAC:
56+
b[4] |= FlagsAudio
57+
obj["audiocodecid"] = CodecAAC
58+
obj["audiosamplerate"] = codec.ClockRate
59+
obj["audiosamplesize"] = 16
60+
obj["stereo"] = codec.Channels == 2
61+
}
62+
}
63+
64+
// Fill optional width/height/framerate if known
65+
if metaWidth > 0 && metaHeight > 0 {
66+
obj["width"] = metaWidth
67+
obj["height"] = metaHeight
68+
}
69+
if metaFPS > 0 {
70+
obj["framerate"] = metaFPS
71+
}
72+
73+
data := amf.EncodeItems("@setDataFrame", "onMetaData", obj)
74+
b = append(b, EncodeTag(TagData, 0, data)...)
75+
76+
for _, codec := range m.codecs {
77+
switch codec.Name {
78+
case core.CodecH264:
79+
sps, pps := h264.GetParameterSet(codec.FmtpLine)
80+
if len(sps) > 0 && len(pps) > 0 {
81+
h264.FixPixFmt(sps)
82+
config := h264.EncodeConfig(sps, pps)
83+
video := append(encodeAVData(codec, 0), config...)
84+
b = append(b, EncodeTag(TagVideo, 0, video)...)
85+
}
6786

6887
case core.CodecAAC:
6988
s := core.Between(codec.FmtpLine, "config=", ";")
@@ -77,31 +96,73 @@ func (m *Muxer) GetInit() []byte {
7796
}
7897

7998
func (m *Muxer) GetPayloader(codec *core.Codec) func(packet *rtp.Packet) []byte {
80-
m.codecs = append(m.codecs, codec)
81-
82-
var ts0 uint32
83-
var k = codec.ClockRate / 1000
84-
85-
switch codec.Name {
86-
case core.CodecH264:
87-
buf := encodeAVData(codec, 1)
88-
89-
return func(packet *rtp.Packet) []byte {
90-
if h264.IsKeyframe(packet.Payload) {
91-
buf[0] = 1<<4 | 7
92-
} else {
93-
buf[0] = 2<<4 | 7
94-
}
95-
96-
buf = append(buf[:5], packet.Payload...) // reset buffer to previous place
97-
98-
if ts0 == 0 {
99-
ts0 = packet.Timestamp
100-
}
101-
102-
timeMS := (packet.Timestamp - ts0) / k
103-
return EncodeTag(TagVideo, timeMS, buf)
104-
}
99+
m.codecs = append(m.codecs, codec)
100+
101+
var ts0 uint32
102+
var k = codec.ClockRate / 1000
103+
104+
switch codec.Name {
105+
case core.CodecH264:
106+
buf := encodeAVData(codec, 1)
107+
// Some RTSP servers (FFmpeg) don't provide sprop-parameter-sets in SDP.
108+
// That makes initial FLV sequence header fallback to a generic SPS/PPS,
109+
// which can confuse some RTMP ingests. Emit a real AVC sequence header
110+
// once we see SPS/PPS inside the first Access Unit.
111+
var sentRealHeader bool
112+
113+
return func(packet *rtp.Packet) []byte {
114+
var header []byte
115+
if !sentRealHeader {
116+
// Try to extract SPS/PPS from the current AVCC payload
117+
var sps, pps []byte
118+
for _, nalu := range h264.SplitNALU(packet.Payload) {
119+
switch h264.NALUType(nalu) {
120+
case h264.NALUTypeSPS:
121+
sps = nalu[4:]
122+
case h264.NALUTypePPS:
123+
pps = nalu[4:]
124+
}
125+
}
126+
if len(sps) > 0 && len(pps) > 0 {
127+
conf := h264.EncodeConfig(sps, pps)
128+
hdr := append(encodeAVData(codec, 0), conf...)
129+
// Propagate discovered SPS/PPS into codec fmtp so late joiners (e.g., WebRTC)
130+
// have sprop-parameter-sets available.
131+
if c := h264.ConfigToCodec(conf); c != nil {
132+
codec.FmtpLine = c.FmtpLine
133+
}
134+
if ts0 == 0 {
135+
ts0 = packet.Timestamp
136+
}
137+
timeMS := (packet.Timestamp - ts0) / k
138+
header = EncodeTag(TagVideo, timeMS, hdr)
139+
sentRealHeader = true
140+
}
141+
}
142+
143+
if h264.IsKeyframe(packet.Payload) {
144+
buf[0] = 1<<4 | 7
145+
} else {
146+
buf[0] = 2<<4 | 7
147+
}
148+
149+
buf = append(buf[:5], packet.Payload...) // reset buffer to previous place
150+
151+
if ts0 == 0 {
152+
ts0 = packet.Timestamp
153+
}
154+
155+
timeMS := (packet.Timestamp - ts0) / k
156+
frame := EncodeTag(TagVideo, timeMS, buf)
157+
if len(header) > 0 {
158+
// Emit real config immediately before the first frame containing SPS/PPS
159+
out := make([]byte, 0, len(header)+len(frame))
160+
out = append(out, header...)
161+
out = append(out, frame...)
162+
return out
163+
}
164+
return frame
165+
}
105166

106167
case core.CodecAAC:
107168
buf := encodeAVData(codec, 1)

pkg/h264/payloader.go

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import "encoding/binary"
44

55
// Payloader payloads H264 packets
66
type Payloader struct {
7-
IsAVC bool
8-
stapANalu []byte
7+
IsAVC bool
8+
stapANalu []byte
9+
// Persist latest SPS/PPS across calls to prepend on IDR frames
10+
lastSPS []byte
11+
lastPPS []byte
912
}
1013

1114
const (
@@ -96,26 +99,41 @@ func (p *Payloader) Payload(mtu uint16, payload []byte) [][]byte {
9699
naluType := nalu[0] & naluTypeBitmask
97100
naluRefIdc := nalu[0] & naluRefIdcBitmask
98101

99-
switch naluType {
100-
case audNALUType, fillerNALUType:
101-
return
102-
case spsNALUType, ppsNALUType:
103-
if p.stapANalu == nil {
104-
p.stapANalu = []byte{outputStapAHeader}
105-
}
106-
p.stapANalu = append(p.stapANalu, byte(len(nalu)>>8), byte(len(nalu)))
107-
p.stapANalu = append(p.stapANalu, nalu...)
108-
return
109-
}
110-
111-
if p.stapANalu != nil {
112-
// Pack current NALU with SPS and PPS as STAP-A
113-
// Supports multiple PPS in a row
114-
if len(p.stapANalu) <= int(mtu) {
115-
payloads = append(payloads, p.stapANalu)
116-
}
117-
p.stapANalu = nil
118-
}
102+
switch naluType {
103+
case audNALUType, fillerNALUType:
104+
return
105+
case spsNALUType, ppsNALUType:
106+
// Store latest SPS/PPS for future IDR frames
107+
if naluType == spsNALUType {
108+
p.lastSPS = append(p.lastSPS[:0], nalu...)
109+
} else {
110+
p.lastPPS = append(p.lastPPS[:0], nalu...)
111+
}
112+
if p.stapANalu == nil {
113+
p.stapANalu = []byte{outputStapAHeader}
114+
}
115+
p.stapANalu = append(p.stapANalu, byte(len(nalu)>>8), byte(len(nalu)))
116+
p.stapANalu = append(p.stapANalu, nalu...)
117+
return
118+
}
119+
120+
// If this is an IDR without in-band SPS/PPS, prepend last known SPS/PPS
121+
if naluType == NALUTypeIFrame && p.stapANalu == nil && len(p.lastSPS) > 0 && len(p.lastPPS) > 0 {
122+
p.stapANalu = []byte{outputStapAHeader}
123+
p.stapANalu = append(p.stapANalu, byte(len(p.lastSPS)>>8), byte(len(p.lastSPS)))
124+
p.stapANalu = append(p.stapANalu, p.lastSPS...)
125+
p.stapANalu = append(p.stapANalu, byte(len(p.lastPPS)>>8), byte(len(p.lastPPS)))
126+
p.stapANalu = append(p.stapANalu, p.lastPPS...)
127+
}
128+
129+
if p.stapANalu != nil {
130+
// Pack current NALU with SPS and PPS as STAP-A
131+
// Supports multiple PPS in a row
132+
if len(p.stapANalu) <= int(mtu) {
133+
payloads = append(payloads, p.stapANalu)
134+
}
135+
p.stapANalu = nil
136+
}
119137

120138
// Single NALU
121139
if len(nalu) <= int(mtu) {

pkg/h264/rtp.go

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,48 @@ const RTPPacketVersionAVC = 0
1414
const PSMaxSize = 128 // the biggest SPS I've seen is 48 (EZVIZ CS-CV210)
1515

1616
func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
17-
depack := &codecs.H264Packet{IsAVC: true}
17+
depack := &codecs.H264Packet{IsAVC: true}
1818

19-
sps, pps := GetParameterSet(codec.FmtpLine)
20-
ps := JoinNALU(sps, pps)
19+
sps, pps := GetParameterSet(codec.FmtpLine)
20+
ps := JoinNALU(sps, pps)
21+
// Track latest SPS/PPS observed in-band and use them if SDP had none
22+
var spsLast, ppsLast []byte
2123

2224
buf := make([]byte, 0, 512*1024) // 512K
2325

24-
return func(packet *rtp.Packet) {
26+
return func(packet *rtp.Packet) {
2527
//log.Printf("[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, %v", codec.Name, packet.Payload[0]&0x1F, len(packet.Payload), packet.Timestamp, packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker)
2628

2729
payload, err := depack.Unmarshal(packet.Payload)
2830
if len(payload) == 0 || err != nil {
2931
return
3032
}
3133

32-
// Memory overflow protection. Can happen if we miss a lot of packets with the marker.
33-
// https://github.com/AlexxIT/go2rtc/issues/675
34-
if len(buf) > 5*1024*1024 {
35-
buf = buf[: 0 : 512*1024]
36-
}
34+
// Memory overflow protection. Can happen if we miss a lot of packets with the marker.
35+
// https://github.com/AlexxIT/go2rtc/issues/675
36+
if len(buf) > 5*1024*1024 {
37+
buf = buf[: 0 : 512*1024]
38+
}
39+
40+
// Capture SPS/PPS from current payload (AVCC) to update parameter sets dynamically
41+
// This helps when remote SDP doesn't provide sprop-parameter-sets
42+
for off := 0; off+4 <= len(payload); {
43+
size := 4 + int(binary.BigEndian.Uint32(payload[off:]))
44+
if off+size > len(payload) || size <= 4 {
45+
break
46+
}
47+
nalu := payload[off : off+size]
48+
switch NALUType(nalu) {
49+
case NALUTypeSPS:
50+
spsLast = append(spsLast[:0], nalu[4:]...)
51+
case NALUTypePPS:
52+
ppsLast = append(ppsLast[:0], nalu[4:]...)
53+
}
54+
off += size
55+
}
56+
if len(spsLast) > 0 && len(ppsLast) > 0 {
57+
ps = JoinNALU(spsLast, ppsLast)
58+
}
3759

3860
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
3961
// Reolink Duo 2: sends SPS with Marker and PPS without
@@ -49,14 +71,16 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
4971
}
5072
}
5173

52-
if len(buf) == 0 {
53-
for {
74+
if len(buf) == 0 {
75+
for {
5476
// Amcrest IP4M-1051: 9, 7, 8, 6, 28...
5577
// Amcrest IP4M-1051: 9, 6, 1
5678
switch NALUType(payload) {
57-
case NALUTypeIFrame:
58-
// fix IFrame without SPS,PPS
59-
buf = append(buf, ps...)
79+
case NALUTypeIFrame:
80+
// fix IFrame without SPS,PPS (use latest known)
81+
if len(ps) > 0 {
82+
buf = append(buf, ps...)
83+
}
6084
case NALUTypeSEI, NALUTypeAUD:
6185
// fix ffmpeg with transcoding first frame
6286
i := int(4 + binary.BigEndian.Uint32(payload))

0 commit comments

Comments
 (0)