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