Commit | Line | Data |
---|---|---|
85f07f22 FB |
1 | /* |
2 | * Multiple format streaming server | |
3 | * Copyright (c) 2000,2001 Gerard Lantau. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 2 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to the Free Software | |
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
18 | */ | |
19 | #include <stdarg.h> | |
20 | #include <stdlib.h> | |
21 | #include <stdio.h> | |
22 | #include <string.h> | |
23 | #include <netinet/in.h> | |
24 | #include <unistd.h> | |
25 | #include <fcntl.h> | |
26 | #include <sys/ioctl.h> | |
27 | #include <sys/poll.h> | |
28 | #include <errno.h> | |
29 | #include <sys/time.h> | |
30 | #include <time.h> | |
31 | #include <getopt.h> | |
32 | #include <sys/types.h> | |
33 | #include <sys/socket.h> | |
34 | #include <arpa/inet.h> | |
35 | #include <netdb.h> | |
36 | #include <ctype.h> | |
37 | #include <signal.h> | |
f5d1f41b | 38 | #include <assert.h> |
85f07f22 | 39 | |
d8cf5aea | 40 | #include "bswap.h" // needed for the bitstream writer in common.h which is included in avformat.h |
85f07f22 FB |
41 | #include "avformat.h" |
42 | ||
43 | /* maximum number of simultaneous HTTP connections */ | |
44 | #define HTTP_MAX_CONNECTIONS 2000 | |
45 | ||
46 | enum HTTPState { | |
47 | HTTPSTATE_WAIT_REQUEST, | |
48 | HTTPSTATE_SEND_HEADER, | |
49 | HTTPSTATE_SEND_DATA_HEADER, | |
50 | HTTPSTATE_SEND_DATA, | |
51 | HTTPSTATE_SEND_DATA_TRAILER, | |
52 | HTTPSTATE_RECEIVE_DATA, | |
53 | HTTPSTATE_WAIT_FEED, | |
54 | }; | |
55 | ||
56 | const char *http_state[] = { | |
57 | "WAIT_REQUEST", | |
58 | "SEND_HEADER", | |
59 | "SEND_DATA_HEADER", | |
60 | "SEND_DATA", | |
61 | "SEND_DATA_TRAILER", | |
62 | "RECEIVE_DATA", | |
63 | "WAIT_FEED", | |
64 | }; | |
65 | ||
66 | #define IOBUFFER_MAX_SIZE 16384 | |
67 | ||
68 | /* coef for exponential mean for bitrate estimation in statistics */ | |
69 | #define AVG_COEF 0.9 | |
70 | ||
71 | /* timeouts are in ms */ | |
72 | #define REQUEST_TIMEOUT (15 * 1000) | |
73 | #define SYNC_TIMEOUT (10 * 1000) | |
74 | ||
75 | /* context associated with one connection */ | |
76 | typedef struct HTTPContext { | |
77 | enum HTTPState state; | |
78 | int fd; /* socket file descriptor */ | |
79 | struct sockaddr_in from_addr; /* origin */ | |
80 | struct pollfd *poll_entry; /* used when polling */ | |
81 | long timeout; | |
82 | UINT8 buffer[IOBUFFER_MAX_SIZE]; | |
83 | UINT8 *buffer_ptr, *buffer_end; | |
84 | int http_error; | |
85 | struct HTTPContext *next; | |
86 | int got_key_frame[MAX_STREAMS]; /* for each type */ | |
87 | INT64 data_count; | |
88 | /* feed input */ | |
89 | int feed_fd; | |
90 | /* input format handling */ | |
91 | AVFormatContext *fmt_in; | |
92 | /* output format handling */ | |
93 | struct FFStream *stream; | |
94 | AVFormatContext fmt_ctx; | |
95 | int last_packet_sent; /* true if last data packet was sent */ | |
96 | } HTTPContext; | |
97 | ||
98 | /* each generated stream is described here */ | |
99 | enum StreamType { | |
100 | STREAM_TYPE_LIVE, | |
101 | STREAM_TYPE_STATUS, | |
102 | }; | |
103 | ||
104 | /* description of each stream of the ffserver.conf file */ | |
105 | typedef struct FFStream { | |
106 | enum StreamType stream_type; | |
107 | char filename[1024]; /* stream filename */ | |
108 | struct FFStream *feed; | |
109 | AVFormat *fmt; | |
110 | int nb_streams; | |
111 | AVStream *streams[MAX_STREAMS]; | |
112 | int feed_streams[MAX_STREAMS]; /* index of streams in the feed */ | |
113 | char feed_filename[1024]; /* file name of the feed storage, or | |
114 | input file name for a stream */ | |
115 | struct FFStream *next; | |
116 | /* feed specific */ | |
117 | int feed_opened; /* true if someone if writing to feed */ | |
118 | int is_feed; /* true if it is a feed */ | |
119 | INT64 feed_max_size; /* maximum storage size */ | |
120 | INT64 feed_write_index; /* current write position in feed (it wraps round) */ | |
121 | INT64 feed_size; /* current size of feed */ | |
122 | struct FFStream *next_feed; | |
123 | } FFStream; | |
124 | ||
125 | typedef struct FeedData { | |
126 | long long data_count; | |
127 | float avg_frame_size; /* frame size averraged over last frames with exponential mean */ | |
128 | } FeedData; | |
129 | ||
130 | struct sockaddr_in my_addr; | |
131 | char logfilename[1024]; | |
132 | HTTPContext *first_http_ctx; | |
133 | FFStream *first_feed; /* contains only feeds */ | |
134 | FFStream *first_stream; /* contains all streams, including feeds */ | |
135 | ||
136 | static int handle_http(HTTPContext *c, long cur_time); | |
137 | static int http_parse_request(HTTPContext *c); | |
138 | static int http_send_data(HTTPContext *c); | |
139 | static void compute_stats(HTTPContext *c); | |
140 | static int open_input_stream(HTTPContext *c, const char *info); | |
141 | static int http_start_receive_data(HTTPContext *c); | |
142 | static int http_receive_data(HTTPContext *c); | |
143 | ||
144 | int nb_max_connections; | |
145 | int nb_connections; | |
146 | ||
147 | static long gettime_ms(void) | |
148 | { | |
149 | struct timeval tv; | |
150 | ||
151 | gettimeofday(&tv,NULL); | |
152 | return (long long)tv.tv_sec * 1000 + (tv.tv_usec / 1000); | |
153 | } | |
154 | ||
155 | static FILE *logfile = NULL; | |
156 | ||
157 | static void http_log(char *fmt, ...) | |
158 | { | |
159 | va_list ap; | |
160 | va_start(ap, fmt); | |
161 | ||
162 | if (logfile) | |
163 | vfprintf(logfile, fmt, ap); | |
164 | va_end(ap); | |
165 | } | |
166 | ||
167 | /* main loop of the http server */ | |
168 | static int http_server(struct sockaddr_in my_addr) | |
169 | { | |
170 | int server_fd, tmp, ret; | |
171 | struct sockaddr_in from_addr; | |
172 | struct pollfd poll_table[HTTP_MAX_CONNECTIONS + 1], *poll_entry; | |
173 | HTTPContext *c, **cp; | |
174 | long cur_time; | |
175 | ||
176 | server_fd = socket(AF_INET,SOCK_STREAM,0); | |
177 | if (server_fd < 0) { | |
178 | perror ("socket"); | |
179 | return -1; | |
180 | } | |
181 | ||
182 | tmp = 1; | |
183 | setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)); | |
184 | ||
185 | if (bind (server_fd, (struct sockaddr *) &my_addr, sizeof (my_addr)) < 0) { | |
186 | perror ("bind"); | |
187 | close(server_fd); | |
188 | return -1; | |
189 | } | |
190 | ||
191 | if (listen (server_fd, 5) < 0) { | |
192 | perror ("listen"); | |
193 | close(server_fd); | |
194 | return -1; | |
195 | } | |
196 | ||
197 | http_log("ffserver started.\n"); | |
198 | ||
199 | fcntl(server_fd, F_SETFL, O_NONBLOCK); | |
200 | first_http_ctx = NULL; | |
201 | nb_connections = 0; | |
202 | first_http_ctx = NULL; | |
203 | for(;;) { | |
204 | poll_entry = poll_table; | |
205 | poll_entry->fd = server_fd; | |
206 | poll_entry->events = POLLIN; | |
207 | poll_entry++; | |
208 | ||
209 | /* wait for events on each HTTP handle */ | |
210 | c = first_http_ctx; | |
211 | while (c != NULL) { | |
212 | int fd; | |
213 | fd = c->fd; | |
214 | switch(c->state) { | |
215 | case HTTPSTATE_WAIT_REQUEST: | |
216 | c->poll_entry = poll_entry; | |
217 | poll_entry->fd = fd; | |
218 | poll_entry->events = POLLIN; | |
219 | poll_entry++; | |
220 | break; | |
221 | case HTTPSTATE_SEND_HEADER: | |
222 | case HTTPSTATE_SEND_DATA_HEADER: | |
223 | case HTTPSTATE_SEND_DATA: | |
224 | case HTTPSTATE_SEND_DATA_TRAILER: | |
225 | c->poll_entry = poll_entry; | |
226 | poll_entry->fd = fd; | |
227 | poll_entry->events = POLLOUT; | |
228 | poll_entry++; | |
229 | break; | |
230 | case HTTPSTATE_RECEIVE_DATA: | |
231 | c->poll_entry = poll_entry; | |
232 | poll_entry->fd = fd; | |
233 | poll_entry->events = POLLIN; | |
234 | poll_entry++; | |
235 | break; | |
236 | case HTTPSTATE_WAIT_FEED: | |
237 | /* need to catch errors */ | |
238 | c->poll_entry = poll_entry; | |
239 | poll_entry->fd = fd; | |
240 | poll_entry->events = 0; | |
241 | poll_entry++; | |
242 | break; | |
243 | default: | |
244 | c->poll_entry = NULL; | |
245 | break; | |
246 | } | |
247 | c = c->next; | |
248 | } | |
249 | ||
250 | /* wait for an event on one connection. We poll at least every | |
251 | second to handle timeouts */ | |
252 | do { | |
253 | ret = poll(poll_table, poll_entry - poll_table, 1000); | |
254 | } while (ret == -1); | |
255 | ||
256 | cur_time = gettime_ms(); | |
257 | ||
258 | /* now handle the events */ | |
259 | ||
260 | cp = &first_http_ctx; | |
261 | while ((*cp) != NULL) { | |
262 | c = *cp; | |
263 | if (handle_http (c, cur_time) < 0) { | |
264 | /* close and free the connection */ | |
265 | close(c->fd); | |
266 | if (c->fmt_in) | |
267 | av_close_input_file(c->fmt_in); | |
268 | *cp = c->next; | |
269 | free(c); | |
270 | nb_connections--; | |
271 | } else { | |
272 | cp = &c->next; | |
273 | } | |
274 | } | |
275 | ||
276 | /* new connection request ? */ | |
277 | poll_entry = poll_table; | |
278 | if (poll_entry->revents & POLLIN) { | |
279 | int fd, len; | |
280 | ||
281 | len = sizeof(from_addr); | |
282 | fd = accept(server_fd, (struct sockaddr *)&from_addr, | |
283 | &len); | |
284 | if (fd >= 0) { | |
285 | fcntl(fd, F_SETFL, O_NONBLOCK); | |
286 | /* XXX: should output a warning page when coming | |
287 | close to the connection limit */ | |
288 | if (nb_connections >= nb_max_connections) { | |
289 | close(fd); | |
290 | } else { | |
291 | /* add a new connection */ | |
292 | c = av_mallocz(sizeof(HTTPContext)); | |
293 | c->next = first_http_ctx; | |
294 | first_http_ctx = c; | |
295 | c->fd = fd; | |
296 | c->poll_entry = NULL; | |
297 | c->from_addr = from_addr; | |
298 | c->state = HTTPSTATE_WAIT_REQUEST; | |
299 | c->buffer_ptr = c->buffer; | |
300 | c->buffer_end = c->buffer + IOBUFFER_MAX_SIZE; | |
301 | c->timeout = cur_time + REQUEST_TIMEOUT; | |
302 | nb_connections++; | |
303 | } | |
304 | } | |
305 | } | |
306 | poll_entry++; | |
307 | } | |
308 | } | |
309 | ||
310 | static int handle_http(HTTPContext *c, long cur_time) | |
311 | { | |
312 | int len; | |
313 | ||
314 | switch(c->state) { | |
315 | case HTTPSTATE_WAIT_REQUEST: | |
316 | /* timeout ? */ | |
317 | if ((c->timeout - cur_time) < 0) | |
318 | return -1; | |
319 | if (c->poll_entry->revents & (POLLERR | POLLHUP)) | |
320 | return -1; | |
321 | ||
322 | /* no need to read if no events */ | |
323 | if (!(c->poll_entry->revents & POLLIN)) | |
324 | return 0; | |
325 | /* read the data */ | |
326 | len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); | |
327 | if (len < 0) { | |
328 | if (errno != EAGAIN && errno != EINTR) | |
329 | return -1; | |
330 | } else if (len == 0) { | |
331 | return -1; | |
332 | } else { | |
333 | /* search for end of request. XXX: not fully correct since garbage could come after the end */ | |
334 | UINT8 *ptr; | |
335 | c->buffer_ptr += len; | |
336 | ptr = c->buffer_ptr; | |
337 | if ((ptr >= c->buffer + 2 && !memcmp(ptr-2, "\n\n", 2)) || | |
338 | (ptr >= c->buffer + 4 && !memcmp(ptr-4, "\r\n\r\n", 4))) { | |
339 | /* request found : parse it and reply */ | |
340 | if (http_parse_request(c) < 0) | |
341 | return -1; | |
342 | } else if (ptr >= c->buffer_end) { | |
343 | /* request too long: cannot do anything */ | |
344 | return -1; | |
345 | } | |
346 | } | |
347 | break; | |
348 | ||
349 | case HTTPSTATE_SEND_HEADER: | |
350 | if (c->poll_entry->revents & (POLLERR | POLLHUP)) | |
351 | return -1; | |
352 | ||
353 | /* no need to read if no events */ | |
354 | if (!(c->poll_entry->revents & POLLOUT)) | |
355 | return 0; | |
356 | len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); | |
357 | if (len < 0) { | |
358 | if (errno != EAGAIN && errno != EINTR) { | |
359 | /* error : close connection */ | |
360 | return -1; | |
361 | } | |
362 | } else { | |
363 | c->buffer_ptr += len; | |
364 | if (c->buffer_ptr >= c->buffer_end) { | |
365 | /* if error, exit */ | |
366 | if (c->http_error) | |
367 | return -1; | |
368 | /* all the buffer was send : synchronize to the incoming stream */ | |
369 | c->state = HTTPSTATE_SEND_DATA_HEADER; | |
370 | c->buffer_ptr = c->buffer_end = c->buffer; | |
371 | } | |
372 | } | |
373 | break; | |
374 | ||
375 | case HTTPSTATE_SEND_DATA: | |
376 | case HTTPSTATE_SEND_DATA_HEADER: | |
377 | case HTTPSTATE_SEND_DATA_TRAILER: | |
378 | /* no need to read if no events */ | |
379 | if (c->poll_entry->revents & (POLLERR | POLLHUP)) | |
380 | return -1; | |
381 | ||
382 | if (!(c->poll_entry->revents & POLLOUT)) | |
383 | return 0; | |
384 | if (http_send_data(c) < 0) | |
385 | return -1; | |
386 | break; | |
387 | case HTTPSTATE_RECEIVE_DATA: | |
388 | /* no need to read if no events */ | |
389 | if (c->poll_entry->revents & (POLLERR | POLLHUP)) | |
390 | return -1; | |
391 | if (!(c->poll_entry->revents & POLLIN)) | |
392 | return 0; | |
393 | if (http_receive_data(c) < 0) | |
394 | return -1; | |
395 | break; | |
396 | case HTTPSTATE_WAIT_FEED: | |
397 | /* no need to read if no events */ | |
398 | if (c->poll_entry->revents & (POLLERR | POLLHUP)) | |
399 | return -1; | |
400 | ||
401 | /* nothing to do, we'll be waken up by incoming feed packets */ | |
402 | break; | |
403 | default: | |
404 | return -1; | |
405 | } | |
406 | return 0; | |
407 | } | |
408 | ||
409 | /* parse http request and prepare header */ | |
410 | static int http_parse_request(HTTPContext *c) | |
411 | { | |
412 | char *p; | |
413 | int post; | |
414 | char cmd[32]; | |
415 | char info[1024], *filename; | |
416 | char url[1024], *q; | |
417 | char protocol[32]; | |
418 | char msg[1024]; | |
419 | const char *mime_type; | |
420 | FFStream *stream; | |
421 | ||
422 | p = c->buffer; | |
423 | q = cmd; | |
424 | while (!isspace(*p) && *p != '\0') { | |
425 | if ((q - cmd) < sizeof(cmd) - 1) | |
426 | *q++ = *p; | |
427 | p++; | |
428 | } | |
429 | *q = '\0'; | |
430 | if (!strcmp(cmd, "GET")) | |
431 | post = 0; | |
432 | else if (!strcmp(cmd, "POST")) | |
433 | post = 1; | |
434 | else | |
435 | return -1; | |
436 | ||
437 | while (isspace(*p)) p++; | |
438 | q = url; | |
439 | while (!isspace(*p) && *p != '\0') { | |
440 | if ((q - url) < sizeof(url) - 1) | |
441 | *q++ = *p; | |
442 | p++; | |
443 | } | |
444 | *q = '\0'; | |
445 | ||
446 | while (isspace(*p)) p++; | |
447 | q = protocol; | |
448 | while (!isspace(*p) && *p != '\0') { | |
449 | if ((q - protocol) < sizeof(protocol) - 1) | |
450 | *q++ = *p; | |
451 | p++; | |
452 | } | |
453 | *q = '\0'; | |
454 | if (strcmp(protocol, "HTTP/1.0") && strcmp(protocol, "HTTP/1.1")) | |
455 | return -1; | |
456 | ||
457 | /* find the filename and the optional info string in the request */ | |
458 | p = url; | |
459 | if (*p == '/') | |
460 | p++; | |
461 | filename = p; | |
462 | p = strchr(p, '?'); | |
463 | if (p) { | |
464 | strcpy(info, p); | |
465 | *p = '\0'; | |
466 | } else { | |
467 | info[0] = '\0'; | |
468 | } | |
469 | ||
470 | stream = first_stream; | |
471 | while (stream != NULL) { | |
472 | if (!strcmp(stream->filename, filename)) | |
473 | break; | |
474 | stream = stream->next; | |
475 | } | |
476 | if (stream == NULL) { | |
477 | sprintf(msg, "File '%s' not found", url); | |
478 | goto send_error; | |
479 | } | |
480 | c->stream = stream; | |
481 | ||
482 | /* should do it after so that the size can be computed */ | |
483 | { | |
484 | char buf1[32], buf2[32], *p; | |
485 | time_t ti; | |
486 | /* XXX: reentrant function ? */ | |
487 | p = inet_ntoa(c->from_addr.sin_addr); | |
488 | strcpy(buf1, p); | |
489 | ti = time(NULL); | |
490 | p = ctime(&ti); | |
491 | strcpy(buf2, p); | |
492 | p = buf2 + strlen(p) - 1; | |
493 | if (*p == '\n') | |
494 | *p = '\0'; | |
495 | http_log("%s - - [%s] \"%s %s %s\" %d %d\n", | |
496 | buf1, buf2, cmd, url, protocol, 200, 1024); | |
497 | } | |
498 | ||
499 | /* XXX: add there authenticate and IP match */ | |
500 | ||
501 | if (post) { | |
502 | /* if post, it means a feed is being sent */ | |
503 | if (!stream->is_feed) { | |
504 | sprintf(msg, "POST command not handled"); | |
505 | goto send_error; | |
506 | } | |
507 | if (http_start_receive_data(c) < 0) { | |
508 | sprintf(msg, "could not open feed"); | |
509 | goto send_error; | |
510 | } | |
511 | c->http_error = 0; | |
512 | c->state = HTTPSTATE_RECEIVE_DATA; | |
513 | return 0; | |
514 | } | |
515 | ||
516 | if (c->stream->stream_type == STREAM_TYPE_STATUS) | |
517 | goto send_stats; | |
518 | ||
519 | /* open input stream */ | |
520 | if (open_input_stream(c, info) < 0) { | |
521 | sprintf(msg, "Input stream corresponding to '%s' not found", url); | |
522 | goto send_error; | |
523 | } | |
524 | ||
525 | /* prepare http header */ | |
526 | q = c->buffer; | |
527 | q += sprintf(q, "HTTP/1.0 200 OK\r\n"); | |
528 | mime_type = c->stream->fmt->mime_type; | |
529 | if (!mime_type) | |
530 | mime_type = "application/x-octet_stream"; | |
531 | q += sprintf(q, "Content-type: %s\r\n", mime_type); | |
532 | q += sprintf(q, "Pragma: no-cache\r\n"); | |
533 | ||
534 | /* for asf, we need extra headers */ | |
535 | if (!strcmp(c->stream->fmt->name,"asf")) { | |
536 | q += sprintf(q, "Pragma: features=broadcast\r\n"); | |
537 | } | |
538 | q += sprintf(q, "\r\n"); | |
539 | ||
540 | /* prepare output buffer */ | |
541 | c->http_error = 0; | |
542 | c->buffer_ptr = c->buffer; | |
543 | c->buffer_end = q; | |
544 | c->state = HTTPSTATE_SEND_HEADER; | |
545 | return 0; | |
546 | send_error: | |
547 | c->http_error = 404; | |
548 | q = c->buffer; | |
549 | q += sprintf(q, "HTTP/1.0 404 Not Found\r\n"); | |
550 | q += sprintf(q, "Content-type: %s\r\n", "text/html"); | |
551 | q += sprintf(q, "\r\n"); | |
552 | q += sprintf(q, "<HTML>\n"); | |
553 | q += sprintf(q, "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n"); | |
554 | q += sprintf(q, "<BODY>%s</BODY>\n", msg); | |
555 | q += sprintf(q, "</HTML>\n"); | |
556 | ||
557 | /* prepare output buffer */ | |
558 | c->buffer_ptr = c->buffer; | |
559 | c->buffer_end = q; | |
560 | c->state = HTTPSTATE_SEND_HEADER; | |
561 | return 0; | |
562 | send_stats: | |
563 | compute_stats(c); | |
564 | c->http_error = 200; /* horrible : we use this value to avoid | |
565 | going to the send data state */ | |
566 | c->state = HTTPSTATE_SEND_HEADER; | |
567 | return 0; | |
568 | } | |
569 | ||
570 | static void compute_stats(HTTPContext *c) | |
571 | { | |
572 | HTTPContext *c1; | |
573 | FFStream *stream; | |
574 | char *q, *p; | |
575 | time_t ti; | |
576 | int i; | |
577 | ||
578 | q = c->buffer; | |
579 | q += sprintf(q, "HTTP/1.0 200 OK\r\n"); | |
580 | q += sprintf(q, "Content-type: %s\r\n", "text/html"); | |
581 | q += sprintf(q, "Pragma: no-cache\r\n"); | |
582 | q += sprintf(q, "\r\n"); | |
583 | ||
584 | q += sprintf(q, "<HEAD><TITLE>FFServer Status</TITLE></HEAD>\n<BODY>"); | |
585 | q += sprintf(q, "<H1>FFServer Status</H1>\n"); | |
586 | /* format status */ | |
587 | q += sprintf(q, "<H1>Available Streams</H1>\n"); | |
588 | q += sprintf(q, "<TABLE>\n"); | |
589 | q += sprintf(q, "<TR><TD>Path<TD>Format<TD>Bit rate (kbits/s)<TD>Video<TD>Audio<TD>Feed\n"); | |
590 | stream = first_stream; | |
591 | while (stream != NULL) { | |
592 | q += sprintf(q, "<TR><TD><A HREF=\"/%s\">%s</A> ", | |
593 | stream->filename, stream->filename); | |
594 | switch(stream->stream_type) { | |
595 | case STREAM_TYPE_LIVE: | |
596 | { | |
597 | int audio_bit_rate = 0; | |
598 | int video_bit_rate = 0; | |
599 | ||
600 | for(i=0;i<stream->nb_streams;i++) { | |
601 | AVStream *st = stream->streams[i]; | |
602 | switch(st->codec.codec_type) { | |
603 | case CODEC_TYPE_AUDIO: | |
604 | audio_bit_rate += st->codec.bit_rate; | |
605 | break; | |
606 | case CODEC_TYPE_VIDEO: | |
607 | video_bit_rate += st->codec.bit_rate; | |
608 | break; | |
609 | } | |
610 | } | |
611 | q += sprintf(q, "<TD> %s <TD> %d <TD> %d <TD> %d", | |
612 | stream->fmt->name, | |
613 | (audio_bit_rate + video_bit_rate) / 1000, | |
614 | video_bit_rate / 1000, audio_bit_rate / 1000); | |
615 | if (stream->feed) { | |
616 | q += sprintf(q, "<TD>%s", stream->feed->filename); | |
617 | } else { | |
618 | q += sprintf(q, "<TD>%s", stream->feed_filename); | |
619 | } | |
620 | q += sprintf(q, "\n"); | |
621 | } | |
622 | break; | |
623 | default: | |
624 | q += sprintf(q, "<TD> - <TD> - <TD> - <TD> -\n"); | |
625 | break; | |
626 | } | |
627 | stream = stream->next; | |
628 | } | |
629 | q += sprintf(q, "</TABLE>\n"); | |
630 | ||
631 | #if 0 | |
632 | { | |
633 | float avg; | |
634 | AVCodecContext *enc; | |
635 | char buf[1024]; | |
636 | ||
637 | /* feed status */ | |
638 | stream = first_feed; | |
639 | while (stream != NULL) { | |
640 | q += sprintf(q, "<H1>Feed '%s'</H1>\n", stream->filename); | |
641 | q += sprintf(q, "<TABLE>\n"); | |
642 | q += sprintf(q, "<TR><TD>Parameters<TD>Frame count<TD>Size<TD>Avg bitrate (kbits/s)\n"); | |
643 | for(i=0;i<stream->nb_streams;i++) { | |
644 | AVStream *st = stream->streams[i]; | |
645 | FeedData *fdata = st->priv_data; | |
646 | enc = &st->codec; | |
647 | ||
648 | avcodec_string(buf, sizeof(buf), enc); | |
649 | avg = fdata->avg_frame_size * (float)enc->rate * 8.0; | |
650 | if (enc->codec->type == CODEC_TYPE_AUDIO && enc->frame_size > 0) | |
651 | avg /= enc->frame_size; | |
652 | q += sprintf(q, "<TR><TD>%s <TD> %d <TD> %Ld <TD> %0.1f\n", | |
653 | buf, enc->frame_number, fdata->data_count, avg / 1000.0); | |
654 | } | |
655 | q += sprintf(q, "</TABLE>\n"); | |
656 | stream = stream->next_feed; | |
657 | } | |
658 | } | |
659 | #endif | |
660 | ||
661 | /* connection status */ | |
662 | q += sprintf(q, "<H1>Connection Status</H1>\n"); | |
663 | ||
664 | q += sprintf(q, "Number of connections: %d / %d<BR>\n", | |
665 | nb_connections, nb_max_connections); | |
666 | ||
667 | q += sprintf(q, "<TABLE>\n"); | |
668 | q += sprintf(q, "<TR><TD>#<TD>File<TD>IP<TD>State<TD>Size\n"); | |
669 | c1 = first_http_ctx; | |
670 | i = 0; | |
671 | while (c1 != NULL) { | |
672 | i++; | |
673 | p = inet_ntoa(c1->from_addr.sin_addr); | |
674 | q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <TD> %Ld\n", | |
675 | i, c1->stream->filename, | |
676 | c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", | |
677 | p, | |
678 | http_state[c1->state], | |
679 | c1->data_count); | |
680 | c1 = c1->next; | |
681 | } | |
682 | q += sprintf(q, "</TABLE>\n"); | |
683 | ||
684 | /* date */ | |
685 | ti = time(NULL); | |
686 | p = ctime(&ti); | |
687 | q += sprintf(q, "<HR>Generated at %s", p); | |
688 | q += sprintf(q, "</BODY>\n</HTML>\n"); | |
689 | ||
690 | c->buffer_ptr = c->buffer; | |
691 | c->buffer_end = q; | |
692 | } | |
693 | ||
694 | ||
695 | static void http_write_packet(void *opaque, | |
696 | unsigned char *buf, int size) | |
697 | { | |
698 | HTTPContext *c = opaque; | |
699 | if (size > IOBUFFER_MAX_SIZE) | |
700 | abort(); | |
701 | memcpy(c->buffer, buf, size); | |
702 | c->buffer_ptr = c->buffer; | |
703 | c->buffer_end = c->buffer + size; | |
704 | } | |
705 | ||
706 | static int open_input_stream(HTTPContext *c, const char *info) | |
707 | { | |
708 | char buf[128]; | |
709 | char input_filename[1024]; | |
710 | AVFormatContext *s; | |
711 | int buf_size; | |
712 | INT64 stream_pos; | |
713 | ||
714 | /* find file name */ | |
715 | if (c->stream->feed) { | |
716 | strcpy(input_filename, c->stream->feed->feed_filename); | |
717 | buf_size = FFM_PACKET_SIZE; | |
718 | /* compute position (absolute time) */ | |
719 | if (find_info_tag(buf, sizeof(buf), "date", info)) { | |
720 | stream_pos = parse_date(buf, 0); | |
721 | } else { | |
722 | stream_pos = gettime(); | |
723 | } | |
724 | } else { | |
725 | strcpy(input_filename, c->stream->feed_filename); | |
726 | buf_size = 0; | |
727 | /* compute position (relative time) */ | |
728 | if (find_info_tag(buf, sizeof(buf), "date", info)) { | |
729 | stream_pos = parse_date(buf, 1); | |
730 | } else { | |
731 | stream_pos = 0; | |
732 | } | |
733 | } | |
734 | if (input_filename[0] == '\0') | |
735 | return -1; | |
736 | ||
737 | /* open stream */ | |
63bdb086 | 738 | s = av_open_input_file(input_filename, NULL, buf_size, NULL); |
85f07f22 FB |
739 | if (!s) |
740 | return -1; | |
741 | c->fmt_in = s; | |
742 | ||
743 | if (c->fmt_in->format->read_seek) { | |
744 | c->fmt_in->format->read_seek(c->fmt_in, stream_pos); | |
745 | } | |
746 | ||
747 | // printf("stream %s opened pos=%0.6f\n", input_filename, stream_pos / 1000000.0); | |
748 | return 0; | |
749 | } | |
750 | ||
751 | static int http_prepare_data(HTTPContext *c) | |
752 | { | |
753 | int i; | |
754 | ||
755 | switch(c->state) { | |
756 | case HTTPSTATE_SEND_DATA_HEADER: | |
757 | memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); | |
758 | if (c->stream->feed) { | |
759 | /* open output stream by using specified codecs */ | |
760 | c->fmt_ctx.format = c->stream->fmt; | |
761 | c->fmt_ctx.nb_streams = c->stream->nb_streams; | |
762 | for(i=0;i<c->fmt_ctx.nb_streams;i++) { | |
763 | AVStream *st; | |
764 | st = av_mallocz(sizeof(AVStream)); | |
765 | c->fmt_ctx.streams[i] = st; | |
766 | memcpy(st, c->stream->streams[i], sizeof(AVStream)); | |
767 | st->codec.frame_number = 0; /* XXX: should be done in | |
768 | AVStream, not in codec */ | |
769 | c->got_key_frame[i] = 0; | |
770 | } | |
771 | } else { | |
772 | /* open output stream by using codecs in specified file */ | |
773 | c->fmt_ctx.format = c->stream->fmt; | |
774 | c->fmt_ctx.nb_streams = c->fmt_in->nb_streams; | |
775 | for(i=0;i<c->fmt_ctx.nb_streams;i++) { | |
776 | AVStream *st; | |
777 | st = av_mallocz(sizeof(AVStream)); | |
778 | c->fmt_ctx.streams[i] = st; | |
779 | memcpy(st, c->fmt_in->streams[i], sizeof(AVStream)); | |
780 | st->codec.frame_number = 0; /* XXX: should be done in | |
781 | AVStream, not in codec */ | |
782 | c->got_key_frame[i] = 0; | |
783 | } | |
784 | } | |
785 | init_put_byte(&c->fmt_ctx.pb, c->buffer, IOBUFFER_MAX_SIZE, | |
786 | 1, c, NULL, http_write_packet, NULL); | |
787 | c->fmt_ctx.pb.is_streamed = 1; | |
788 | /* prepare header */ | |
789 | c->fmt_ctx.format->write_header(&c->fmt_ctx); | |
790 | c->state = HTTPSTATE_SEND_DATA; | |
791 | c->last_packet_sent = 0; | |
792 | break; | |
793 | case HTTPSTATE_SEND_DATA: | |
794 | /* find a new packet */ | |
795 | #if 0 | |
796 | fifo_total_size = http_fifo_write_count - c->last_http_fifo_write_count; | |
797 | if (fifo_total_size >= ((3 * FIFO_MAX_SIZE) / 4)) { | |
798 | /* overflow : resync. We suppose that wptr is at this | |
799 | point a pointer to a valid packet */ | |
800 | c->rptr = http_fifo.wptr; | |
801 | for(i=0;i<c->fmt_ctx.nb_streams;i++) { | |
802 | c->got_key_frame[i] = 0; | |
803 | } | |
804 | } | |
805 | ||
806 | start_rptr = c->rptr; | |
807 | if (fifo_read(&http_fifo, (UINT8 *)&hdr, sizeof(hdr), &c->rptr) < 0) | |
808 | return 0; | |
809 | payload_size = ntohs(hdr.payload_size); | |
810 | payload = malloc(payload_size); | |
811 | if (fifo_read(&http_fifo, payload, payload_size, &c->rptr) < 0) { | |
812 | /* cannot read all the payload */ | |
813 | free(payload); | |
814 | c->rptr = start_rptr; | |
815 | return 0; | |
816 | } | |
817 | ||
818 | c->last_http_fifo_write_count = http_fifo_write_count - | |
819 | fifo_size(&http_fifo, c->rptr); | |
820 | ||
821 | if (c->stream->stream_type != STREAM_TYPE_MASTER) { | |
822 | /* test if the packet can be handled by this format */ | |
823 | ret = 0; | |
824 | for(i=0;i<c->fmt_ctx.nb_streams;i++) { | |
825 | AVStream *st = c->fmt_ctx.streams[i]; | |
826 | if (test_header(&hdr, &st->codec)) { | |
827 | /* only begin sending when got a key frame */ | |
828 | if (st->codec.key_frame) | |
829 | c->got_key_frame[i] = 1; | |
830 | if (c->got_key_frame[i]) { | |
831 | ret = c->fmt_ctx.format->write_packet(&c->fmt_ctx, i, | |
832 | payload, payload_size); | |
833 | } | |
834 | break; | |
835 | } | |
836 | } | |
837 | if (ret) { | |
838 | /* must send trailer now */ | |
839 | c->state = HTTPSTATE_SEND_DATA_TRAILER; | |
840 | } | |
841 | } else { | |
842 | /* master case : send everything */ | |
843 | char *q; | |
844 | q = c->buffer; | |
845 | memcpy(q, &hdr, sizeof(hdr)); | |
846 | q += sizeof(hdr); | |
847 | memcpy(q, payload, payload_size); | |
848 | q += payload_size; | |
849 | c->buffer_ptr = c->buffer; | |
850 | c->buffer_end = q; | |
851 | } | |
852 | free(payload); | |
853 | #endif | |
854 | { | |
855 | AVPacket pkt; | |
856 | ||
857 | /* read a packet from the input stream */ | |
858 | if (c->stream->feed) { | |
859 | ffm_set_write_index(c->fmt_in, | |
860 | c->stream->feed->feed_write_index, | |
861 | c->stream->feed->feed_size); | |
862 | } | |
863 | if (av_read_packet(c->fmt_in, &pkt) < 0) { | |
864 | if (c->stream->feed && c->stream->feed->feed_opened) { | |
865 | /* if coming from feed, it means we reached the end of the | |
866 | ffm file, so must wait for more data */ | |
867 | c->state = HTTPSTATE_WAIT_FEED; | |
868 | return 1; /* state changed */ | |
869 | } else { | |
870 | /* must send trailer now because eof or error */ | |
871 | c->state = HTTPSTATE_SEND_DATA_TRAILER; | |
872 | } | |
873 | } else { | |
874 | /* send it to the appropriate stream */ | |
875 | if (c->stream->feed) { | |
876 | /* if coming from a feed, select the right stream */ | |
877 | for(i=0;i<c->stream->nb_streams;i++) { | |
878 | if (c->stream->feed_streams[i] == pkt.stream_index) { | |
879 | pkt.stream_index = i; | |
880 | goto send_it; | |
881 | } | |
882 | } | |
883 | } else { | |
608d0dee | 884 | send_it: |
10bb7023 | 885 | if (av_write_packet(&c->fmt_ctx, &pkt, 0)) |
608d0dee | 886 | c->state = HTTPSTATE_SEND_DATA_TRAILER; |
85f07f22 FB |
887 | } |
888 | ||
889 | av_free_packet(&pkt); | |
890 | } | |
891 | } | |
892 | break; | |
893 | default: | |
894 | case HTTPSTATE_SEND_DATA_TRAILER: | |
895 | /* last packet test ? */ | |
896 | if (c->last_packet_sent) | |
897 | return -1; | |
898 | /* prepare header */ | |
899 | c->fmt_ctx.format->write_trailer(&c->fmt_ctx); | |
900 | c->last_packet_sent = 1; | |
901 | break; | |
902 | } | |
903 | return 0; | |
904 | } | |
905 | ||
906 | /* should convert the format at the same time */ | |
907 | static int http_send_data(HTTPContext *c) | |
908 | { | |
909 | int len, ret; | |
910 | ||
911 | while (c->buffer_ptr >= c->buffer_end) { | |
912 | ret = http_prepare_data(c); | |
913 | if (ret < 0) | |
914 | return -1; | |
915 | else if (ret == 0) { | |
916 | break; | |
917 | } else { | |
918 | /* state change requested */ | |
919 | return 0; | |
920 | } | |
921 | } | |
922 | ||
923 | len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); | |
924 | if (len < 0) { | |
925 | if (errno != EAGAIN && errno != EINTR) { | |
926 | /* error : close connection */ | |
927 | return -1; | |
928 | } | |
929 | } else { | |
930 | c->buffer_ptr += len; | |
931 | c->data_count += len; | |
932 | } | |
933 | return 0; | |
934 | } | |
935 | ||
936 | static int http_start_receive_data(HTTPContext *c) | |
937 | { | |
938 | int fd; | |
939 | ||
940 | if (c->stream->feed_opened) | |
941 | return -1; | |
942 | ||
943 | /* open feed */ | |
944 | fd = open(c->stream->feed_filename, O_RDWR); | |
945 | if (fd < 0) | |
946 | return -1; | |
947 | c->feed_fd = fd; | |
948 | ||
949 | c->stream->feed_write_index = ffm_read_write_index(fd); | |
950 | c->stream->feed_size = lseek(fd, 0, SEEK_END); | |
951 | lseek(fd, 0, SEEK_SET); | |
952 | ||
953 | /* init buffer input */ | |
954 | c->buffer_ptr = c->buffer; | |
955 | c->buffer_end = c->buffer + FFM_PACKET_SIZE; | |
956 | c->stream->feed_opened = 1; | |
957 | return 0; | |
958 | } | |
959 | ||
960 | static int http_receive_data(HTTPContext *c) | |
961 | { | |
962 | int len; | |
963 | HTTPContext *c1; | |
964 | ||
965 | if (c->buffer_ptr >= c->buffer_end) { | |
966 | /* a packet has been received : write it in the store, except | |
967 | if header */ | |
968 | if (c->data_count > FFM_PACKET_SIZE) { | |
969 | FFStream *feed = c->stream; | |
970 | ||
971 | // printf("writing pos=0x%Lx size=0x%Lx\n", feed->feed_write_index, feed->feed_size); | |
972 | /* XXX: use llseek or url_seek */ | |
973 | lseek(c->feed_fd, feed->feed_write_index, SEEK_SET); | |
974 | write(c->feed_fd, c->buffer, FFM_PACKET_SIZE); | |
975 | ||
976 | feed->feed_write_index += FFM_PACKET_SIZE; | |
977 | /* update file size */ | |
978 | if (feed->feed_write_index > c->stream->feed_size) | |
979 | feed->feed_size = feed->feed_write_index; | |
980 | ||
981 | /* handle wrap around if max file size reached */ | |
982 | if (feed->feed_write_index >= c->stream->feed_max_size) | |
983 | feed->feed_write_index = FFM_PACKET_SIZE; | |
984 | ||
985 | /* write index */ | |
986 | ffm_write_write_index(c->feed_fd, feed->feed_write_index); | |
987 | ||
988 | /* wake up any waiting connections */ | |
989 | for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) { | |
990 | if (c1->state == HTTPSTATE_WAIT_FEED && | |
991 | c1->stream->feed == c->stream->feed) { | |
992 | c1->state = HTTPSTATE_SEND_DATA; | |
993 | } | |
994 | } | |
995 | } | |
996 | c->buffer_ptr = c->buffer; | |
997 | } | |
998 | ||
999 | len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); | |
1000 | if (len < 0) { | |
1001 | if (errno != EAGAIN && errno != EINTR) { | |
1002 | /* error : close connection */ | |
1003 | goto fail; | |
1004 | } | |
1005 | } else if (len == 0) { | |
1006 | /* end of connection : close it */ | |
1007 | goto fail; | |
1008 | } else { | |
1009 | c->buffer_ptr += len; | |
1010 | c->data_count += len; | |
1011 | } | |
1012 | return 0; | |
1013 | fail: | |
1014 | c->stream->feed_opened = 0; | |
1015 | close(c->feed_fd); | |
1016 | return -1; | |
1017 | } | |
1018 | ||
1019 | /* return the stream number in the feed */ | |
1020 | int add_av_stream(FFStream *feed, | |
1021 | AVStream *st) | |
1022 | { | |
1023 | AVStream *fst; | |
1024 | AVCodecContext *av, *av1; | |
1025 | int i; | |
1026 | ||
1027 | av = &st->codec; | |
1028 | for(i=0;i<feed->nb_streams;i++) { | |
1029 | st = feed->streams[i]; | |
1030 | av1 = &st->codec; | |
1031 | if (av1->codec == av->codec && | |
1032 | av1->bit_rate == av->bit_rate) { | |
1033 | ||
1034 | switch(av->codec_type) { | |
1035 | case CODEC_TYPE_AUDIO: | |
1036 | if (av1->channels == av->channels && | |
1037 | av1->sample_rate == av->sample_rate) | |
1038 | goto found; | |
1039 | break; | |
1040 | case CODEC_TYPE_VIDEO: | |
1041 | if (av1->width == av->width && | |
1042 | av1->height == av->height && | |
1043 | av1->frame_rate == av->frame_rate && | |
1044 | av1->gop_size == av->gop_size) | |
1045 | goto found; | |
1046 | break; | |
1047 | } | |
1048 | } | |
1049 | } | |
1050 | ||
1051 | fst = av_mallocz(sizeof(AVStream)); | |
1052 | if (!fst) | |
1053 | return -1; | |
1054 | fst->priv_data = av_mallocz(sizeof(FeedData)); | |
1055 | memcpy(&fst->codec, av, sizeof(AVCodecContext)); | |
1056 | feed->streams[feed->nb_streams++] = fst; | |
1057 | return feed->nb_streams - 1; | |
1058 | found: | |
1059 | return i; | |
1060 | } | |
1061 | ||
1062 | /* compute the needed AVStream for each feed */ | |
1063 | void build_feed_streams(void) | |
1064 | { | |
1065 | FFStream *stream, *feed; | |
1066 | int i; | |
1067 | ||
1068 | /* gather all streams */ | |
1069 | for(stream = first_stream; stream != NULL; stream = stream->next) { | |
1070 | feed = stream->feed; | |
1071 | if (feed) { | |
1072 | if (!stream->is_feed) { | |
1073 | for(i=0;i<stream->nb_streams;i++) { | |
1074 | stream->feed_streams[i] = add_av_stream(feed, stream->streams[i]); | |
1075 | } | |
1076 | } else { | |
1077 | for(i=0;i<stream->nb_streams;i++) { | |
1078 | stream->feed_streams[i] = i; | |
1079 | } | |
1080 | } | |
1081 | } | |
1082 | } | |
1083 | ||
1084 | /* create feed files if needed */ | |
1085 | for(feed = first_feed; feed != NULL; feed = feed->next_feed) { | |
1086 | int fd; | |
1087 | ||
1088 | if (!url_exist(feed->feed_filename)) { | |
1089 | AVFormatContext s1, *s = &s1; | |
1090 | ||
1091 | /* only write the header of the ffm file */ | |
1092 | if (url_fopen(&s->pb, feed->feed_filename, URL_WRONLY) < 0) { | |
1093 | fprintf(stderr, "Could not open output feed file '%s'\n", | |
1094 | feed->feed_filename); | |
1095 | exit(1); | |
1096 | } | |
1097 | s->format = feed->fmt; | |
1098 | s->nb_streams = feed->nb_streams; | |
1099 | for(i=0;i<s->nb_streams;i++) { | |
1100 | AVStream *st; | |
1101 | st = feed->streams[i]; | |
1102 | s->streams[i] = st; | |
1103 | } | |
1104 | s->format->write_header(s); | |
1105 | ||
1106 | url_fclose(&s->pb); | |
1107 | } | |
1108 | /* get feed size and write index */ | |
1109 | fd = open(feed->feed_filename, O_RDONLY); | |
1110 | if (fd < 0) { | |
1111 | fprintf(stderr, "Could not open output feed file '%s'\n", | |
1112 | feed->feed_filename); | |
1113 | exit(1); | |
1114 | } | |
1115 | ||
1116 | feed->feed_write_index = ffm_read_write_index(fd); | |
1117 | feed->feed_size = lseek(fd, 0, SEEK_END); | |
1118 | /* ensure that we do not wrap before the end of file */ | |
1119 | if (feed->feed_max_size < feed->feed_size) | |
1120 | feed->feed_max_size = feed->feed_size; | |
1121 | ||
1122 | close(fd); | |
1123 | } | |
1124 | } | |
1125 | ||
1126 | static void get_arg(char *buf, int buf_size, const char **pp) | |
1127 | { | |
1128 | const char *p; | |
1129 | char *q; | |
1130 | int quote; | |
1131 | ||
1132 | p = *pp; | |
1133 | while (isspace(*p)) p++; | |
1134 | q = buf; | |
1135 | quote = 0; | |
1136 | if (*p == '\"' || *p == '\'') | |
1137 | quote = *p++; | |
1138 | for(;;) { | |
1139 | if (quote) { | |
1140 | if (*p == quote) | |
1141 | break; | |
1142 | } else { | |
1143 | if (isspace(*p)) | |
1144 | break; | |
1145 | } | |
1146 | if (*p == '\0') | |
1147 | break; | |
1148 | if ((q - buf) < buf_size - 1) | |
1149 | *q++ = *p; | |
1150 | p++; | |
1151 | } | |
1152 | *q = '\0'; | |
1153 | if (quote && *p == quote) | |
1154 | p++; | |
1155 | *pp = p; | |
1156 | } | |
1157 | ||
1158 | /* add a codec and set the default parameters */ | |
1159 | void add_codec(FFStream *stream, AVCodecContext *av) | |
1160 | { | |
1161 | AVStream *st; | |
1162 | ||
1163 | /* compute default parameters */ | |
1164 | switch(av->codec_type) { | |
1165 | case CODEC_TYPE_AUDIO: | |
1166 | if (av->bit_rate == 0) | |
1167 | av->bit_rate = 64000; | |
1168 | if (av->sample_rate == 0) | |
1169 | av->sample_rate = 22050; | |
1170 | if (av->channels == 0) | |
1171 | av->channels = 1; | |
1172 | break; | |
1173 | case CODEC_TYPE_VIDEO: | |
1174 | if (av->bit_rate == 0) | |
1175 | av->bit_rate = 64000; | |
1176 | if (av->frame_rate == 0) | |
1177 | av->frame_rate = 5 * FRAME_RATE_BASE; | |
1178 | if (av->width == 0 || av->height == 0) { | |
1179 | av->width = 160; | |
1180 | av->height = 128; | |
1181 | } | |
68d7eef9 MN |
1182 | av->bit_rate_tolerance= 128000; |
1183 | av->qmin= 5; | |
1184 | av->qmax= 31; | |
1185 | av->qcompress= 0.5; | |
1186 | av->qblur= 0.5; | |
1187 | av->max_qdiff= 3; | |
1188 | ||
85f07f22 FB |
1189 | break; |
1190 | } | |
1191 | ||
1192 | st = av_mallocz(sizeof(AVStream)); | |
1193 | if (!st) | |
1194 | return; | |
1195 | stream->streams[stream->nb_streams++] = st; | |
1196 | memcpy(&st->codec, av, sizeof(AVCodecContext)); | |
1197 | } | |
1198 | ||
1199 | int parse_ffconfig(const char *filename) | |
1200 | { | |
1201 | FILE *f; | |
1202 | char line[1024]; | |
1203 | char cmd[64]; | |
1204 | char arg[1024]; | |
1205 | const char *p; | |
1206 | int val, errors, line_num; | |
1207 | FFStream **last_stream, *stream; | |
1208 | FFStream **last_feed, *feed; | |
1209 | AVCodecContext audio_enc, video_enc; | |
1210 | int audio_id, video_id; | |
1211 | ||
1212 | f = fopen(filename, "r"); | |
1213 | if (!f) { | |
1214 | perror(filename); | |
1215 | return -1; | |
1216 | } | |
1217 | ||
1218 | errors = 0; | |
1219 | line_num = 0; | |
1220 | first_stream = NULL; | |
1221 | last_stream = &first_stream; | |
1222 | first_feed = NULL; | |
1223 | last_feed = &first_feed; | |
1224 | stream = NULL; | |
1225 | feed = NULL; | |
1226 | audio_id = CODEC_ID_NONE; | |
1227 | video_id = CODEC_ID_NONE; | |
1228 | for(;;) { | |
1229 | if (fgets(line, sizeof(line), f) == NULL) | |
1230 | break; | |
1231 | line_num++; | |
1232 | p = line; | |
1233 | while (isspace(*p)) | |
1234 | p++; | |
1235 | if (*p == '\0' || *p == '#') | |
1236 | continue; | |
1237 | ||
1238 | get_arg(cmd, sizeof(cmd), &p); | |
1239 | ||
1240 | if (!strcasecmp(cmd, "Port")) { | |
1241 | get_arg(arg, sizeof(arg), &p); | |
1242 | my_addr.sin_port = htons (atoi(arg)); | |
1243 | } else if (!strcasecmp(cmd, "BindAddress")) { | |
1244 | get_arg(arg, sizeof(arg), &p); | |
1245 | if (!inet_aton(arg, &my_addr.sin_addr)) { | |
1246 | fprintf(stderr, "%s:%d: Invalid IP address: %s\n", | |
1247 | filename, line_num, arg); | |
1248 | errors++; | |
1249 | } | |
1250 | } else if (!strcasecmp(cmd, "MaxClients")) { | |
1251 | get_arg(arg, sizeof(arg), &p); | |
1252 | val = atoi(arg); | |
1253 | if (val < 1 || val > HTTP_MAX_CONNECTIONS) { | |
1254 | fprintf(stderr, "%s:%d: Invalid MaxClients: %s\n", | |
1255 | filename, line_num, arg); | |
1256 | errors++; | |
1257 | } else { | |
1258 | nb_max_connections = val; | |
1259 | } | |
1260 | } else if (!strcasecmp(cmd, "CustomLog")) { | |
1261 | get_arg(logfilename, sizeof(logfilename), &p); | |
1262 | } else if (!strcasecmp(cmd, "<Feed")) { | |
1263 | /*********************************************/ | |
1264 | /* Feed related options */ | |
1265 | char *q; | |
1266 | if (stream || feed) { | |
1267 | fprintf(stderr, "%s:%d: Already in a tag\n", | |
1268 | filename, line_num); | |
1269 | } else { | |
1270 | feed = av_mallocz(sizeof(FFStream)); | |
1271 | /* add in stream list */ | |
1272 | *last_stream = feed; | |
1273 | last_stream = &feed->next; | |
1274 | /* add in feed list */ | |
1275 | *last_feed = feed; | |
1276 | last_feed = &feed->next_feed; | |
1277 | ||
1278 | get_arg(feed->filename, sizeof(feed->filename), &p); | |
1279 | q = strrchr(feed->filename, '>'); | |
1280 | if (*q) | |
1281 | *q = '\0'; | |
1282 | feed->fmt = guess_format("ffm", NULL, NULL); | |
1283 | /* defaut feed file */ | |
1284 | snprintf(feed->feed_filename, sizeof(feed->feed_filename), | |
1285 | "/tmp/%s.ffm", feed->filename); | |
1286 | feed->feed_max_size = 5 * 1024 * 1024; | |
1287 | feed->is_feed = 1; | |
1288 | feed->feed = feed; /* self feeding :-) */ | |
1289 | } | |
1290 | } else if (!strcasecmp(cmd, "File")) { | |
1291 | if (feed) { | |
1292 | get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p); | |
1293 | } else if (stream) { | |
1294 | get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p); | |
1295 | } | |
1296 | } else if (!strcasecmp(cmd, "FileMaxSize")) { | |
1297 | if (feed) { | |
1298 | const char *p1; | |
1299 | double fsize; | |
1300 | ||
1301 | get_arg(arg, sizeof(arg), &p); | |
1302 | p1 = arg; | |
1303 | fsize = strtod(p1, (char **)&p1); | |
1304 | switch(toupper(*p1)) { | |
1305 | case 'K': | |
1306 | fsize *= 1024; | |
1307 | break; | |
1308 | case 'M': | |
1309 | fsize *= 1024 * 1024; | |
1310 | break; | |
1311 | case 'G': | |
1312 | fsize *= 1024 * 1024 * 1024; | |
1313 | break; | |
1314 | } | |
1315 | feed->feed_max_size = (INT64)fsize; | |
1316 | } | |
1317 | } else if (!strcasecmp(cmd, "</Feed>")) { | |
1318 | if (!feed) { | |
1319 | fprintf(stderr, "%s:%d: No corresponding <Feed> for </Feed>\n", | |
1320 | filename, line_num); | |
1321 | errors++; | |
1322 | } | |
1323 | feed = NULL; | |
1324 | } else if (!strcasecmp(cmd, "<Stream")) { | |
1325 | /*********************************************/ | |
1326 | /* Stream related options */ | |
1327 | char *q; | |
1328 | if (stream || feed) { | |
1329 | fprintf(stderr, "%s:%d: Already in a tag\n", | |
1330 | filename, line_num); | |
1331 | } else { | |
1332 | stream = av_mallocz(sizeof(FFStream)); | |
1333 | *last_stream = stream; | |
1334 | last_stream = &stream->next; | |
1335 | ||
1336 | get_arg(stream->filename, sizeof(stream->filename), &p); | |
1337 | q = strrchr(stream->filename, '>'); | |
1338 | if (*q) | |
1339 | *q = '\0'; | |
1340 | stream->fmt = guess_format(NULL, stream->filename, NULL); | |
1341 | memset(&audio_enc, 0, sizeof(AVCodecContext)); | |
1342 | memset(&video_enc, 0, sizeof(AVCodecContext)); | |
1343 | audio_id = CODEC_ID_NONE; | |
1344 | video_id = CODEC_ID_NONE; | |
1345 | if (stream->fmt) { | |
1346 | audio_id = stream->fmt->audio_codec; | |
1347 | video_id = stream->fmt->video_codec; | |
1348 | } | |
1349 | } | |
1350 | } else if (!strcasecmp(cmd, "Feed")) { | |
1351 | get_arg(arg, sizeof(arg), &p); | |
1352 | if (stream) { | |
1353 | FFStream *sfeed; | |
1354 | ||
1355 | sfeed = first_feed; | |
1356 | while (sfeed != NULL) { | |
1357 | if (!strcmp(sfeed->filename, arg)) | |
1358 | break; | |
1359 | sfeed = sfeed->next_feed; | |
1360 | } | |
1361 | if (!sfeed) { | |
1362 | fprintf(stderr, "%s:%d: feed '%s' not defined\n", | |
1363 | filename, line_num, arg); | |
1364 | } else { | |
1365 | stream->feed = sfeed; | |
1366 | } | |
1367 | } | |
1368 | } else if (!strcasecmp(cmd, "Format")) { | |
1369 | get_arg(arg, sizeof(arg), &p); | |
1370 | if (!strcmp(arg, "status")) { | |
1371 | stream->stream_type = STREAM_TYPE_STATUS; | |
1372 | stream->fmt = NULL; | |
1373 | } else { | |
1374 | stream->stream_type = STREAM_TYPE_LIVE; | |
dd2af5aa FB |
1375 | /* jpeg cannot be used here, so use single frame jpeg */ |
1376 | if (!strcmp(arg, "jpeg")) | |
1377 | strcpy(arg, "singlejpeg"); | |
85f07f22 FB |
1378 | stream->fmt = guess_format(arg, NULL, NULL); |
1379 | if (!stream->fmt) { | |
1380 | fprintf(stderr, "%s:%d: Unknown Format: %s\n", | |
1381 | filename, line_num, arg); | |
1382 | errors++; | |
1383 | } | |
1384 | } | |
1385 | if (stream->fmt) { | |
1386 | audio_id = stream->fmt->audio_codec; | |
1387 | video_id = stream->fmt->video_codec; | |
1388 | } | |
1389 | } else if (!strcasecmp(cmd, "AudioBitRate")) { | |
1390 | get_arg(arg, sizeof(arg), &p); | |
1391 | if (stream) { | |
1392 | audio_enc.bit_rate = atoi(arg) * 1000; | |
1393 | } | |
1394 | } else if (!strcasecmp(cmd, "AudioChannels")) { | |
1395 | get_arg(arg, sizeof(arg), &p); | |
1396 | if (stream) { | |
1397 | audio_enc.channels = atoi(arg); | |
1398 | } | |
1399 | } else if (!strcasecmp(cmd, "AudioSampleRate")) { | |
1400 | get_arg(arg, sizeof(arg), &p); | |
1401 | if (stream) { | |
1402 | audio_enc.sample_rate = atoi(arg); | |
1403 | } | |
1404 | } else if (!strcasecmp(cmd, "VideoBitRate")) { | |
1405 | get_arg(arg, sizeof(arg), &p); | |
1406 | if (stream) { | |
1407 | video_enc.bit_rate = atoi(arg) * 1000; | |
1408 | } | |
1409 | } else if (!strcasecmp(cmd, "VideoSize")) { | |
1410 | get_arg(arg, sizeof(arg), &p); | |
1411 | if (stream) { | |
1412 | parse_image_size(&video_enc.width, &video_enc.height, arg); | |
1413 | if ((video_enc.width % 16) != 0 || | |
1414 | (video_enc.height % 16) != 0) { | |
1415 | fprintf(stderr, "%s:%d: Image size must be a multiple of 16\n", | |
1416 | filename, line_num); | |
1417 | errors++; | |
1418 | } | |
1419 | } | |
1420 | } else if (!strcasecmp(cmd, "VideoFrameRate")) { | |
1421 | get_arg(arg, sizeof(arg), &p); | |
1422 | if (stream) { | |
1423 | video_enc.frame_rate = (int)(strtod(arg, NULL) * FRAME_RATE_BASE); | |
1424 | } | |
1425 | } else if (!strcasecmp(cmd, "VideoGopSize")) { | |
1426 | get_arg(arg, sizeof(arg), &p); | |
1427 | if (stream) { | |
1428 | video_enc.gop_size = atoi(arg); | |
1429 | } | |
1430 | } else if (!strcasecmp(cmd, "VideoIntraOnly")) { | |
1431 | if (stream) { | |
1432 | video_enc.gop_size = 1; | |
1433 | } | |
1434 | } else if (!strcasecmp(cmd, "NoVideo")) { | |
1435 | video_id = CODEC_ID_NONE; | |
1436 | } else if (!strcasecmp(cmd, "NoAudio")) { | |
1437 | audio_id = CODEC_ID_NONE; | |
1438 | } else if (!strcasecmp(cmd, "</Stream>")) { | |
1439 | if (!stream) { | |
1440 | fprintf(stderr, "%s:%d: No corresponding <Stream> for </Stream>\n", | |
1441 | filename, line_num); | |
1442 | errors++; | |
1443 | } | |
1444 | if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm") != 0) { | |
1445 | if (audio_id != CODEC_ID_NONE) { | |
1446 | audio_enc.codec_type = CODEC_TYPE_AUDIO; | |
1447 | audio_enc.codec_id = audio_id; | |
1448 | add_codec(stream, &audio_enc); | |
1449 | } | |
1450 | if (video_id != CODEC_ID_NONE) { | |
1451 | video_enc.codec_type = CODEC_TYPE_VIDEO; | |
1452 | video_enc.codec_id = video_id; | |
1453 | add_codec(stream, &video_enc); | |
1454 | } | |
1455 | } | |
1456 | stream = NULL; | |
1457 | } else { | |
1458 | fprintf(stderr, "%s:%d: Incorrect keyword: '%s'\n", | |
1459 | filename, line_num, cmd); | |
1460 | errors++; | |
1461 | } | |
1462 | } | |
1463 | ||
1464 | fclose(f); | |
1465 | if (errors) | |
1466 | return -1; | |
1467 | else | |
1468 | return 0; | |
1469 | } | |
1470 | ||
1471 | ||
1472 | void *http_server_thread(void *arg) | |
1473 | { | |
1474 | http_server(my_addr); | |
1475 | return NULL; | |
1476 | } | |
1477 | ||
1478 | #if 0 | |
1479 | static void write_packet(FFCodec *ffenc, | |
1480 | UINT8 *buf, int size) | |
1481 | { | |
1482 | PacketHeader hdr; | |
1483 | AVCodecContext *enc = &ffenc->enc; | |
1484 | UINT8 *wptr; | |
1485 | mk_header(&hdr, enc, size); | |
1486 | wptr = http_fifo.wptr; | |
1487 | fifo_write(&http_fifo, (UINT8 *)&hdr, sizeof(hdr), &wptr); | |
1488 | fifo_write(&http_fifo, buf, size, &wptr); | |
1489 | /* atomic modification of wptr */ | |
1490 | http_fifo.wptr = wptr; | |
1491 | ffenc->data_count += size; | |
1492 | ffenc->avg_frame_size = ffenc->avg_frame_size * AVG_COEF + size * (1.0 - AVG_COEF); | |
1493 | } | |
1494 | #endif | |
1495 | ||
1496 | void help(void) | |
1497 | { | |
1498 | printf("ffserver version " FFMPEG_VERSION ", Copyright (c) 2000,2001 Gerard Lantau\n" | |
1499 | "usage: ffserver [-L] [-h] [-f configfile]\n" | |
1500 | "Hyper fast multi format Audio/Video streaming server\n" | |
1501 | "\n" | |
1502 | "-L : print the LICENCE\n" | |
1503 | "-h : this help\n" | |
1504 | "-f configfile : use configfile instead of /etc/ffserver.conf\n" | |
1505 | ); | |
1506 | } | |
1507 | ||
1508 | void licence(void) | |
1509 | { | |
1510 | printf( | |
1511 | "ffserver version " FFMPEG_VERSION "\n" | |
1512 | "Copyright (c) 2000,2001 Gerard Lantau\n" | |
1513 | "This program is free software; you can redistribute it and/or modify\n" | |
1514 | "it under the terms of the GNU General Public License as published by\n" | |
1515 | "the Free Software Foundation; either version 2 of the License, or\n" | |
1516 | "(at your option) any later version.\n" | |
1517 | "\n" | |
1518 | "This program is distributed in the hope that it will be useful,\n" | |
1519 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" | |
1520 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" | |
1521 | "GNU General Public License for more details.\n" | |
1522 | "\n" | |
1523 | "You should have received a copy of the GNU General Public License\n" | |
1524 | "along with this program; if not, write to the Free Software\n" | |
1525 | "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n" | |
1526 | ); | |
1527 | } | |
1528 | ||
1529 | int main(int argc, char **argv) | |
1530 | { | |
1531 | const char *config_filename; | |
1532 | int c; | |
1533 | ||
1534 | register_all(); | |
1535 | ||
1536 | config_filename = "/etc/ffserver.conf"; | |
1537 | ||
1538 | for(;;) { | |
1539 | c = getopt_long_only(argc, argv, "Lh?f:", NULL, NULL); | |
1540 | if (c == -1) | |
1541 | break; | |
1542 | switch(c) { | |
1543 | case 'L': | |
1544 | licence(); | |
1545 | exit(1); | |
1546 | case '?': | |
1547 | case 'h': | |
1548 | help(); | |
1549 | exit(1); | |
1550 | case 'f': | |
1551 | config_filename = optarg; | |
1552 | break; | |
1553 | default: | |
1554 | exit(2); | |
1555 | } | |
1556 | } | |
1557 | ||
1558 | /* address on which the server will handle connections */ | |
1559 | my_addr.sin_family = AF_INET; | |
1560 | my_addr.sin_port = htons (8080); | |
1561 | my_addr.sin_addr.s_addr = htonl (INADDR_ANY); | |
1562 | nb_max_connections = 5; | |
1563 | first_stream = NULL; | |
1564 | logfilename[0] = '\0'; | |
1565 | ||
1566 | if (parse_ffconfig(config_filename) < 0) { | |
1567 | fprintf(stderr, "Incorrect config file - exiting.\n"); | |
1568 | exit(1); | |
1569 | } | |
1570 | ||
1571 | build_feed_streams(); | |
1572 | ||
1573 | /* signal init */ | |
1574 | signal(SIGPIPE, SIG_IGN); | |
1575 | ||
1576 | /* open log file if needed */ | |
1577 | if (logfilename[0] != '\0') { | |
1578 | if (!strcmp(logfilename, "-")) | |
1579 | logfile = stdout; | |
1580 | else | |
1581 | logfile = fopen(logfilename, "w"); | |
1582 | } | |
1583 | ||
1584 | if (http_server(my_addr) < 0) { | |
1585 | fprintf(stderr, "Could start http server\n"); | |
1586 | exit(1); | |
1587 | } | |
1588 | ||
1589 | return 0; | |
1590 | } |