-
Notifications
You must be signed in to change notification settings - Fork 141
/
stream.go
243 lines (207 loc) · 6.88 KB
/
stream.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package ffmpeg
import (
"fmt"
"github.com/brutella/hc/log"
"github.com/brutella/hc/rtp"
"os/exec"
"strings"
"syscall"
)
type stream struct {
inputDevice string
inputFilename string
h264Decoder string
h264Encoder string
req rtp.SetupEndpoints
resp rtp.SetupEndpointsResponse
cmd *exec.Cmd
}
func (s *stream) isActive() bool {
return s.cmd != nil
}
func (s *stream) stop() {
log.Debug.Println("stop stream")
if s.cmd != nil {
s.cmd.Process.Signal(syscall.SIGINT)
s.cmd = nil
}
}
func (s *stream) start(video rtp.VideoParameters, audio rtp.AudioParameters) error {
log.Debug.Println("start stream")
// -vsync 2: Fixes "Frame rate very high for a muxer not efficiently supporting it."
// -framerate before -i specifies the framerate for the input, after -i sets it for the output https://stackoverflow.com/questions/38498599/webcam-with-ffmpeg-on-mac-selected-framerate-29-970030-is-not-supported-by-th#38549528
// ffmpeg -i input.jpg -vf scale=w=320:h=240:force_original_aspect_ratio=decrease output_320.png
ffmpegVideo := fmt.Sprintf("-f %s", s.inputDevice) +
fmt.Sprintf(" -framerate %d", s.framerate(video.Attributes)) +
fmt.Sprintf("%s", s.videoDecoderOption(video)) +
fmt.Sprintf(" -i %s", s.inputFilename) +
" -an" +
fmt.Sprintf(" -codec:v %s", s.videoEncoder(video)) +
" -pix_fmt yuv420p -vsync 2" +
// height "-2" keeps the aspect ratio
fmt.Sprintf(" -video_size %d:-2", video.Attributes.Width) +
fmt.Sprintf(" -framerate %d", video.Attributes.Framerate) +
// 2018-08-18 (mah) Disable profile arguments because it cannot be parsed
// [h264_omx @ 0x93a410] [Eval @ 0xbeaad160] Undefined constant or missing '(' in 'high'
// fmt.Sprintf(" -profile:v %s", videoProfile(video.CodecParams)) +
fmt.Sprintf(" -level:v %s", videoLevel(video.CodecParams)) +
" -f rawvideo -tune zerolatency" +
fmt.Sprintf(" -b:v %dk -bufsize %dk", video.RTP.Bitrate, video.RTP.Bitrate) +
fmt.Sprintf(" -payload_type %d", video.RTP.PayloadType) +
fmt.Sprintf(" -ssrc %d", s.resp.SsrcVideo) +
" -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
fmt.Sprintf(" -srtp_out_params %s", s.req.Video.SrtpKey()) +
fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&pkt_size=%s&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, videoMTU(s.req))
// FIXME (mah) Audio doesn't work yet
// ffmpegAudio := "-vn" +
// fmt.Sprintf(" %s", audioCodecOption(audio)) +
// // compression-level 0-10 (fastest-slowest)
// fmt.Sprintf(" -b:a %dk -bufsize 48k", audio.RTP.Bitrate) +
// fmt.Sprintf(" -ar %s", audioSamplingRate(audio)) +
// fmt.Sprintf(" -payload_type %d", audio.RTP.PayloadType) +
// fmt.Sprintf(" -ssrc %d", s.resp.SsrcAudio) +
// " -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
// fmt.Sprintf(" -srtp_out_params %s", s.req.Audio.SrtpKey()) +
// fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort)
args := strings.Split(ffmpegVideo, " ")
cmd := exec.Command("ffmpeg", args[:]...)
cmd.Stdout = Stdout
cmd.Stderr = Stderr
log.Debug.Println(cmd)
err := cmd.Start()
if err == nil {
s.cmd = cmd
}
return err
}
// TODO (mah) test
func (s *stream) suspend() {
log.Debug.Println("suspend stream")
s.cmd.Process.Signal(syscall.SIGSTOP)
}
// TODO (mah) test
func (s *stream) resume() {
log.Debug.Println("resume stream")
s.cmd.Process.Signal(syscall.SIGCONT)
}
// TODO (mah) implement
func (s *stream) reconfigure(video rtp.VideoParameters, audio rtp.AudioParameters) error {
if s.cmd != nil {
log.Debug.Println("reconfigure() is not implemented")
}
return nil
}
func (s *stream) videoEncoder(param rtp.VideoParameters) string {
switch param.CodecType {
case rtp.VideoCodecType_H264:
return s.h264Encoder
}
return "?"
}
func (s *stream) videoDecoderOption(param rtp.VideoParameters) string {
switch param.CodecType {
case rtp.VideoCodecType_H264:
if s.h264Decoder != "" {
return fmt.Sprintf(" -codec:v %s", s.h264Decoder)
}
}
return ""
}
// https://superuser.com/a/564007
func videoProfile(param rtp.VideoCodecParameters) string {
for _, p := range param.Profiles {
switch p.Id {
case rtp.VideoCodecProfileConstrainedBaseline:
return "baseline"
case rtp.VideoCodecProfileMain:
return "main"
case rtp.VideoCodecProfileHigh:
return "high"
default:
break
}
}
return ""
}
func (s *stream) framerate(attr rtp.VideoCodecAttributes) byte {
if s.inputDevice == "avfoundation" {
// avfoundation only supports 30 fps on a MacBook Pro (Retina, 15-inch, Late 2013) running macOS 10.12 Sierra
// TODO (mah) test this with other Macs
return 30
}
return attr.Framerate
}
// https://superuser.com/a/564007
func videoLevel(param rtp.VideoCodecParameters) string {
for _, l := range param.Levels {
switch l.Level {
case rtp.VideoCodecLevel3_1:
return "3.1"
case rtp.VideoCodecLevel3_2:
return "3.2"
case rtp.VideoCodecLevel4:
return "4.0"
default:
break
}
}
return ""
}
func videoMTU(setup rtp.SetupEndpoints) string {
switch setup.ControllerAddr.IPVersion {
case rtp.IPAddrVersionv4:
return "1378"
case rtp.IPAddrVersionv6:
return "1228"
}
return "1378"
}
// https://trac.ffmpeg.org/wiki/audio%20types
func audioCodecOption(param rtp.AudioParameters) string {
switch param.CodecType {
case rtp.AudioCodecType_PCMU:
log.Debug.Println("audioCodec(PCMU) not supported")
case rtp.AudioCodecType_PCMA:
log.Debug.Println("audioCodec(PCMA) not supported")
case rtp.AudioCodecType_AAC_ELD:
return "-acodec aac"
// return "-acodec libfdk_aac -aprofile aac_eld" // requires ffmpeg built with --enable-libfdk-aac
case rtp.AudioCodecType_Opus:
// requires ffmpeg built with --enable-libopus
// - macOS: brew reinstall ffmpeg --with-opus
return fmt.Sprintf("-acodec libopus")
case rtp.AudioCodecType_MSBC:
log.Debug.Println("audioCodec(MSBC) not supported")
case rtp.AudioCodecType_AMR:
log.Debug.Println("audioCodec(AMR) not supported")
case rtp.AudioCodecType_ARM_WB:
log.Debug.Println("audioCodec(ARM_WB) not supported")
}
return ""
}
func audioVariableBitrate(param rtp.AudioParameters) string {
switch param.CodecParams.Bitrate {
case rtp.AudioCodecBitrateVariable:
return "on"
case rtp.AudioCodecBitrateConstant:
return "off"
default:
log.Info.Println("variableBitrate() undefined bitrate", param.CodecParams.Bitrate)
break
}
return "?"
}
func audioSamplingRate(param rtp.AudioParameters) string {
switch param.CodecParams.Samplerate {
case rtp.AudioCodecSampleRate8Khz:
return "8k"
case rtp.AudioCodecSampleRate16Khz:
return "16k"
case rtp.AudioCodecSampleRate24Khz:
return "24k"
default:
log.Info.Println("audioSamplingRate() undefined samplrate", param.CodecParams.Samplerate)
break
}
return ""
}