put all integer init code to compute n^(4/3) - memory alloc and header fixes
[ffmpeg.git] / ffserver.c
index 152dd37..3fc8250 100644 (file)
@@ -63,7 +63,8 @@ const char *http_state[] = {
     "WAIT_FEED",
 };
 
-#define IOBUFFER_MAX_SIZE 16384
+#define IOBUFFER_MAX_SIZE 32768
+#define PACKET_MAX_SIZE 16384
 
 /* coef for exponential mean for bitrate estimation in statistics */
 #define AVG_COEF 0.9
@@ -79,11 +80,10 @@ typedef struct HTTPContext {
     struct sockaddr_in from_addr; /* origin */
     struct pollfd *poll_entry; /* used when polling */
     long timeout;
-    UINT8 buffer[IOBUFFER_MAX_SIZE];
     UINT8 *buffer_ptr, *buffer_end;
     int http_error;
     struct HTTPContext *next;
-    int got_key_frame[MAX_STREAMS]; /* for each type */
+    int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */
     INT64 data_count;
     /* feed input */
     int feed_fd;
@@ -93,6 +93,13 @@ typedef struct HTTPContext {
     struct FFStream *stream;
     AVFormatContext fmt_ctx;
     int last_packet_sent; /* true if last data packet was sent */
+    int suppress_log;
+    int bandwidth;
+    char protocol[16];
+    char method[16];
+    char url[128];
+    UINT8 buffer[IOBUFFER_MAX_SIZE];
+    UINT8 pbuffer[PACKET_MAX_SIZE];
 } HTTPContext;
 
 /* each generated stream is described here */
@@ -108,6 +115,8 @@ typedef struct FFStream {
     struct FFStream *feed;
     AVFormat *fmt;
     int nb_streams;
+    int prebuffer;      /* Number of millseconds early to start */
+    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
@@ -116,6 +125,8 @@ typedef struct FFStream {
     /* feed specific */
     int feed_opened;     /* true if someone if writing to feed */
     int is_feed;         /* true if it is a feed */
+    int conns_served;
+    INT64 bytes_served;
     INT64 feed_max_size;      /* maximum storage size */
     INT64 feed_write_index;   /* current write position in feed (it wraps round) */
     INT64 feed_size;          /* current size of feed */
@@ -144,6 +155,9 @@ static int http_receive_data(HTTPContext *c);
 int nb_max_connections;
 int nb_connections;
 
+int nb_max_bandwidth;
+int nb_bandwidth;
+
 static long gettime_ms(void)
 {
     struct timeval tv;
@@ -159,11 +173,34 @@ static void http_log(char *fmt, ...)
     va_list ap;
     va_start(ap, fmt);
     
-    if (logfile)
+    if (logfile) {
         vfprintf(logfile, fmt, ap);
+        fflush(logfile);
+    }
     va_end(ap);
 }
 
+static void log_connection(HTTPContext *c)
+{
+    char buf1[32], buf2[32], *p;
+    time_t ti;
+
+    if (c->suppress_log) 
+        return;
+
+    /* XXX: reentrant function ? */
+    p = inet_ntoa(c->from_addr.sin_addr);
+    strcpy(buf1, p);
+    ti = time(NULL);
+    p = ctime(&ti);
+    strcpy(buf2, p);
+    p = buf2 + strlen(p) - 1;
+    if (*p == '\n')
+        *p = '\0';
+    http_log("%s - - [%s] \"%s %s %s\" %d %lld\n", 
+             buf1, buf2, c->method, c->url, c->protocol, (c->http_error ? c->http_error : 200), c->data_count);
+}
+
 /* main loop of the http server */
 static int http_server(struct sockaddr_in my_addr)
 {
@@ -237,7 +274,7 @@ static int http_server(struct sockaddr_in my_addr)
                 /* need to catch errors */
                 c->poll_entry = poll_entry;
                 poll_entry->fd = fd;
-                poll_entry->events = 0;
+                poll_entry->events = POLLIN;/* Maybe this will work */
                 poll_entry++;
                 break;
             default:
@@ -262,10 +299,12 @@ static int http_server(struct sockaddr_in my_addr)
             c = *cp;
             if (handle_http (c, cur_time) < 0) {
                 /* close and free the connection */
+                log_connection(c);
                 close(c->fd);
                 if (c->fmt_in)
                     av_close_input_file(c->fmt_in);
                 *cp = c->next;
+                nb_bandwidth -= c->bandwidth;
                 free(c);
                 nb_connections--;
             } else {
@@ -361,6 +400,8 @@ static int handle_http(HTTPContext *c, long cur_time)
             }
         } else {
             c->buffer_ptr += len;
+            c->stream->bytes_served += len;
+            c->data_count += len;
             if (c->buffer_ptr >= c->buffer_end) {
                 /* if error, exit */
                 if (c->http_error)
@@ -395,7 +436,7 @@ static int handle_http(HTTPContext *c, long cur_time)
         break;
     case HTTPSTATE_WAIT_FEED:
         /* no need to read if no events */
-        if (c->poll_entry->revents & (POLLERR | POLLHUP))
+        if (c->poll_entry->revents & (POLLIN | POLLERR | POLLHUP))
             return -1;
 
         /* nothing to do, we'll be waken up by incoming feed packets */
@@ -406,11 +447,14 @@ static int handle_http(HTTPContext *c, long cur_time)
     return 0;
 }
 
+
 /* parse http request and prepare header */
 static int http_parse_request(HTTPContext *c)
 {
     char *p;
     int post;
+    int doing_asx;
+    int doing_ram;
     char cmd[32];
     char info[1024], *filename;
     char url[1024], *q;
@@ -418,6 +462,7 @@ static int http_parse_request(HTTPContext *c)
     char msg[1024];
     const char *mime_type;
     FFStream *stream;
+    int i;
 
     p = c->buffer;
     q = cmd;
@@ -427,6 +472,9 @@ static int http_parse_request(HTTPContext *c)
         p++;
     }
     *q = '\0';
+
+    strlcpy(c->method, cmd, sizeof(c->method));
+
     if (!strcmp(cmd, "GET"))
         post = 0;
     else if (!strcmp(cmd, "POST"))
@@ -443,6 +491,8 @@ static int http_parse_request(HTTPContext *c)
     }
     *q = '\0';
 
+    strlcpy(c->url, url, sizeof(c->url));
+
     while (isspace(*p)) p++;
     q = protocol;
     while (!isspace(*p) && *p != '\0') {
@@ -453,6 +503,8 @@ static int http_parse_request(HTTPContext *c)
     *q = '\0';
     if (strcmp(protocol, "HTTP/1.0") && strcmp(protocol, "HTTP/1.1"))
         return -1;
+
+    strlcpy(c->protocol, protocol, sizeof(c->protocol));
     
     /* find the filename and the optional info string in the request */
     p = url;
@@ -461,12 +513,28 @@ static int http_parse_request(HTTPContext *c)
     filename = p;
     p = strchr(p, '?');
     if (p) {
-        strcpy(info, p);
+        strlcpy(info, p, sizeof(info));
         *p = '\0';
     } else {
         info[0] = '\0';
     }
 
+    if (strlen(filename) > 4 && strcmp(".asx", filename + strlen(filename) - 4) == 0) {
+        doing_asx = 1;
+        filename[strlen(filename)-1] = 'f';
+    } else {
+        doing_asx = 0;
+    }
+
+    if (strlen(filename) > 4 && 
+        (strcmp(".rpm", filename + strlen(filename) - 4) == 0 ||
+         strcmp(".ram", filename + strlen(filename) - 4) == 0)) {
+        doing_ram = 1;
+        strcpy(filename + strlen(filename)-2, "m");
+    } else {
+        doing_ram = 0;
+    }
+
     stream = first_stream;
     while (stream != NULL) {
         if (!strcmp(stream->filename, filename))
@@ -477,30 +545,149 @@ static int http_parse_request(HTTPContext *c)
         sprintf(msg, "File '%s' not found", url);
         goto send_error;
     }
-    c->stream = stream;
+
+    if (post == 0 && stream->stream_type == STREAM_TYPE_LIVE) {
+        /* See if we meet the bandwidth requirements */
+        for(i=0;i<stream->nb_streams;i++) {
+            AVStream *st = stream->streams[i];
+            switch(st->codec.codec_type) {
+            case CODEC_TYPE_AUDIO:
+                c->bandwidth += st->codec.bit_rate;
+                break;
+            case CODEC_TYPE_VIDEO:
+                c->bandwidth += st->codec.bit_rate;
+                break;
+            default:
+                abort();
+            }
+        }
+    }
+
+    c->bandwidth /= 1000;
+    nb_bandwidth += c->bandwidth;
+
+    if (post == 0 && nb_max_bandwidth < nb_bandwidth) {
+        c->http_error = 200;
+        q = c->buffer;
+        q += sprintf(q, "HTTP/1.0 200 Server too busy\r\n");
+        q += sprintf(q, "Content-type: text/html\r\n");
+        q += sprintf(q, "\r\n");
+        q += sprintf(q, "<html><head><title>Too busy</title></head><body>\r\n");
+        q += sprintf(q, "The server is too busy to serve your request at this time.<p>\r\n");
+        q += sprintf(q, "The bandwidth being served (including your stream) is %dkbit/sec, and this exceeds the limit of %dkbit/sec\r\n",
+            nb_bandwidth, nb_max_bandwidth);
+        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;
+    }
     
-    /* should do it after so that the size can be computed */
-    {
-        char buf1[32], buf2[32], *p;
-        time_t ti;
-        /* XXX: reentrant function ? */
-        p = inet_ntoa(c->from_addr.sin_addr);
-        strcpy(buf1, p);
-        ti = time(NULL);
-        p = ctime(&ti);
-        strcpy(buf2, p);
-        p = buf2 + strlen(p) - 1;
-        if (*p == '\n')
-            *p = '\0';
-        http_log("%s - - [%s] \"%s %s %s\" %d %d\n", 
-                 buf1, buf2, cmd, url, protocol, 200, 1024);
+    if (doing_asx || doing_ram) {
+        char *hostinfo = 0;
+        
+        for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
+            if (strncasecmp(p, "Host:", 5) == 0) {
+                hostinfo = p + 5;
+                break;
+            }
+            p = strchr(p, '\n');
+            if (!p)
+                break;
+
+            p++;
+        }
+
+        if (hostinfo) {
+            char *eoh;
+            char hostbuf[260];
+
+            while (isspace(*hostinfo))
+                hostinfo++;
+
+            eoh = strchr(hostinfo, '\n');
+            if (eoh) {
+                if (eoh[-1] == '\r')
+                    eoh--;
+
+                if (eoh - hostinfo < sizeof(hostbuf) - 1) {
+                    memcpy(hostbuf, hostinfo, eoh - hostinfo);
+                    hostbuf[eoh - hostinfo] = 0;
+
+                    c->http_error = 200;
+                    q = c->buffer;
+                    if (doing_asx) {
+                        q += sprintf(q, "HTTP/1.0 200 ASX Follows\r\n");
+                        q += sprintf(q, "Content-type: video/x-ms-asf\r\n");
+                        q += sprintf(q, "\r\n");
+                        q += sprintf(q, "<ASX Version=\"3\">\r\n");
+                        q += sprintf(q, "<!-- Autogenerated by ffserver -->\r\n");
+                        q += sprintf(q, "<ENTRY><REF HREF=\"http://%s/%s%s\"/></ENTRY>\r\n", 
+                                hostbuf, filename, info);
+                        q += sprintf(q, "</ASX>\r\n");
+                    } else if (doing_ram) {
+                        q += sprintf(q, "HTTP/1.0 200 RAM Follows\r\n");
+                        q += sprintf(q, "Content-type: audio/x-pn-realaudio\r\n");
+                        q += sprintf(q, "\r\n");
+                        q += sprintf(q, "# Autogenerated by ffserver\r\n");
+                        q += sprintf(q, "http://%s/%s%s\r\n", 
+                                hostbuf, filename, info);
+                    } else
+                        abort();
+
+                    /* prepare output buffer */
+                    c->buffer_ptr = c->buffer;
+                    c->buffer_end = q;
+                    c->state = HTTPSTATE_SEND_HEADER;
+                    return 0;
+                }
+            }
+        }
+
+        sprintf(msg, "ASX/RAM file not handled");
+        goto send_error;
     }
 
+    c->stream = stream;
+    stream->conns_served++;
+
     /* XXX: add there authenticate and IP match */
 
     if (post) {
         /* if post, it means a feed is being sent */
         if (!stream->is_feed) {
+            /* However it might be a status report from WMP! Lets log the data
+             * as it might come in handy one day
+             */
+            char *logline = 0;
+            
+            for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
+                if (strncasecmp(p, "Pragma: log-line=", 17) == 0) {
+                    logline = p;
+                    break;
+                }
+                p = strchr(p, '\n');
+                if (!p)
+                    break;
+
+                p++;
+            }
+
+            if (logline) {
+                char *eol = strchr(logline, '\n');
+
+                logline += 17;
+
+                if (eol) {
+                    if (eol[-1] == '\r')
+                        eol--;
+                    http_log("%.*s\n", eol - logline, logline);
+                    c->suppress_log = 1;
+                }
+            }
+            
             sprintf(msg, "POST command not handled");
             goto send_error;
         }
@@ -528,13 +715,16 @@ static int http_parse_request(HTTPContext *c)
     mime_type = c->stream->fmt->mime_type;
     if (!mime_type)
         mime_type = "application/x-octet_stream";
-    q += sprintf(q, "Content-type: %s\r\n", mime_type);
     q += sprintf(q, "Pragma: no-cache\r\n");
 
     /* for asf, we need extra headers */
     if (!strcmp(c->stream->fmt->name,"asf")) {
-        q += sprintf(q, "Pragma: features=broadcast\r\n");
+        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";
     }
+    q += sprintf(q, "Content-Type: %s\r\n", mime_type);
     q += sprintf(q, "\r\n");
     
     /* prepare output buffer */
@@ -584,49 +774,114 @@ static void compute_stats(HTTPContext *c)
     q += sprintf(q, "<HEAD><TITLE>FFServer Status</TITLE></HEAD>\n<BODY>");
     q += sprintf(q, "<H1>FFServer Status</H1>\n");
     /* format status */
-    q += sprintf(q, "<H1>Available Streams</H1>\n");
-    q += sprintf(q, "<TABLE>\n");
-    q += sprintf(q, "<TR><TD>Path<TD>Format<TD>Bit rate (kbits/s)<TD>Video<TD>Audio<TD>Feed\n");
+    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");
     stream = first_stream;
     while (stream != NULL) {
-        q += sprintf(q, "<TR><TD><A HREF=\"/%s\">%s</A> ", 
-                     stream->filename, stream->filename);
-        switch(stream->stream_type) {
-        case STREAM_TYPE_LIVE:
-            {
-                int audio_bit_rate = 0;
-                int video_bit_rate = 0;
-
-                for(i=0;i<stream->nb_streams;i++) {
-                    AVStream *st = stream->streams[i];
-                    switch(st->codec.codec_type) {
-                    case CODEC_TYPE_AUDIO:
-                        audio_bit_rate += st->codec.bit_rate;
-                        break;
-                    case CODEC_TYPE_VIDEO:
-                        video_bit_rate += st->codec.bit_rate;
-                        break;
-                    }
+        char sfilename[1024];
+        char *eosf;
+
+        if (stream->feed != stream) {
+            strlcpy(sfilename, stream->filename, sizeof(sfilename) - 1);
+            eosf = sfilename + strlen(sfilename);
+            if (eosf - sfilename >= 4) {
+                if (strcmp(eosf - 4, ".asf") == 0) {
+                    strcpy(eosf - 4, ".asx");
+                } else if (strcmp(eosf - 3, ".rm") == 0) {
+                    strcpy(eosf - 3, ".ram");
                 }
-                q += sprintf(q, "<TD> %s <TD> %d <TD> %d <TD> %d", 
-                             stream->fmt->name,
-                             (audio_bit_rate + video_bit_rate) / 1000,
-                             video_bit_rate / 1000, audio_bit_rate / 1000);
-                if (stream->feed) {
-                    q += sprintf(q, "<TD>%s", stream->feed->filename);
-                } else {
-                    q += sprintf(q, "<TD>%s", stream->feed_filename);
+            }
+            
+            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);
+            switch(stream->stream_type) {
+            case STREAM_TYPE_LIVE:
+                {
+                    int audio_bit_rate = 0;
+                    int video_bit_rate = 0;
+                    char *audio_codec_name = "";
+                    char *video_codec_name = "";
+                    char *audio_codec_name_extra = "";
+                    char *video_codec_name_extra = "";
+
+                    for(i=0;i<stream->nb_streams;i++) {
+                        AVStream *st = stream->streams[i];
+                        AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
+                        switch(st->codec.codec_type) {
+                        case CODEC_TYPE_AUDIO:
+                            audio_bit_rate += st->codec.bit_rate;
+                            if (codec) {
+                                if (*audio_codec_name)
+                                    audio_codec_name_extra = "...";
+                                audio_codec_name = codec->name;
+                            }
+                            break;
+                        case CODEC_TYPE_VIDEO:
+                            video_bit_rate += st->codec.bit_rate;
+                            if (codec) {
+                                if (*video_codec_name)
+                                    video_codec_name_extra = "...";
+                                video_codec_name = codec->name;
+                            }
+                            break;
+                        default:
+                            abort();
+                        }
+                    }
+                    q += sprintf(q, "<TD align=center> %s <TD align=right> %d <TD align=right> %d <TD> %s %s <TD align=right> %d <TD> %s %s", 
+                                 stream->fmt->name,
+                                 (audio_bit_rate + video_bit_rate) / 1000,
+                                 video_bit_rate / 1000, video_codec_name, video_codec_name_extra,
+                                 audio_bit_rate / 1000, audio_codec_name, audio_codec_name_extra);
+                    if (stream->feed) {
+                        q += sprintf(q, "<TD>%s", stream->feed->filename);
+                    } else {
+                        q += sprintf(q, "<TD>%s", stream->feed_filename);
+                    }
+                    q += sprintf(q, "\n");
                 }
-                q += sprintf(q, "\n");
+                break;
+            default:
+                q += sprintf(q, "<TD align=center> - <TD align=right> - <TD align=right> - <td><td align=right> - <TD>\n");
+                break;
             }
-            break;
-        default:
-            q += sprintf(q, "<TD> - <TD> - <TD> - <TD> -\n");
-            break;
         }
         stream = stream->next;
     }
     q += sprintf(q, "</TABLE>\n");
+
+    stream = first_stream;
+    while (stream != NULL) {
+        if (stream->feed == stream) {
+            q += sprintf(q, "<h2>Feed %s</h2>", stream->filename);
+            q += sprintf(q, "<table cellspacing=0 cellpadding=4><tr><th>Stream<th>type<th>kbits/s<th align=left>codec\n");
+
+            for (i = 0; i < stream->nb_streams; i++) {
+                AVStream *st = stream->streams[i];
+                AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
+                char *type = "unknown";
+
+                switch(st->codec.codec_type) {
+                case CODEC_TYPE_AUDIO:
+                    type = "audio";
+                    break;
+                case CODEC_TYPE_VIDEO:
+                    type = "video";
+                    break;
+                default:
+                    abort();
+                }
+                q += sprintf(q, "<tr><td align=right>%d<td>%s<td align=right>%d<td>%s\n",
+                        i, type, st->codec.bit_rate/1000, codec ? codec->name : "");
+            }
+            q += sprintf(q, "</table>\n");
+
+        }       
+        stream = stream->next;
+    }
     
 #if 0
     {
@@ -659,16 +914,19 @@ static void compute_stats(HTTPContext *c)
 #endif
 
     /* connection status */
-    q += sprintf(q, "<H1>Connection Status</H1>\n");
+    q += sprintf(q, "<H2>Connection Status</H2>\n");
 
     q += sprintf(q, "Number of connections: %d / %d<BR>\n",
                  nb_connections, nb_max_connections);
 
+    q += sprintf(q, "Bandwidth in use: %dk / %dk<BR>\n",
+                 nb_bandwidth, nb_max_bandwidth);
+
     q += sprintf(q, "<TABLE>\n");
     q += sprintf(q, "<TR><TD>#<TD>File<TD>IP<TD>State<TD>Size\n");
     c1 = first_http_ctx;
     i = 0;
-    while (c1 != NULL) {
+    while (c1 != NULL && q < (char *) c->buffer + sizeof(c->buffer) - 2048) {
         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", 
@@ -684,7 +942,7 @@ static void compute_stats(HTTPContext *c)
     /* date */
     ti = time(NULL);
     p = ctime(&ti);
-    q += sprintf(q, "<HR>Generated at %s", p);
+    q += sprintf(q, "<HR size=1 noshade>Generated at %s", p);
     q += sprintf(q, "</BODY>\n</HTML>\n");
 
     c->buffer_ptr = c->buffer;
@@ -696,11 +954,15 @@ static void http_write_packet(void *opaque,
                               unsigned char *buf, int size)
 {
     HTTPContext *c = opaque;
-    if (size > IOBUFFER_MAX_SIZE)
+
+    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)
         abort();
-    memcpy(c->buffer, buf, size);
-    c->buffer_ptr = c->buffer;
-    c->buffer_end = c->buffer + size;
+
+    memcpy(c->buffer_end, buf, size);
+    c->buffer_end += size;
 }
 
 static int open_input_stream(HTTPContext *c, const char *info)
@@ -718,8 +980,11 @@ static int open_input_stream(HTTPContext *c, const char *info)
         /* compute position (absolute time) */
         if (find_info_tag(buf, sizeof(buf), "date", info)) {
             stream_pos = parse_date(buf, 0);
+        } else if (find_info_tag(buf, sizeof(buf), "buffer", info)) {
+            int prebuffer = strtol(buf, 0, 10);
+            stream_pos = gettime() - prebuffer * 1000000;
         } else {
-            stream_pos = gettime();
+            stream_pos = gettime() - c->stream->prebuffer * 1000;
         }
     } else {
         strcpy(input_filename, c->stream->feed_filename);
@@ -763,11 +1028,15 @@ static int http_prepare_data(HTTPContext *c)
                 AVStream *st;
                 st = av_mallocz(sizeof(AVStream));
                 c->fmt_ctx.streams[i] = st;
-                memcpy(st, c->stream->streams[i], sizeof(AVStream));
+                if (c->stream->feed == c->stream)
+                    memcpy(st, c->stream->streams[i], sizeof(AVStream));
+                else
+                    memcpy(st, c->stream->feed->streams[c->stream->feed_streams[i]], sizeof(AVStream));
+
                 st->codec.frame_number = 0; /* XXX: should be done in
                                                AVStream, not in codec */
-                c->got_key_frame[i] = 0;
             }
+            c->got_key_frame = 0;
         } else {
             /* open output stream by using codecs in specified file */
             c->fmt_ctx.format = c->stream->fmt;
@@ -779,10 +1048,10 @@ static int http_prepare_data(HTTPContext *c)
                 memcpy(st, c->fmt_in->streams[i], sizeof(AVStream));
                 st->codec.frame_number = 0; /* XXX: should be done in
                                                AVStream, not in codec */
-                c->got_key_frame[i] = 0;
             }
+            c->got_key_frame = 0;
         }
-        init_put_byte(&c->fmt_ctx.pb, c->buffer, IOBUFFER_MAX_SIZE,
+        init_put_byte(&c->fmt_ctx.pb, c->pbuffer, PACKET_MAX_SIZE,
                       1, c, NULL, http_write_packet, NULL);
         c->fmt_ctx.pb.is_streamed = 1;
         /* prepare header */
@@ -798,9 +1067,7 @@ static int http_prepare_data(HTTPContext *c)
             /* overflow : resync. We suppose that wptr is at this
                point a pointer to a valid packet */
             c->rptr = http_fifo.wptr;
-            for(i=0;i<c->fmt_ctx.nb_streams;i++) {
-                c->got_key_frame[i] = 0;
-            }
+            c->got_key_frame = 0;
         }
         
         start_rptr = c->rptr;
@@ -826,8 +1093,8 @@ static int http_prepare_data(HTTPContext *c)
                 if (test_header(&hdr, &st->codec)) {
                     /* only begin sending when got a key frame */
                     if (st->codec.key_frame)
-                        c->got_key_frame[i] = 1;
-                    if (c->got_key_frame[i]) {
+                        c->got_key_frame |= 1 << i;
+                    if (c->got_key_frame & (1 << i)) {
                         ret = c->fmt_ctx.format->write_packet(&c->fmt_ctx, i,
                                                                    payload, payload_size);
                     }
@@ -860,6 +1127,7 @@ static int http_prepare_data(HTTPContext *c)
                                     c->stream->feed->feed_write_index,
                                     c->stream->feed->feed_size);
             }
+            
             if (av_read_packet(c->fmt_in, &pkt) < 0) {
                 if (c->stream->feed && c->stream->feed->feed_opened) {
                     /* if coming from feed, it means we reached the end of the
@@ -877,15 +1145,42 @@ static int http_prepare_data(HTTPContext *c)
                     for(i=0;i<c->stream->nb_streams;i++) {
                         if (c->stream->feed_streams[i] == pkt.stream_index) {
                             pkt.stream_index = i;
-                            goto send_it;
+                            if (pkt.flags & PKT_FLAG_KEY) {
+                                c->got_key_frame |= 1 << i;
+                            }
+                            /* See if we have all the key frames, then 
+                             * we start to send. This logic is not quite
+                             * right, but it works for the case of a 
+                             * single video stream with one or more
+                             * audio streams (for which every frame is 
+                             * typically a key frame). 
+                             */
+                            if (!c->stream->send_on_key || ((c->got_key_frame + 1) >> c->stream->nb_streams)) {
+                                goto send_it;
+                            }
                         }
                     }
                 } else {
-               send_it:
-                    if (av_write_packet(&c->fmt_ctx, &pkt))
-                       c->state = HTTPSTATE_SEND_DATA_TRAILER;
+                    AVCodecContext *codec;
+                send_it:
+                    /* Fudge here */
+                    codec = &c->fmt_ctx.streams[pkt.stream_index]->codec;
+
+                    codec->key_frame = ((pkt.flags & PKT_FLAG_KEY) != 0);
+
+#ifdef PJSG
+                    if (codec->codec_type == CODEC_TYPE_AUDIO) {
+                        codec->frame_size = (codec->sample_rate * pkt.duration + 500000) / 1000000;
+                        /* printf("Calculated size %d, from sr %d, duration %d\n", codec->frame_size, codec->sample_rate, pkt.duration); */
+                    }
+#endif
+
+                    if (av_write_packet(&c->fmt_ctx, &pkt, 0))
+                        c->state = HTTPSTATE_SEND_DATA_TRAILER;
+
+                    codec->frame_number++;
                 }
-                
+
                 av_free_packet(&pkt);
             }
         }
@@ -913,22 +1208,25 @@ static int http_send_data(HTTPContext *c)
         if (ret < 0)
             return -1;
         else if (ret == 0) {
-            break;
+            continue;
         } else {
             /* state change requested */
             return 0;
         }
     }
 
-    len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
-    if (len < 0) {
-        if (errno != EAGAIN && errno != EINTR) {
-            /* error : close connection */
-            return -1;
+    if (c->buffer_end > c->buffer_ptr) {
+        len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
+        if (len < 0) {
+            if (errno != EAGAIN && errno != EINTR) {
+                /* error : close connection */
+                return -1;
+            }
+        } else {
+            c->buffer_ptr += len;
+            c->data_count += len;
+            c->stream->bytes_served += len;
         }
-    } else {
-        c->buffer_ptr += len;
-        c->data_count += len;
     }
     return 0;
 }
@@ -959,14 +1257,31 @@ static int http_start_receive_data(HTTPContext *c)
     
 static int http_receive_data(HTTPContext *c)
 {
-    int len;
     HTTPContext *c1;
 
+    if (c->buffer_end > c->buffer_ptr) {
+        int len;
+
+        len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
+        if (len < 0) {
+            if (errno != EAGAIN && errno != EINTR) {
+                /* error : close connection */
+                goto fail;
+            }
+        } else if (len == 0) {
+            /* end of connection : close it */
+            goto fail;
+        } else {
+            c->buffer_ptr += len;
+            c->data_count += len;
+        }
+    }
+
     if (c->buffer_ptr >= c->buffer_end) {
+        FFStream *feed = c->stream;
         /* a packet has been received : write it in the store, except
            if header */
         if (c->data_count > FFM_PACKET_SIZE) {
-            FFStream *feed = c->stream;
             
             //            printf("writing pos=0x%Lx size=0x%Lx\n", feed->feed_write_index, feed->feed_size);
             /* XXX: use llseek or url_seek */
@@ -992,23 +1307,33 @@ static int http_receive_data(HTTPContext *c)
                     c1->state = HTTPSTATE_SEND_DATA;
                 }
             }
+        } else {
+            /* We have a header in our hands that contains useful data */
+            AVFormatContext s;
+            ByteIOContext *pb = &s.pb;
+            int i;
+
+            memset(&s, 0, sizeof(s));
+
+            url_open_buf(pb, c->buffer, c->buffer_end - c->buffer, URL_RDONLY);
+            pb->buf_end = c->buffer_end;        /* ?? */
+            pb->is_streamed = 1;
+
+            if (feed->fmt->read_header(&s, 0) < 0) {
+                goto fail;
+            }
+
+            /* Now we have the actual streams */
+            if (s.nb_streams != feed->nb_streams) {
+                goto fail;
+            }
+            for (i = 0; i < s.nb_streams; i++) {
+                memcpy(&feed->streams[i]->codec, &s.streams[i]->codec, sizeof(AVCodecContext));
+            } 
         }
         c->buffer_ptr = c->buffer;
     }
 
-    len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
-    if (len < 0) {
-        if (errno != EAGAIN && errno != EINTR) {
-            /* error : close connection */
-            goto fail;
-        }
-    } else if (len == 0) {
-        /* end of connection : close it */
-        goto fail;
-    } else {
-        c->buffer_ptr += len;
-        c->data_count += len;
-    }
     return 0;
  fail:
     c->stream->feed_opened = 0;
@@ -1028,7 +1353,8 @@ int add_av_stream(FFStream *feed,
     for(i=0;i<feed->nb_streams;i++) {
         st = feed->streams[i];
         av1 = &st->codec;
-        if (av1->codec == av->codec &&
+        if (av1->codec_id == av->codec_id &&
+            av1->codec_type == av->codec_type &&
             av1->bit_rate == av->bit_rate) {
 
             switch(av->codec_type) {
@@ -1044,6 +1370,8 @@ int add_av_stream(FFStream *feed,
                     av1->gop_size == av->gop_size)
                     goto found;
                 break;
+            default:
+                abort();
             }
         }
     }
@@ -1179,7 +1507,21 @@ void add_codec(FFStream *stream, AVCodecContext *av)
             av->width = 160;
             av->height = 128;
         }
+        /* Bitrate tolerance is less for streaming */
+        if (av->bit_rate_tolerance == 0)
+            av->bit_rate_tolerance = av->bit_rate / 4;
+        if (av->qmin == 0)
+            av->qmin = 3;
+        if (av->qmax == 0)
+            av->qmax = 31;
+        if (av->max_qdiff == 0)
+            av->max_qdiff = 3;
+        av->qcompress = 0.5;
+        av->qblur = 0.5;
+
         break;
+    default:
+        abort();
     }
 
     st = av_mallocz(sizeof(AVStream));
@@ -1189,6 +1531,40 @@ void add_codec(FFStream *stream, AVCodecContext *av)
     memcpy(&st->codec, av, sizeof(AVCodecContext));
 }
 
+int opt_audio_codec(const char *arg)
+{
+    AVCodec *p;
+
+    p = first_avcodec;
+    while (p) {
+        if (!strcmp(p->name, arg) && p->type == CODEC_TYPE_AUDIO)
+            break;
+        p = p->next;
+    }
+    if (p == NULL) {
+        return CODEC_ID_NONE;
+    }
+
+    return p->id;
+}
+
+int opt_video_codec(const char *arg)
+{
+    AVCodec *p;
+
+    p = first_avcodec;
+    while (p) {
+        if (!strcmp(p->name, arg) && p->type == CODEC_TYPE_VIDEO)
+            break;
+        p = p->next;
+    }
+    if (p == NULL) {
+        return CODEC_ID_NONE;
+    }
+
+    return p->id;
+}
+
 int parse_ffconfig(const char *filename)
 {
     FILE *f;
@@ -1250,6 +1626,16 @@ int parse_ffconfig(const char *filename)
             } else {
                 nb_max_connections = val;
             }
+        } else if (!strcasecmp(cmd, "MaxBandwidth")) {
+            get_arg(arg, sizeof(arg), &p);
+            val = atoi(arg);
+            if (val < 10 || val > 100000) {
+                fprintf(stderr, "%s:%d: Invalid MaxBandwidth: %s\n", 
+                        filename, line_num, arg);
+                errors++;
+            } else {
+                nb_max_bandwidth = val;
+            }
         } else if (!strcasecmp(cmd, "CustomLog")) {
             get_arg(logfilename, sizeof(logfilename), &p);
         } else if (!strcasecmp(cmd, "<Feed")) {
@@ -1312,6 +1698,14 @@ int parse_ffconfig(const char *filename)
                 fprintf(stderr, "%s:%d: No corresponding <Feed> for </Feed>\n",
                         filename, line_num);
                 errors++;
+            } else {
+                /* Make sure that we start out clean */
+                if (unlink(feed->feed_filename) < 0 
+                    && errno != ENOENT) {
+                    fprintf(stderr, "%s:%d: Unable to clean old feed file '%s': %s\n",
+                        filename, line_num, feed->feed_filename, strerror(errno));
+                    errors++;
+                }
             }
             feed = NULL;
         } else if (!strcasecmp(cmd, "<Stream")) {
@@ -1379,6 +1773,31 @@ int parse_ffconfig(const char *filename)
                 audio_id = stream->fmt->audio_codec;
                 video_id = stream->fmt->video_codec;
             }
+        } else if (!strcasecmp(cmd, "Preroll")) {
+            get_arg(arg, sizeof(arg), &p);
+            if (stream) {
+                stream->prebuffer = atoi(arg) * 1000;
+            }
+        } else if (!strcasecmp(cmd, "StartSendOnKey")) {
+            if (stream) {
+                stream->send_on_key = 1;
+            }
+        } else if (!strcasecmp(cmd, "AudioCodec")) {
+            get_arg(arg, sizeof(arg), &p);
+            audio_id = opt_audio_codec(arg);
+            if (audio_id == CODEC_ID_NONE) {
+                fprintf(stderr, "%s:%d: Unknown AudioCodec: %s\n", 
+                        filename, line_num, arg);
+                errors++;
+            }
+        } else if (!strcasecmp(cmd, "VideoCodec")) {
+            get_arg(arg, sizeof(arg), &p);
+            video_id = opt_video_codec(arg);
+            if (video_id == CODEC_ID_NONE) {
+                fprintf(stderr, "%s:%d: Unknown VideoCodec: %s\n", 
+                        filename, line_num, arg);
+                errors++;
+            }
         } else if (!strcasecmp(cmd, "AudioBitRate")) {
             get_arg(arg, sizeof(arg), &p);
             if (stream) {
@@ -1424,6 +1843,37 @@ int parse_ffconfig(const char *filename)
             if (stream) {
                 video_enc.gop_size = 1;
             }
+        } else if (!strcasecmp(cmd, "VideoHighQuality")) {
+            if (stream) {
+                video_enc.flags |= CODEC_FLAG_HQ;
+            }
+        } else if (!strcasecmp(cmd, "VideoQDiff")) {
+            if (stream) {
+                video_enc.max_qdiff = atoi(arg);
+                if (video_enc.max_qdiff < 1 || video_enc.max_qdiff > 31) {
+                    fprintf(stderr, "%s:%d: VideoQDiff out of range\n",
+                            filename, line_num);
+                    errors++;
+                }
+            }
+        } else if (!strcasecmp(cmd, "VideoQMax")) {
+            if (stream) {
+                video_enc.qmax = atoi(arg);
+                if (video_enc.qmax < 1 || video_enc.qmax > 31) {
+                    fprintf(stderr, "%s:%d: VideoQMax out of range\n",
+                            filename, line_num);
+                    errors++;
+                }
+            }
+        } else if (!strcasecmp(cmd, "VideoQMin")) {
+            if (stream) {
+                video_enc.qmin = atoi(arg);
+                if (video_enc.qmin < 1 || video_enc.qmin > 31) {
+                    fprintf(stderr, "%s:%d: VideoQMin out of range\n",
+                            filename, line_num);
+                    errors++;
+                }
+            }
         } else if (!strcasecmp(cmd, "NoVideo")) {
             video_id = CODEC_ID_NONE;
         } else if (!strcasecmp(cmd, "NoAudio")) {
@@ -1553,6 +2003,7 @@ int main(int argc, char **argv)
     my_addr.sin_port = htons (8080);
     my_addr.sin_addr.s_addr = htonl (INADDR_ANY);
     nb_max_connections = 5;
+    nb_max_bandwidth = 1000;
     first_stream = NULL;
     logfilename[0] = '\0';