11package flv
22
33import (
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
1313type Muxer struct {
@@ -20,50 +20,69 @@ const (
2020)
2121
2222func (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
7998func (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 )
0 commit comments