-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathvideortsp.cpp
More file actions
372 lines (352 loc) · 11.6 KB
/
Copy pathvideortsp.cpp
File metadata and controls
372 lines (352 loc) · 11.6 KB
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/*
RTSP ingest by splicing to a loopback ffmpeg. See videortsp.h.
*/
#include "videortsp.h"
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
RtspBackend::~RtspBackend(void)
{
stop();
}
int RtspBackend::pick_loopback_port(void)
{
const int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
return -1;
}
struct sockaddr_in a {};
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.sin_port = 0;
if (bind(s, (struct sockaddr *)&a, sizeof(a)) != 0) {
::close(s);
return -1;
}
socklen_t alen = sizeof(a);
if (getsockname(s, (struct sockaddr *)&a, &alen) != 0) {
::close(s);
return -1;
}
const int port = ntohs(a.sin_port);
::close(s);
return port;
}
const char *splice_proto_name(splice_proto_t p)
{
return p == SPLICE_RTMP ? "RTMP" : "RTSP";
}
bool RtspBackend::start(int port2, int slot, bool want_audio,
splice_proto_t proto, const char *vbsf)
{
port2_ = port2;
slot_ = slot;
proto_ = proto;
// RTSP splices into a loopback listener; RTMP is fed FLV on stdin.
int lport = 0;
if (proto == SPLICE_RTSP) {
lport = pick_loopback_port();
if (lport <= 0) {
printf("[%d] video slot %d: no loopback port for the RTSP "
"backend\n", port2_, slot_);
return false;
}
}
int media[2] = { -1, -1 };
if (pipe(media) != 0) {
printf("[%d] video slot %d: pipe failed - %s\n",
port2_, slot_, strerror(errno));
return false;
}
/*
A socketpair rather than a pipe: the caller pushes FLV through the
same queue it uses for the RTSP splice, and that writes with
send(MSG_NOSIGNAL), which fails ENOTSOCK on a pipe. The child sees
it as fd 0 either way.
*/
int feed[2] = { -1, -1 };
if (proto == SPLICE_RTMP
&& socketpair(AF_UNIX, SOCK_STREAM, 0, feed) != 0) {
::close(media[0]);
::close(media[1]);
printf("[%d] video slot %d: socketpair failed - %s\n",
port2_, slot_, strerror(errno));
return false;
}
char url[128];
if (proto == SPLICE_RTMP) {
snprintf(url, sizeof(url), "pipe:0");
} else {
snprintf(url, sizeof(url), "rtsp://127.0.0.1:%d/", lport);
}
const pid_t pid = fork();
if (pid < 0) {
::close(media[0]);
::close(media[1]);
if (feed[0] >= 0) {
::close(feed[0]);
::close(feed[1]);
}
printf("[%d] video slot %d: fork failed - %s\n",
port2_, slot_, strerror(errno));
return false;
}
if (pid == 0) {
// die with us rather than lingering on a crash
prctl(PR_SET_PDEATHSIG, SIGTERM);
if (getppid() == 1) {
_exit(0);
}
/*
The backend parses SDP, RTP and codec bitstreams from a peer
we have only address-authorised. Fixed argv stops shell
injection; these stop a parser bug from becoming worse.
*/
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
struct rlimit rl {};
rl.rlim_cur = rl.rlim_max = RTSP_BACKEND_MEM_BYTES;
setrlimit(RLIMIT_AS, &rl);
rl.rlim_cur = rl.rlim_max = RTSP_BACKEND_CPU_SECONDS;
setrlimit(RLIMIT_CPU, &rl);
/*
Deliberately no RLIMIT_NPROC. It bounds processes and threads
per *UID*, not per process, so a small value is instantly
exceeded by whatever else the account is already running --
ffmpeg then fails at pthread_create and produces no output at
all. It is the wrong tool for sandboxing one child.
*/
/*
Unchecked, a failed dup2 would leave the child running with
our stdout -- writing MPEG-TS into the proxy's log. Die
instead; the parent sees the exit and reports it.
*/
::close(media[0]);
if (dup2(media[1], STDOUT_FILENO) == -1) {
_exit(126);
}
::close(media[1]);
if (proto == SPLICE_RTMP) {
::close(feed[1]);
if (dup2(feed[0], STDIN_FILENO) == -1) {
_exit(126);
}
::close(feed[0]);
} else {
const int devnull = open("/dev/null", O_RDONLY);
if (devnull >= 0) {
if (dup2(devnull, STDIN_FILENO) == -1) {
_exit(126);
}
::close(devnull);
}
}
/*
Drop every other inherited descriptor.
Without this the backend keeps the accepted publisher socket
open -- visible in ss as ffmpeg and supportproxy both holding
the same public connection. The damage is not the leak itself
but that the socket never fully closes, so ffmpeg never sees
the end of its input, never exits, and running() stays true --
which refuses every later publisher on that slot as slot-busy
until the child is killed by hand.
*/
#ifdef SYS_close_range
if (syscall(SYS_close_range, 3, ~0U, 0) != 0)
#endif
{
/*
Close up to the real limit, not 4096. The old bound only
applied when RLIMIT_NOFILE was under 65536, so on a host
with a high limit every descriptor above 4095 survived --
which is the case this loop exists to cover.
*/
struct rlimit nof {};
long maxfd = 4096;
if (getrlimit(RLIMIT_NOFILE, &nof) == 0
&& nof.rlim_cur != RLIM_INFINITY) {
maxfd = long(nof.rlim_cur);
} else {
const long n = sysconf(_SC_OPEN_MAX);
maxfd = (n > 0) ? n : 65536;
}
for (int f = 3; f < maxfd; f++) {
::close(f);
}
}
/*
-an by default: audio is rarely useful from an aircraft, and
dropping it keeps the muxed stream video-only. With audio on,
it must be re-encoded -- pcm_s16be cannot be carried in
MPEG-TS at all (ffmpeg emits it as private data that probes
back as bin_data), so "copy" would silently destroy it.
*/
const char *audio1 = want_audio ? "-c:a" : "-an";
const char *audio2 = want_audio ? "aac" : nullptr;
const char *argv[40];
int n = 0;
argv[n++] = "ffmpeg";
argv[n++] = "-hide_banner";
argv[n++] = "-nostdin";
argv[n++] = "-loglevel";
argv[n++] = "warning";
if (proto == SPLICE_RTMP) {
// live_flv rather than flv: the stream never ends, and the
// plain flv demuxer waits for a file it will never see.
// No -timeout: that is a socket option, and this is a pipe.
argv[n++] = "-protocol_whitelist";
argv[n++] = "file,pipe";
argv[n++] = "-f";
argv[n++] = "live_flv";
} else {
argv[n++] = "-protocol_whitelist";
argv[n++] = "file,rtp,udp,tcp";
argv[n++] = "-rtsp_flags";
argv[n++] = "listen";
argv[n++] = "-rtsp_transport";
argv[n++] = "tcp";
argv[n++] = "-timeout";
argv[n++] = "10000000";
}
argv[n++] = "-i";
argv[n++] = url;
argv[n++] = "-map";
argv[n++] = "0";
argv[n++] = "-c:v";
argv[n++] = "copy";
if (vbsf != nullptr && vbsf[0] != '\0') {
argv[n++] = "-bsf:v";
argv[n++] = vbsf;
}
argv[n++] = audio1;
if (audio2 != nullptr) {
argv[n++] = audio2;
}
/*
Live output, so do not let the muxer sit on data. The default
32 KiB AVIO buffer is most of a second at this camera's ~0.4
Mbit/s, and muxdelay/muxpreload add their own offset on top.
Measured as part of the join-to-live delay.
*/
argv[n++] = "-muxdelay";
argv[n++] = "0";
argv[n++] = "-muxpreload";
argv[n++] = "0";
argv[n++] = "-flush_packets";
argv[n++] = "1";
argv[n++] = "-f";
argv[n++] = "mpegts";
argv[n++] = "pipe:1";
argv[n] = nullptr;
execvp("ffmpeg", const_cast<char *const *>(argv));
// Only reached if ffmpeg is missing.
_exit(127);
}
::close(media[1]);
pid_ = pid;
media_fd_ = media[0];
fcntl(media_fd_, F_SETFL, fcntl(media_fd_, F_GETFL, 0) | O_NONBLOCK);
if (proto == SPLICE_RTMP) {
::close(feed[0]);
backend_fd_ = feed[1];
fcntl(backend_fd_, F_SETFL,
fcntl(backend_fd_, F_GETFL, 0) | O_NONBLOCK);
printf("[%d] video slot %d RTMP backend pid %d (FLV on stdin, "
"bsf %s)\n", port2_, slot_, int(pid_),
(vbsf != nullptr && vbsf[0] != '\0') ? vbsf : "none");
return true;
}
// Retry-connect until the backend's listener is up. It bound the
// port after we picked it, so a short race is expected.
const int step_ms = 20;
for (int waited = 0; waited < RTSP_BACKEND_READY_MS; waited += step_ms) {
const int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
break;
}
struct sockaddr_in a {};
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.sin_port = htons(uint16_t(lport));
if (connect(s, (struct sockaddr *)&a, sizeof(a)) == 0) {
int one = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK);
backend_fd_ = s;
printf("[%d] video slot %d RTSP backend pid %d on 127.0.0.1:%d\n",
port2_, slot_, int(pid_), lport);
return true;
}
::close(s);
if (reap()) {
printf("[%d] video slot %d: RTSP backend exited before it "
"listened (is ffmpeg installed?)\n", port2_, slot_);
stop();
return false;
}
struct timespec ts { 0, step_ms * 1000000L };
nanosleep(&ts, nullptr);
}
printf("[%d] video slot %d: RTSP backend never became connectable\n",
port2_, slot_);
stop();
return false;
}
bool RtspBackend::reap(void)
{
if (pid_ <= 0) {
return true;
}
int status = 0;
const pid_t r = waitpid(pid_, &status, WNOHANG);
if (r == pid_) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
printf("[%d] video slot %d: ffmpeg not found; RTSP ingest needs "
"it installed\n", port2_, slot_);
}
pid_ = -1;
return true;
}
if (r < 0 && errno == ECHILD) {
pid_ = -1;
return true;
}
return false;
}
void RtspBackend::stop(void)
{
if (backend_fd_ >= 0) {
::close(backend_fd_);
backend_fd_ = -1;
}
if (media_fd_ >= 0) {
::close(media_fd_);
media_fd_ = -1;
}
if (pid_ > 0) {
kill(pid_, SIGTERM);
// Give it a moment to go on its own, then insist.
for (int i = 0; i < 50; i++) {
if (reap()) {
return;
}
struct timespec ts { 0, 10 * 1000000L };
nanosleep(&ts, nullptr);
}
kill(pid_, SIGKILL);
int status = 0;
waitpid(pid_, &status, 0);
pid_ = -1;
}
}