]> git.sesse.net Git - ffmpeg/blobdiff - ffserver.c
reverted to inttypes.h since it gives problems on some unixes
[ffmpeg] / ffserver.c
index 037c407e343bcefb83187f72a28fada1afbf3b60..e11f8763bbb840c3aa192c83fc1677ca866acf6f 100644 (file)
@@ -31,6 +31,7 @@
 #include <getopt.h>
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/wait.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <ctype.h>
@@ -59,8 +60,8 @@ const char *http_state[] = {
     "WAIT_FEED",
 };
 
-#define IOBUFFER_MAX_SIZE 32768
-#define PACKET_MAX_SIZE 16384
+#define IOBUFFER_INIT_SIZE 8192
+#define PBUFFER_INIT_SIZE 8192
 
 /* coef for exponential mean for bitrate estimation in statistics */
 #define AVG_COEF 0.9
@@ -69,6 +70,11 @@ const char *http_state[] = {
 #define REQUEST_TIMEOUT (15 * 1000)
 #define SYNC_TIMEOUT (10 * 1000)
 
+typedef struct {
+    INT64 count1, count2;
+    long time1, time2;
+} DataRateData;
+
 /* context associated with one connection */
 typedef struct HTTPContext {
     enum HTTPState state;
@@ -87,22 +93,31 @@ typedef struct HTTPContext {
     AVFormatContext *fmt_in;
     /* output format handling */
     struct FFStream *stream;
+    /* -1 is invalid stream */
+    int feed_streams[MAX_STREAMS]; /* index of streams in the feed */
+    int switch_feed_streams[MAX_STREAMS]; /* index of streams in the feed */
+    int switch_pending;
     AVFormatContext fmt_ctx;
     int last_packet_sent; /* true if last data packet was sent */
     int suppress_log;
     int bandwidth;
-    time_t start_time;
+    long start_time;            /* In milliseconds - this wraps fairly often */
+    DataRateData datarate;
+    int wmp_client_id;
     char protocol[16];
     char method[16];
     char url[128];
-    UINT8 buffer[IOBUFFER_MAX_SIZE];
-    UINT8 pbuffer[PACKET_MAX_SIZE];
+    int buffer_size;
+    UINT8 *buffer;
+    int pbuffer_size;
+    UINT8 *pbuffer;
 } HTTPContext;
 
 /* each generated stream is described here */
 enum StreamType {
     STREAM_TYPE_LIVE,
     STREAM_TYPE_STATUS,
+    STREAM_TYPE_REDIRECT,
 };
 
 /* description of each stream of the ffserver.conf file */
@@ -113,12 +128,19 @@ typedef struct FFStream {
     AVOutputFormat *fmt;
     int nb_streams;
     int prebuffer;      /* Number of millseconds early to start */
-    time_t max_time;
+    long max_time;      /* Number of milliseconds to run */
     int send_on_key;
     AVStream *streams[MAX_STREAMS];
     int feed_streams[MAX_STREAMS]; /* index of streams in the feed */
     char feed_filename[1024]; /* file name of the feed storage, or
                                  input file name for a stream */
+    char author[512];
+    char title[512];
+    char copyright[512];
+    char comment[512];
+    pid_t pid;  /* Of ffmpeg process */
+    time_t pid_start;  /* Of ffmpeg process */
+    char **child_argv;
     struct FFStream *next;
     /* feed specific */
     int feed_opened;     /* true if someone if writing to feed */
@@ -142,7 +164,7 @@ HTTPContext *first_http_ctx;
 FFStream *first_feed;   /* contains only feeds */
 FFStream *first_stream; /* contains all streams, including feeds */
 
-static int handle_http(HTTPContext *c, long cur_time);
+static int handle_http(HTTPContext *c);
 static int http_parse_request(HTTPContext *c);
 static int http_send_data(HTTPContext *c);
 static void compute_stats(HTTPContext *c);
@@ -150,12 +172,20 @@ static int open_input_stream(HTTPContext *c, const char *info);
 static int http_start_receive_data(HTTPContext *c);
 static int http_receive_data(HTTPContext *c);
 
+static const char *my_program_name;
+
+static int ffserver_debug;
+static int no_launch;
+static int need_to_start_children;
+
 int nb_max_connections;
 int nb_connections;
 
 int nb_max_bandwidth;
 int nb_bandwidth;
 
+static long cur_time;           // Making this global saves on passing it around everywhere
+
 static long gettime_ms(void)
 {
     struct timeval tv;
@@ -199,6 +229,83 @@ static void log_connection(HTTPContext *c)
              buf1, buf2, c->method, c->url, c->protocol, (c->http_error ? c->http_error : 200), c->data_count);
 }
 
+static void update_datarate(DataRateData *drd, INT64 count)
+{
+    if (!drd->time1 && !drd->count1) {
+        drd->time1 = drd->time2 = cur_time;
+        drd->count1 = drd->count2 = count;
+    } else {
+        if (cur_time - drd->time2 > 5000) {
+            drd->time1 = drd->time2;
+            drd->count1 = drd->count2;
+            drd->time2 = cur_time;
+            drd->count2 = count;
+        }
+    }
+}
+
+/* In bytes per second */
+static int compute_datarate(DataRateData *drd, INT64 count)
+{
+    if (cur_time == drd->time1)
+        return 0;
+
+    return ((count - drd->count1) * 1000) / (cur_time - drd->time1);
+}
+
+static void start_children(FFStream *feed)
+{
+    if (no_launch)
+        return;
+
+    for (; feed; feed = feed->next) {
+        if (feed->child_argv && !feed->pid) {
+            feed->pid_start = time(0);
+
+            feed->pid = fork();
+
+            if (feed->pid < 0) {
+                fprintf(stderr, "Unable to create children\n");
+                exit(1);
+            }
+            if (!feed->pid) {
+                /* In child */
+                char pathname[1024];
+                char *slash;
+                int i;
+
+                for (i = 3; i < 256; i++) {
+                    close(i);
+                }
+
+                if (!ffserver_debug) {
+                    i = open("/dev/null", O_RDWR);
+                    if (i)
+                        dup2(i, 0);
+                    dup2(i, 1);
+                    dup2(i, 2);
+                    if (i)
+                        close(i);
+                }
+
+                pstrcpy(pathname, sizeof(pathname), my_program_name);
+
+                slash = strrchr(pathname, '/');
+                if (!slash) {
+                    slash = pathname;
+                } else {
+                    slash++;
+                }
+                strcpy(slash, "ffmpeg");
+
+                execvp(pathname, feed->child_argv);
+
+                _exit(1);
+            }
+        }
+    }
+}
+
 /* main loop of the http server */
 static int http_server(struct sockaddr_in my_addr)
 {
@@ -206,7 +313,6 @@ static int http_server(struct sockaddr_in my_addr)
     struct sockaddr_in from_addr;
     struct pollfd poll_table[HTTP_MAX_CONNECTIONS + 1], *poll_entry;
     HTTPContext *c, **cp;
-    long cur_time;
 
     server_fd = socket(AF_INET,SOCK_STREAM,0);
     if (server_fd < 0) {
@@ -231,6 +337,8 @@ static int http_server(struct sockaddr_in my_addr)
 
     http_log("ffserver started.\n");
 
+    start_children(first_feed);
+
     fcntl(server_fd, F_SETFL, O_NONBLOCK);
     first_http_ctx = NULL;
     nb_connections = 0;
@@ -290,12 +398,17 @@ static int http_server(struct sockaddr_in my_addr)
         
         cur_time = gettime_ms();
 
+        if (need_to_start_children) {
+            need_to_start_children = 0;
+            start_children(first_feed);
+        }
+
         /* now handle the events */
 
         cp = &first_http_ctx;
         while ((*cp) != NULL) {
             c = *cp;
-            if (handle_http (c, cur_time) < 0) {
+            if (handle_http (c) < 0) {
                 /* close and free the connection */
                 log_connection(c);
                 close(c->fd);
@@ -303,6 +416,8 @@ static int http_server(struct sockaddr_in my_addr)
                     av_close_input_file(c->fmt_in);
                 *cp = c->next;
                 nb_bandwidth -= c->bandwidth;
+                av_free(c->buffer);
+                av_free(c->pbuffer);
                 av_free(c);
                 nb_connections--;
             } else {
@@ -323,20 +438,34 @@ static int http_server(struct sockaddr_in my_addr)
                 /* XXX: should output a warning page when coming
                    close to the connection limit */
                 if (nb_connections >= nb_max_connections) {
-                    close(fd);
+                    c = NULL;
                 } else {
                     /* add a new connection */
                     c = av_mallocz(sizeof(HTTPContext));
-                    c->next = first_http_ctx;
-                    first_http_ctx = c;
-                    c->fd = fd;
-                    c->poll_entry = NULL;
-                    c->from_addr = from_addr;
-                    c->state = HTTPSTATE_WAIT_REQUEST;
-                    c->buffer_ptr = c->buffer;
-                    c->buffer_end = c->buffer + IOBUFFER_MAX_SIZE;
-                    c->timeout = cur_time + REQUEST_TIMEOUT;
-                    nb_connections++;
+                    if (c) {
+                        c->next = first_http_ctx;
+                        first_http_ctx = c;
+                        c->fd = fd;
+                        c->poll_entry = NULL;
+                        c->from_addr = from_addr;
+                        c->state = HTTPSTATE_WAIT_REQUEST;
+                        c->buffer = av_malloc(c->buffer_size = IOBUFFER_INIT_SIZE);
+                        c->pbuffer = av_malloc(c->pbuffer_size = PBUFFER_INIT_SIZE);
+                        if (!c->buffer || !c->pbuffer) {
+                            av_free(c->buffer);
+                            av_free(c->pbuffer);
+                            av_freep(&c);
+                        } else {
+                            c->buffer_ptr = c->buffer;
+                            c->buffer_end = c->buffer + c->buffer_size;
+                            c->timeout = cur_time + REQUEST_TIMEOUT;
+                            c->start_time = cur_time;
+                            nb_connections++;
+                        }
+                    }
+                }
+                if (!c) {
+                    close(fd);
                 }
             }
         }
@@ -344,7 +473,7 @@ static int http_server(struct sockaddr_in my_addr)
     }
 }
 
-static int handle_http(HTTPContext *c, long cur_time)
+static int handle_http(HTTPContext *c)
 {
     int len;
     
@@ -446,6 +575,138 @@ static int handle_http(HTTPContext *c, long cur_time)
     return 0;
 }
 
+static int extract_rates(char *rates, int ratelen, const char *request)
+{
+    const char *p;
+
+    for (p = request; *p && *p != '\r' && *p != '\n'; ) {
+        if (strncasecmp(p, "Pragma:", 7) == 0) {
+            const char *q = p + 7;
+
+            while (*q && *q != '\n' && isspace(*q))
+                q++;
+
+            if (strncasecmp(q, "stream-switch-entry=", 20) == 0) {
+                int stream_no;
+                int rate_no;
+
+                q += 20;
+
+                memset(rates, 0xff, ratelen);
+
+                while (1) {
+                    while (*q && *q != '\n' && *q != ':')
+                        q++;
+
+                    if (sscanf(q, ":%d:%d", &stream_no, &rate_no) != 2) {
+                        break;
+                    }
+                    stream_no--;
+                    if (stream_no < ratelen && stream_no >= 0) {
+                        rates[stream_no] = rate_no;
+                    }
+
+                    while (*q && *q != '\n' && !isspace(*q))
+                        q++;
+                }
+
+                return 1;
+            }
+        }
+        p = strchr(p, '\n');
+        if (!p)
+            break;
+
+        p++;
+    }
+
+    return 0;
+}
+
+static int find_stream_in_feed(FFStream *feed, AVCodecContext *codec, int bit_rate)
+{
+    int i;
+    int best_bitrate = 100000000;
+    int best = -1;
+
+    for (i = 0; i < feed->nb_streams; i++) {
+        AVCodecContext *feed_codec = &feed->streams[i]->codec;
+
+        if (feed_codec->codec_id != codec->codec_id ||
+            feed_codec->sample_rate != codec->sample_rate ||
+            feed_codec->width != codec->width ||
+            feed_codec->height != codec->height) {
+            continue;
+        }
+
+        /* Potential stream */
+
+        /* We want the fastest stream less than bit_rate, or the slowest 
+         * faster than bit_rate
+         */
+
+        if (feed_codec->bit_rate <= bit_rate) {
+            if (best_bitrate > bit_rate || feed_codec->bit_rate > best_bitrate) {
+                best_bitrate = feed_codec->bit_rate;
+                best = i;
+            }
+        } else {
+            if (feed_codec->bit_rate < best_bitrate) {
+                best_bitrate = feed_codec->bit_rate;
+                best = i;
+            }
+        }
+    }
+
+    return best;
+}
+
+static int modify_current_stream(HTTPContext *c, char *rates)
+{
+    int i;
+    FFStream *req = c->stream;
+    int action_required = 0;
+
+    for (i = 0; i < req->nb_streams; i++) {
+        AVCodecContext *codec = &req->streams[i]->codec;
+
+        switch(rates[i]) {
+            case 0:
+                c->switch_feed_streams[i] = req->feed_streams[i];
+                break;
+            case 1:
+                c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 2);
+                break;
+            case 2:
+                /* Wants off or slow */
+                c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 4);
+#ifdef WANTS_OFF
+                /* This doesn't work well when it turns off the only stream! */
+                c->switch_feed_streams[i] = -2;
+                c->feed_streams[i] = -2;
+#endif
+                break;
+        }
+
+        if (c->switch_feed_streams[i] >= 0 && c->switch_feed_streams[i] != c->feed_streams[i])
+            action_required = 1;
+    }
+
+    return action_required;
+}
+
+
+static void do_switch_stream(HTTPContext *c, int i)
+{
+    if (c->switch_feed_streams[i] >= 0) {
+#ifdef PHILIP        
+        c->feed_streams[i] = c->switch_feed_streams[i];
+#endif
+
+        /* Now update the stream */
+    }
+    c->switch_feed_streams[i] = -1;
+}
 
 /* parse http request and prepare header */
 static int http_parse_request(HTTPContext *c)
@@ -453,6 +714,7 @@ static int http_parse_request(HTTPContext *c)
     char *p;
     int post;
     int doing_asx;
+    int doing_asf_redirector;
     int doing_ram;
     char cmd[32];
     char info[1024], *filename;
@@ -462,6 +724,8 @@ static int http_parse_request(HTTPContext *c)
     const char *mime_type;
     FFStream *stream;
     int i;
+    char ratebuf[32];
+    char *useragent = 0;
 
     p = c->buffer;
     q = cmd;
@@ -518,6 +782,20 @@ static int http_parse_request(HTTPContext *c)
         info[0] = '\0';
     }
 
+    for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
+        if (strncasecmp(p, "User-Agent:", 11) == 0) {
+            useragent = p + 11;
+            if (*useragent && *useragent != '\n' && isspace(*useragent))
+                useragent++;
+            break;
+        }
+        p = strchr(p, '\n');
+        if (!p)
+            break;
+
+        p++;
+    }
+
     if (strlen(filename) > 4 && strcmp(".asx", filename + strlen(filename) - 4) == 0) {
         doing_asx = 1;
         filename[strlen(filename)-1] = 'f';
@@ -525,6 +803,14 @@ static int http_parse_request(HTTPContext *c)
         doing_asx = 0;
     }
 
+    if (strlen(filename) > 4 && strcmp(".asf", filename + strlen(filename) - 4) == 0 &&
+        (!useragent || strncasecmp(useragent, "NSPlayer", 8) != 0)) {
+        /* if this isn't WMP or lookalike, return the redirector file */
+        doing_asf_redirector = 1;
+    } else {
+        doing_asf_redirector = 0;
+    }
+
     if (strlen(filename) > 4 && 
         (strcmp(".rpm", filename + strlen(filename) - 4) == 0 ||
          strcmp(".ram", filename + strlen(filename) - 4) == 0)) {
@@ -545,6 +831,38 @@ static int http_parse_request(HTTPContext *c)
         goto send_error;
     }
 
+    c->stream = stream;
+    memcpy(c->feed_streams, stream->feed_streams, sizeof(c->feed_streams));
+    memset(c->switch_feed_streams, -1, sizeof(c->switch_feed_streams));
+
+    if (stream->stream_type == STREAM_TYPE_REDIRECT) {
+        c->http_error = 301;
+        q = c->buffer;
+        q += sprintf(q, "HTTP/1.0 301 Moved\r\n");
+        q += sprintf(q, "Location: %s\r\n", stream->feed_filename);
+        q += sprintf(q, "Content-type: text/html\r\n");
+        q += sprintf(q, "\r\n");
+        q += sprintf(q, "<html><head><title>Moved</title></head><body>\r\n");
+        q += sprintf(q, "You should be <a href=\"%s\">redirected</a>.\r\n", stream->feed_filename);
+        q += sprintf(q, "</body></html>\r\n");
+
+        /* prepare output buffer */
+        c->buffer_ptr = c->buffer;
+        c->buffer_end = q;
+        c->state = HTTPSTATE_SEND_HEADER;
+        return 0;
+    }
+
+    /* If this is WMP, get the rate information */
+    if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) {
+        if (modify_current_stream(c, ratebuf)) {
+            for (i = 0; i < sizeof(c->feed_streams) / sizeof(c->feed_streams[0]); i++) {
+                if (c->switch_feed_streams[i] >= 0)
+                    do_switch_stream(c, i);
+            }
+        }
+    }
+
     if (post == 0 && stream->stream_type == STREAM_TYPE_LIVE) {
         /* See if we meet the bandwidth requirements */
         for(i=0;i<stream->nb_streams;i++) {
@@ -584,7 +902,7 @@ static int http_parse_request(HTTPContext *c)
         return 0;
     }
     
-    if (doing_asx || doing_ram) {
+    if (doing_asx || doing_ram || doing_asf_redirector) {
         char *hostinfo = 0;
         
         for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
@@ -633,6 +951,13 @@ static int http_parse_request(HTTPContext *c)
                         q += sprintf(q, "# Autogenerated by ffserver\r\n");
                         q += sprintf(q, "http://%s/%s%s\r\n", 
                                 hostbuf, filename, info);
+                    } else if (doing_asf_redirector) {
+                        q += sprintf(q, "HTTP/1.0 200 ASF Redirect follows\r\n");
+                        q += sprintf(q, "Content-type: video/x-ms-asf\r\n");
+                        q += sprintf(q, "\r\n");
+                        q += sprintf(q, "[Reference]\r\n");
+                        q += sprintf(q, "Ref1=http://%s/%s%s\r\n", 
+                                hostbuf, filename, info);
                     } else
                         av_abort();
 
@@ -649,7 +974,6 @@ static int http_parse_request(HTTPContext *c)
         goto send_error;
     }
 
-    c->stream = stream;
     stream->conns_served++;
 
     /* XXX: add there authenticate and IP match */
@@ -661,12 +985,16 @@ static int http_parse_request(HTTPContext *c)
              * as it might come in handy one day
              */
             char *logline = 0;
+            int client_id = 0;
             
             for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
                 if (strncasecmp(p, "Pragma: log-line=", 17) == 0) {
                     logline = p;
                     break;
                 }
+                if (strncasecmp(p, "Pragma: client-id=", 18) == 0) {
+                    client_id = strtol(p + 18, 0, 10);
+                }
                 p = strchr(p, '\n');
                 if (!p)
                     break;
@@ -686,6 +1014,26 @@ static int http_parse_request(HTTPContext *c)
                     c->suppress_log = 1;
                 }
             }
+
+#ifdef DEBUG_WMP
+            http_log("\nGot request:\n%s\n", c->buffer);
+#endif
+
+            if (client_id && extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) {
+                HTTPContext *wmpc;
+
+                /* Now we have to find the client_id */
+                for (wmpc = first_http_ctx; wmpc; wmpc = wmpc->next) {
+                    if (wmpc->wmp_client_id == client_id)
+                        break;
+                }
+
+                if (wmpc) {
+                    if (modify_current_stream(wmpc, ratebuf)) {
+                        wmpc->switch_pending = 1;
+                    }
+                }
+            }
             
             sprintf(msg, "POST command not handled");
             goto send_error;
@@ -699,6 +1047,12 @@ static int http_parse_request(HTTPContext *c)
         return 0;
     }
 
+#ifdef DEBUG_WMP
+    if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) {
+        http_log("\nGot request:\n%s\n", c->buffer);
+    }
+#endif
+
     if (c->stream->stream_type == STREAM_TYPE_STATUS)
         goto send_stats;
 
@@ -718,10 +1072,16 @@ static int http_parse_request(HTTPContext *c)
 
     /* for asf, we need extra headers */
     if (!strcmp(c->stream->fmt->name,"asf")) {
-        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");
-        /* mime_type = "application/octet-stream"; */
-        /* video/x-ms-asf seems better -- netscape doesn't crash any more! */
-        mime_type = "video/x-ms-asf";
+        /* Need to allocate a client id */
+        static int wmp_session;
+
+        if (!wmp_session)
+            wmp_session = time(0) & 0xffffff;
+
+        c->wmp_client_id = ++wmp_session;
+
+        q += sprintf(q, "Server: Cougar 4.1.0.3923\r\nCache-Control: no-cache\r\nPragma: client-id=%d\r\nPragma: features=\"broadcast\"\r\n", c->wmp_client_id);
+        mime_type = "application/octet-stream"; 
     }
     q += sprintf(q, "Content-Type: %s\r\n", mime_type);
     q += sprintf(q, "\r\n");
@@ -756,6 +1116,17 @@ static int http_parse_request(HTTPContext *c)
     return 0;
 }
 
+static int fmt_bytecount(char *q, INT64 count)
+{
+    static const char *suffix = " kMGTP";
+    const char *s;
+
+    for (s = suffix; count >= 100000 && s[1]; count /= 1000, s++) {
+    }
+
+    return sprintf(q, "%lld%c", count, *s);
+}
+
 static void compute_stats(HTTPContext *c)
 {
     HTTPContext *c1;
@@ -763,6 +1134,16 @@ static void compute_stats(HTTPContext *c)
     char *q, *p;
     time_t ti;
     int i;
+    char *new_buffer;
+
+    new_buffer = av_malloc(65536);
+    if (new_buffer) {
+        av_free(c->buffer);
+        c->buffer_size = 65536;
+        c->buffer = new_buffer;
+        c->buffer_ptr = c->buffer;
+        c->buffer_end = c->buffer + c->buffer_size;
+    }
 
     q = c->buffer;
     q += sprintf(q, "HTTP/1.0 200 OK\r\n");
@@ -770,12 +1151,16 @@ static void compute_stats(HTTPContext *c)
     q += sprintf(q, "Pragma: no-cache\r\n");
     q += sprintf(q, "\r\n");
     
-    q += sprintf(q, "<HEAD><TITLE>FFServer Status</TITLE></HEAD>\n<BODY>");
+    q += sprintf(q, "<HEAD><TITLE>FFServer Status</TITLE>\n");
+    if (c->stream->feed_filename) {
+        q += sprintf(q, "<link rel=\"shortcut icon\" href=\"%s\">\n", c->stream->feed_filename);
+    }
+    q += sprintf(q, "</HEAD>\n<BODY>");
     q += sprintf(q, "<H1>FFServer Status</H1>\n");
     /* format status */
     q += sprintf(q, "<H2>Available Streams</H2>\n");
     q += sprintf(q, "<TABLE cellspacing=0 cellpadding=4>\n");
-    q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>kbytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n");
+    q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>bytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n");
     stream = first_stream;
     while (stream != NULL) {
         char sfilename[1024];
@@ -794,8 +1179,9 @@ static void compute_stats(HTTPContext *c)
             
             q += sprintf(q, "<TR><TD><A HREF=\"/%s\">%s</A> ", 
                          sfilename, stream->filename);
-            q += sprintf(q, "<td align=right> %d <td align=right> %lld",
-                        stream->conns_served, stream->bytes_served / 1000);
+            q += sprintf(q, "<td align=right> %d <td align=right> ",
+                        stream->conns_served);
+            q += fmt_bytecount(q, stream->bytes_served);
             switch(stream->stream_type) {
             case STREAM_TYPE_LIVE:
                 {
@@ -856,6 +1242,31 @@ static void compute_stats(HTTPContext *c)
     while (stream != NULL) {
         if (stream->feed == stream) {
             q += sprintf(q, "<h2>Feed %s</h2>", stream->filename);
+            if (stream->pid) {
+                FILE *pid_stat;
+                char ps_cmd[64];
+
+                q += sprintf(q, "Running as pid %d.\n", stream->pid);
+
+#ifdef linux
+                /* This is somewhat linux specific I guess */
+                snprintf(ps_cmd, sizeof(ps_cmd), "ps -o \"%%cpu,bsdtime\" --no-headers %d", stream->pid);
+
+                pid_stat = popen(ps_cmd, "r");
+                if (pid_stat) {
+                    char cpuperc[10];
+                    char cpuused[64];
+
+                    if (fscanf(pid_stat, "%10s %64s", cpuperc, cpuused) == 2) {
+                        q += sprintf(q, "Currently using %s%% of the cpu. Total time used %s.\n",
+                            cpuperc, cpuused);
+                    }
+                    fclose(pid_stat);
+                }
+#endif
+
+                q += sprintf(q, "<p>");
+            }
             q += sprintf(q, "<table cellspacing=0 cellpadding=4><tr><th>Stream<th>type<th>kbits/s<th align=left>codec<th align=left>Parameters\n");
 
             for (i = 0; i < stream->nb_streams; i++) {
@@ -872,8 +1283,8 @@ static void compute_stats(HTTPContext *c)
                     break;
                 case CODEC_TYPE_VIDEO:
                     type = "video";
-                    sprintf(parameters, "%dx%d, q=%d-%d", st->codec.width, st->codec.height,
-                                st->codec.qmin, st->codec.qmax);
+                    sprintf(parameters, "%dx%d, q=%d-%d, fps=%d", st->codec.width, st->codec.height,
+                                st->codec.qmin, st->codec.qmax, st->codec.frame_rate / FRAME_RATE_BASE);
                     break;
                 default:
                     av_abort();
@@ -927,18 +1338,33 @@ static void compute_stats(HTTPContext *c)
                  nb_bandwidth, nb_max_bandwidth);
 
     q += sprintf(q, "<TABLE>\n");
-    q += sprintf(q, "<TR><TD>#<TD>File<TD>IP<TD>State<TD>Size\n");
+    q += sprintf(q, "<TR><Th>#<Th>File<Th>IP<Th>State<Th>Target bits/sec<Th>Actual bits/sec<Th>Bytes transferred\n");
     c1 = first_http_ctx;
     i = 0;
-    while (c1 != NULL && q < (char *) c->buffer + sizeof(c->buffer) - 2048) {
+    while (c1 != NULL && q < (char *) c->buffer + c->buffer_size - 2048) {
+        int bitrate;
+        int j;
+
+        bitrate = 0;
+        for (j = 0; j < c1->stream->nb_streams; j++) {
+            if (c1->feed_streams[j] >= 0) {
+                bitrate += c1->stream->feed->streams[c1->feed_streams[j]]->codec.bit_rate;
+            }
+        }
+
         i++;
         p = inet_ntoa(c1->from_addr.sin_addr);
-        q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <TD> %Ld\n", 
+        q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <td align=right>", 
                      i, c1->stream->filename, 
                      c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "",
                      p, 
-                     http_state[c1->state],
-                     c1->data_count);
+                     http_state[c1->state]);
+        q += fmt_bytecount(q, bitrate);
+        q += sprintf(q, "<td align=right>");
+        q += fmt_bytecount(q, compute_datarate(&c1->datarate, c1->data_count) * 8);
+        q += sprintf(q, "<td align=right>");
+        q += fmt_bytecount(q, c1->data_count);
+        *q++ = '\n';
         c1 = c1->next;
     }
     q += sprintf(q, "</TABLE>\n");
@@ -962,8 +1388,26 @@ static void http_write_packet(void *opaque,
     if (c->buffer_ptr == c->buffer_end || !c->buffer_ptr)
         c->buffer_ptr = c->buffer_end = c->buffer;
 
-    if (c->buffer_end - c->buffer + size > IOBUFFER_MAX_SIZE)
-        av_abort();
+    if (c->buffer_end - c->buffer + size > c->buffer_size) {
+        int new_buffer_size = c->buffer_size * 2;
+        UINT8 *new_buffer;
+
+        if (new_buffer_size <= c->buffer_end - c->buffer + size) {
+            new_buffer_size = c->buffer_end - c->buffer + size + c->buffer_size;
+        }
+
+        new_buffer = av_malloc(new_buffer_size);
+        if (new_buffer) {
+            memcpy(new_buffer, c->buffer, c->buffer_end - c->buffer);
+            c->buffer_end += (new_buffer - c->buffer);
+            c->buffer_ptr += (new_buffer - c->buffer);
+            av_free(c->buffer);
+            c->buffer = new_buffer;
+            c->buffer_size = new_buffer_size;
+        } else {
+            av_abort();
+        }
+    }
 
     memcpy(c->buffer_end, buf, size);
     c->buffer_end += size;
@@ -1023,6 +1467,11 @@ static int http_prepare_data(HTTPContext *c)
     switch(c->state) {
     case HTTPSTATE_SEND_DATA_HEADER:
         memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx));
+        pstrcpy(c->fmt_ctx.author, sizeof(c->fmt_ctx.author), c->stream->author);
+        pstrcpy(c->fmt_ctx.comment, sizeof(c->fmt_ctx.comment), c->stream->comment);
+        pstrcpy(c->fmt_ctx.copyright, sizeof(c->fmt_ctx.copyright), c->stream->copyright);
+        pstrcpy(c->fmt_ctx.title, sizeof(c->fmt_ctx.title), c->stream->title);
+
         if (c->stream->feed) {
             /* open output stream by using specified codecs */
             c->fmt_ctx.oformat = c->stream->fmt;
@@ -1054,7 +1503,7 @@ static int http_prepare_data(HTTPContext *c)
             }
             c->got_key_frame = 0;
         }
-        init_put_byte(&c->fmt_ctx.pb, c->pbuffer, PACKET_MAX_SIZE,
+        init_put_byte(&c->fmt_ctx.pb, c->pbuffer, c->pbuffer_size,
                       1, c, NULL, http_write_packet, NULL);
         c->fmt_ctx.pb.is_streamed = 1;
         /* prepare header */
@@ -1132,7 +1581,7 @@ static int http_prepare_data(HTTPContext *c)
             }
 
             if (c->stream->max_time && 
-                c->stream->max_time + c->start_time > time(0)) {
+                c->stream->max_time + c->start_time - cur_time < 0) {
                 /* We have timed out */
                 c->state = HTTPSTATE_SEND_DATA_TRAILER;
             } else if (av_read_packet(c->fmt_in, &pkt) < 0) {
@@ -1149,8 +1598,21 @@ static int http_prepare_data(HTTPContext *c)
                 /* send it to the appropriate stream */
                 if (c->stream->feed) {
                     /* if coming from a feed, select the right stream */
+                    if (c->switch_pending) {
+                        c->switch_pending = 0;
+                        for(i=0;i<c->stream->nb_streams;i++) {
+                            if (c->switch_feed_streams[i] == pkt.stream_index) {
+                                if (pkt.flags & PKT_FLAG_KEY) {
+                                    do_switch_stream(c, i);
+                                }
+                            }
+                            if (c->switch_feed_streams[i] >= 0) {
+                                c->switch_pending = 1;
+                            }
+                        }
+                    }
                     for(i=0;i<c->stream->nb_streams;i++) {
-                        if (c->stream->feed_streams[i] == pkt.stream_index) {
+                        if (c->feed_streams[i] == pkt.stream_index) {
                             pkt.stream_index = i;
                             if (pkt.flags & PKT_FLAG_KEY) {
                                 c->got_key_frame |= 1 << i;
@@ -1232,6 +1694,7 @@ static int http_send_data(HTTPContext *c)
         } else {
             c->buffer_ptr += len;
             c->data_count += len;
+            update_datarate(&c->datarate, c->data_count);
             if (c->stream)
                 c->stream->bytes_served += len;
         }
@@ -1282,6 +1745,7 @@ static int http_receive_data(HTTPContext *c)
         } else {
             c->buffer_ptr += len;
             c->data_count += len;
+            update_datarate(&c->datarate, c->data_count);
         }
     }
 
@@ -1423,7 +1887,15 @@ void build_feed_streams(void)
                 for(i=0;i<stream->nb_streams;i++) {
                     stream->feed_streams[i] = add_av_stream(feed, stream->streams[i]);
                 }
-            } else {
+            }
+        }
+    }
+
+    /* gather all streams */
+    for(stream = first_stream; stream != NULL; stream = stream->next) {
+        feed = stream->feed;
+        if (feed) {
+            if (stream->is_feed) {
                 for(i=0;i<stream->nb_streams;i++) {
                     stream->feed_streams[i] = i;
                 }
@@ -1596,7 +2068,7 @@ int parse_ffconfig(const char *filename)
     char arg[1024];
     const char *p;
     int val, errors, line_num;
-    FFStream **last_stream, *stream;
+    FFStream **last_stream, *stream, *redirect;
     FFStream **last_feed, *feed;
     AVCodecContext audio_enc, video_enc;
     int audio_id, video_id;
@@ -1615,6 +2087,7 @@ int parse_ffconfig(const char *filename)
     last_feed = &first_feed;
     stream = NULL;
     feed = NULL;
+    redirect = NULL;
     audio_id = CODEC_ID_NONE;
     video_id = CODEC_ID_NONE;
     for(;;) {
@@ -1689,6 +2162,31 @@ int parse_ffconfig(const char *filename)
                 feed->is_feed = 1;
                 feed->feed = feed; /* self feeding :-) */
             }
+        } else if (!strcasecmp(cmd, "Launch")) {
+            if (feed) {
+                int i;
+
+                feed->child_argv = (char **) av_mallocz(64 * sizeof(char *));
+
+                feed->child_argv[0] = av_malloc(7);
+                strcpy(feed->child_argv[0], "ffmpeg");
+
+                for (i = 1; i < 62; i++) {
+                    char argbuf[256];
+
+                    get_arg(argbuf, sizeof(argbuf), &p);
+                    if (!argbuf[0])
+                        break;
+
+                    feed->child_argv[i] = av_malloc(strlen(argbuf + 1));
+                    strcpy(feed->child_argv[i], argbuf);
+                }
+
+                feed->child_argv[i] = av_malloc(30 + strlen(feed->filename));
+
+                snprintf(feed->child_argv[i], 256, "http://127.0.0.1:%d/%s", 
+                    ntohs(my_addr.sin_port), feed->filename);
+            }
         } else if (!strcasecmp(cmd, "File")) {
             if (feed) {
                 get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p);
@@ -1796,6 +2294,30 @@ int parse_ffconfig(const char *filename)
                 audio_id = stream->fmt->audio_codec;
                 video_id = stream->fmt->video_codec;
             }
+        } else if (!strcasecmp(cmd, "FaviconURL")) {
+            if (stream && stream->stream_type == STREAM_TYPE_STATUS) {
+                get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p);
+            } else {
+                fprintf(stderr, "%s:%d: FaviconURL only permitted for status streams\n", 
+                            filename, line_num);
+                errors++;
+            }
+        } else if (!strcasecmp(cmd, "Author")) {
+            if (stream) {
+                get_arg(stream->author, sizeof(stream->author), &p);
+            }
+        } else if (!strcasecmp(cmd, "Comment")) {
+            if (stream) {
+                get_arg(stream->comment, sizeof(stream->comment), &p);
+            }
+        } else if (!strcasecmp(cmd, "Copyright")) {
+            if (stream) {
+                get_arg(stream->copyright, sizeof(stream->copyright), &p);
+            }
+        } else if (!strcasecmp(cmd, "Title")) {
+            if (stream) {
+                get_arg(stream->title, sizeof(stream->title), &p);
+            }
         } else if (!strcasecmp(cmd, "Preroll")) {
             get_arg(arg, sizeof(arg), &p);
             if (stream) {
@@ -1824,7 +2346,7 @@ int parse_ffconfig(const char *filename)
         } else if (!strcasecmp(cmd, "MaxTime")) {
             get_arg(arg, sizeof(arg), &p);
             if (stream) {
-                stream->max_time = atoi(arg);
+                stream->max_time = atoi(arg) * 1000;
             }
         } else if (!strcasecmp(cmd, "AudioBitRate")) {
             get_arg(arg, sizeof(arg), &p);
@@ -1925,6 +2447,40 @@ int parse_ffconfig(const char *filename)
                 }
             }
             stream = NULL;
+        } else if (!strcasecmp(cmd, "<Redirect")) {
+            /*********************************************/
+            char *q;
+            if (stream || feed || redirect) {
+                fprintf(stderr, "%s:%d: Already in a tag\n",
+                        filename, line_num);
+                errors++;
+            } else {
+                redirect = av_mallocz(sizeof(FFStream));
+                *last_stream = redirect;
+                last_stream = &redirect->next;
+
+                get_arg(redirect->filename, sizeof(redirect->filename), &p);
+                q = strrchr(redirect->filename, '>');
+                if (*q)
+                    *q = '\0';
+                redirect->stream_type = STREAM_TYPE_REDIRECT;
+            }
+        } else if (!strcasecmp(cmd, "URL")) {
+            if (redirect) {
+                get_arg(redirect->feed_filename, sizeof(redirect->feed_filename), &p);
+            }
+        } else if (!strcasecmp(cmd, "</Redirect>")) {
+            if (!redirect) {
+                fprintf(stderr, "%s:%d: No corresponding <Redirect> for </Redirect>\n",
+                        filename, line_num);
+                errors++;
+            }
+            if (!redirect->feed_filename[0]) {
+                fprintf(stderr, "%s:%d: No URL found for <Redirect>\n",
+                        filename, line_num);
+                errors++;
+            }
+            redirect = NULL;
         } else {
             fprintf(stderr, "%s:%d: Incorrect keyword: '%s'\n", 
                     filename, line_num, cmd);
@@ -1997,17 +2553,46 @@ void licence(void)
     );
 }
 
+static void handle_child_exit(int sig)
+{
+    pid_t pid;
+    int status;
+
+    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+        FFStream *feed;
+
+        for (feed = first_feed; feed; feed = feed->next) {
+            if (feed->pid == pid) {
+                int uptime = time(0) - feed->pid_start;
+
+                feed->pid = 0;
+                fprintf(stderr, "%s: Pid %d exited with status %d after %d seconds\n", feed->filename, pid, status, uptime);
+
+                if (uptime < 30) {
+                    /* Turn off any more restarts */
+                    feed->child_argv = 0;
+                }    
+            }
+        }
+    }
+
+    need_to_start_children = 1;
+}
+
 int main(int argc, char **argv)
 {
     const char *config_filename;
     int c;
+    struct sigaction sigact;
 
     register_all();
 
     config_filename = "/etc/ffserver.conf";
 
+    my_program_name = argv[0];
+
     for(;;) {
-        c = getopt_long_only(argc, argv, "Lh?f:", NULL, NULL);
+        c = getopt_long_only(argc, argv, "ndLh?f:", NULL, NULL);
         if (c == -1)
             break;
         switch(c) {
@@ -2018,6 +2603,12 @@ int main(int argc, char **argv)
         case 'h':
             help();
             exit(1);
+        case 'n':
+            no_launch = 1;
+            break;
+        case 'd':
+            ffserver_debug = 1;
+            break;
         case 'f':
             config_filename = optarg;
             break;
@@ -2026,6 +2617,8 @@ int main(int argc, char **argv)
         }
     }
 
+    putenv("http_proxy");               /* Kill the http_proxy */
+
     /* address on which the server will handle connections */
     my_addr.sin_family = AF_INET;
     my_addr.sin_port = htons (8080);
@@ -2035,6 +2628,11 @@ int main(int argc, char **argv)
     first_stream = NULL;
     logfilename[0] = '\0';
 
+    memset(&sigact, 0, sizeof(sigact));
+    sigact.sa_handler = handle_child_exit;
+    sigact.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+    sigaction(SIGCHLD, &sigact, 0);
+
     if (parse_ffconfig(config_filename) < 0) {
         fprintf(stderr, "Incorrect config file - exiting.\n");
         exit(1);