From 42a63c6a02ec6abb8579a743e76025e7f6309033 Mon Sep 17 00:00:00 2001
From: Philip Gladstone <philipjsg@users.sourceforge.net>
Date: Thu, 16 May 2002 02:03:07 +0000
Subject: [PATCH] * Add code to configure the following:   *
 prebuffering/preroll a live stream -- this improves startup time   *
 videoqmin/videoqmax/videoqdiff -- codec parameters   * maximum bandwidth for
 live streams * Add support for .ram and .rpm extensions mapping onto .rm *
 Make the status page show bandwidth. Also make the .asf and .rm   links go to
 .asx and .ram files. * Make a stream only start streaming when it gets a
 keyframe on each   stream. This is arguable, and it maybe ought to be
 restricted to   live streams. However, since I don't think that file streams
 work,   this is a step in the right direction. It improves the startup delay.
 * Log an error if we are unable to delete the temp feed file.

Originally committed as revision 501 to svn://svn.ffmpeg.org/ffmpeg/trunk
---
 ffserver.c | 199 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 172 insertions(+), 27 deletions(-)

diff --git a/ffserver.c b/ffserver.c
index 5567fe11e7..f0fea5dd3b 100644
--- a/ffserver.c
+++ b/ffserver.c
@@ -83,7 +83,7 @@ typedef struct HTTPContext {
     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;
@@ -94,6 +94,7 @@ typedef struct HTTPContext {
     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];
@@ -114,6 +115,7 @@ typedef struct FFStream {
     struct FFStream *feed;
     AVFormat *fmt;
     int nb_streams;
+    int prebuffer;      /* Number of millseconds early to start */
     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
@@ -150,6 +152,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;
@@ -296,6 +301,7 @@ static int http_server(struct sockaddr_in my_addr)
                 if (c->fmt_in)
                     av_close_input_file(c->fmt_in);
                 *cp = c->next;
+                nb_bandwidth -= c->bandwidth;
                 free(c);
                 nb_connections--;
             } else {
@@ -443,6 +449,7 @@ 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;
@@ -450,6 +457,7 @@ static int http_parse_request(HTTPContext *c)
     char msg[1024];
     const char *mime_type;
     FFStream *stream;
+    int i;
 
     p = c->buffer;
     q = cmd;
@@ -513,6 +521,15 @@ static int http_parse_request(HTTPContext *c)
         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))
@@ -523,7 +540,47 @@ static int http_parse_request(HTTPContext *c)
         sprintf(msg, "File '%s' not found", url);
         goto send_error;
     }
-    if (doing_asx) {
+
+    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;
+    }
+    
+    if (doing_asx || doing_ram) {
         char *hostinfo = 0;
         
         for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
@@ -556,14 +613,24 @@ static int http_parse_request(HTTPContext *c)
 
                     c->http_error = 200;
                     q = c->buffer;
-                    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");
+                    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;
@@ -574,7 +641,7 @@ static int http_parse_request(HTTPContext *c)
             }
         }
 
-        sprintf(msg, "ASX file not handled");
+        sprintf(msg, "ASX/RAM file not handled");
         goto send_error;
     }
 
@@ -706,8 +773,21 @@ static void compute_stats(HTTPContext *c)
     q += sprintf(q, "<TR><TD>Path<TD>Format<TD>Bit rate (kbits/s)<TD>Video<TD>Audio<TD>Feed\n");
     stream = first_stream;
     while (stream != NULL) {
+        char sfilename[1024];
+        char *eosf;
+
+        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, "<TR><TD><A HREF=\"/%s\">%s</A> ", 
-                     stream->filename, stream->filename);
+                     sfilename, stream->filename);
         switch(stream->stream_type) {
         case STREAM_TYPE_LIVE:
             {
@@ -783,6 +863,9 @@ static void compute_stats(HTTPContext *c)
     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;
@@ -845,7 +928,7 @@ static int open_input_stream(HTTPContext *c, const char *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);
@@ -896,8 +979,8 @@ static int http_prepare_data(HTTPContext *c)
 
                 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;
@@ -909,8 +992,8 @@ 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->pbuffer, PACKET_MAX_SIZE,
                       1, c, NULL, http_write_packet, NULL);
@@ -928,9 +1011,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;
@@ -956,8 +1037,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);
                     }
@@ -1007,7 +1088,19 @@ 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->got_key_frame + 1) >> c->stream->nb_streams) {
+                                goto send_it;
+                            }
                         }
                     }
                 } else {
@@ -1030,7 +1123,7 @@ static int http_prepare_data(HTTPContext *c)
 
                     codec->frame_number++;
                 }
-                
+
                 av_free_packet(&pkt);
             }
         }
@@ -1353,12 +1446,16 @@ void add_codec(FFStream *stream, AVCodecContext *av)
             av->height = 128;
         }
         /* Bitrate tolerance is less for streaming */
-        av->bit_rate_tolerance = av->bit_rate / 4;
-        av->qmin = 3;
-        av->qmax = 31;
+        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;
-        av->max_qdiff = 3;
 
         break;
     default:
@@ -1467,6 +1564,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")) {
@@ -1531,7 +1638,12 @@ int parse_ffconfig(const char *filename)
                 errors++;
             } else {
                 /* Make sure that we start out clean */
-                unlink(feed->feed_filename);
+                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")) {
@@ -1599,6 +1711,11 @@ 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, "AudioCodec")) {
             get_arg(arg, sizeof(arg), &p);
             audio_id = opt_audio_codec(arg);
@@ -1664,6 +1781,33 @@ int parse_ffconfig(const char *filename)
             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")) {
@@ -1793,6 +1937,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';