tangled
alpha
login
or
join now
stream.place
/
streamplace
74
fork
atom
Live video on the AT Protocol
74
fork
atom
overview
issues
1
pulls
pipelines
media: add rtmp push
Eli Mallon
6 months ago
bb37b591
e2afb222
+172
3 changed files
expand all
collapse all
unified
split
Makefile
pkg
director
stream_session.go
media
rtmp_push.go
+2
Makefile
···
176
176
-D "gst-plugins-bad:mpegtsdemux=enabled" \
177
177
-D "gst-plugins-bad:codectimestamper=enabled" \
178
178
-D "gst-plugins-bad:opus=enabled" \
179
179
+
-D "gst-plugins-bad:rtmp2=enabled" \
180
180
+
-D "gst-plugins-good:flv=enabled" \
179
181
-D "gst-plugins-ugly:x264=enabled" \
180
182
-D "gst-plugins-ugly:gpl=enabled" \
181
183
-D "x264:asm=enabled" \
+18
pkg/director/stream_session.go
···
101
101
102
102
close(ss.started)
103
103
104
104
+
ss.Go(ctx, func() error {
105
105
+
for {
106
106
+
err := ss.mm.RTMPPush(ctx, spseg.Creator, "source", "rtmp://localhost:21935/live/live")
107
107
+
if err != nil {
108
108
+
log.Error(ctx, "failed to push to RTMP server", "error", err)
109
109
+
}
110
110
+
if ctx.Err() != nil {
111
111
+
return nil
112
112
+
}
113
113
+
select {
114
114
+
case <-ctx.Done():
115
115
+
return nil
116
116
+
case <-time.After(time.Second * 5):
117
117
+
continue
118
118
+
}
119
119
+
}
120
120
+
})
121
121
+
104
122
for {
105
123
select {
106
124
case <-ss.segmentChan:
+152
pkg/media/rtmp_push.go
···
1
1
+
package media
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"fmt"
6
6
+
"strings"
7
7
+
8
8
+
"github.com/go-gst/go-gst/gst"
9
9
+
"github.com/google/uuid"
10
10
+
"stream.place/streamplace/pkg/bus"
11
11
+
"stream.place/streamplace/pkg/log"
12
12
+
)
13
13
+
14
14
+
// This function remains in scope for the duration of a single users' playback
15
15
+
func (mm *MediaManager) RTMPPush(ctx context.Context, user string, rendition string, url string) error {
16
16
+
uu, err := uuid.NewV7()
17
17
+
if err != nil {
18
18
+
return err
19
19
+
}
20
20
+
ctx, cancel := context.WithCancel(ctx)
21
21
+
defer cancel()
22
22
+
ctx = log.WithLogValues(ctx, "webrtcID", uu.String())
23
23
+
ctx = log.WithLogValues(ctx, "mediafunc", "RTMPPush")
24
24
+
25
25
+
pipelineSlice := []string{
26
26
+
"flvmux name=muxer ! rtmp2sink name=rtmp2sink",
27
27
+
"h264parse name=videoparse ! muxer.video",
28
28
+
"opusparse name=audioparse ! opusdec ! fdkaacenc ! muxer.audio",
29
29
+
}
30
30
+
31
31
+
pipeline, err := gst.NewPipelineFromString(strings.Join(pipelineSlice, "\n"))
32
32
+
if err != nil {
33
33
+
return fmt.Errorf("failed to create GStreamer pipeline: %w", err) //nolint:all
34
34
+
}
35
35
+
36
36
+
rtmp2sink, err := pipeline.GetElementByName("rtmp2sink")
37
37
+
if err != nil {
38
38
+
return fmt.Errorf("failed to get rtmp2sink element from pipeline: %w", err)
39
39
+
}
40
40
+
err = rtmp2sink.SetProperty("location", url)
41
41
+
if err != nil {
42
42
+
return fmt.Errorf("failed to set rtmp2sink location: %w", err)
43
43
+
}
44
44
+
45
45
+
segBuffer := make(chan *bus.Seg, 1024)
46
46
+
go func() {
47
47
+
segChan := mm.bus.SubscribeSegment(ctx, user, rendition)
48
48
+
defer mm.bus.UnsubscribeSegment(ctx, user, rendition, segChan)
49
49
+
for {
50
50
+
select {
51
51
+
case <-ctx.Done():
52
52
+
log.Debug(ctx, "exiting segment reader")
53
53
+
return
54
54
+
case file := <-segChan.C:
55
55
+
log.Debug(ctx, "got segment", "file", file.Filepath)
56
56
+
segBuffer <- file
57
57
+
}
58
58
+
}
59
59
+
}()
60
60
+
61
61
+
segCh := make(chan *bus.Seg)
62
62
+
go func() {
63
63
+
for {
64
64
+
select {
65
65
+
case <-ctx.Done():
66
66
+
log.Debug(ctx, "exiting segment reader")
67
67
+
return
68
68
+
case seg := <-segBuffer:
69
69
+
select {
70
70
+
case <-ctx.Done():
71
71
+
return
72
72
+
case segCh <- seg:
73
73
+
}
74
74
+
}
75
75
+
}
76
76
+
}()
77
77
+
78
78
+
concatBin, err := ConcatBin(ctx, segCh)
79
79
+
if err != nil {
80
80
+
return fmt.Errorf("failed to create concat bin: %w", err)
81
81
+
}
82
82
+
83
83
+
err = pipeline.Add(concatBin.Element)
84
84
+
if err != nil {
85
85
+
return fmt.Errorf("failed to add concat bin to pipeline: %w", err)
86
86
+
}
87
87
+
88
88
+
videoPad := concatBin.GetStaticPad("video_0")
89
89
+
if videoPad == nil {
90
90
+
return fmt.Errorf("video pad not found")
91
91
+
}
92
92
+
93
93
+
audioPad := concatBin.GetStaticPad("audio_0")
94
94
+
if audioPad == nil {
95
95
+
return fmt.Errorf("audio pad not found")
96
96
+
}
97
97
+
98
98
+
// queuePadVideo := outputQueue.GetRequestPad("src_%u")
99
99
+
// if queuePadVideo == nil {
100
100
+
// return fmt.Errorf("failed to get queue video pad")
101
101
+
// }
102
102
+
// queuePadAudio := outputQueue.GetRequestPad("src_%u")
103
103
+
// if queuePadAudio == nil {
104
104
+
// return fmt.Errorf("failed to get queue audio pad")
105
105
+
// }
106
106
+
107
107
+
videoParse, err := pipeline.GetElementByName("videoparse")
108
108
+
if err != nil {
109
109
+
return fmt.Errorf("failed to get video sink element from pipeline: %w", err)
110
110
+
}
111
111
+
videoParsePad := videoParse.GetStaticPad("sink")
112
112
+
if videoParsePad == nil {
113
113
+
return fmt.Errorf("video parse pad not found")
114
114
+
}
115
115
+
linked := videoPad.Link(videoParsePad)
116
116
+
if linked != gst.PadLinkOK {
117
117
+
return fmt.Errorf("failed to link video pad to video parse pad: %v", linked)
118
118
+
}
119
119
+
120
120
+
audioParse, err := pipeline.GetElementByName("audioparse")
121
121
+
if err != nil {
122
122
+
return fmt.Errorf("failed to get audio parse element from pipeline: %w", err)
123
123
+
}
124
124
+
audioParsePad := audioParse.GetStaticPad("sink")
125
125
+
if audioParsePad == nil {
126
126
+
return fmt.Errorf("audio parse pad not found")
127
127
+
}
128
128
+
linked = audioPad.Link(audioParsePad)
129
129
+
if linked != gst.PadLinkOK {
130
130
+
return fmt.Errorf("failed to link audio pad to audio parse pad: %v", linked)
131
131
+
}
132
132
+
133
133
+
errCh := make(chan error)
134
134
+
go func() {
135
135
+
err := HandleBusMessages(ctx, pipeline)
136
136
+
errCh <- err
137
137
+
}()
138
138
+
139
139
+
err = pipeline.SetState(gst.StatePlaying)
140
140
+
if err != nil {
141
141
+
return fmt.Errorf("failed to set pipeline state to playing: %w", err)
142
142
+
}
143
143
+
144
144
+
defer func() {
145
145
+
err = pipeline.SetState(gst.StateNull)
146
146
+
if err != nil {
147
147
+
log.Error(ctx, "failed to set pipeline state to null", "error", err)
148
148
+
}
149
149
+
}()
150
150
+
151
151
+
return <-errCh
152
152
+
}