From 42a63c6a02ec6abb8579a743e76025e7f6309033 Mon Sep 17 00:00:00 2001 From: Philip Gladstone 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;inb_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, "Too busy\r\n"); + q += sprintf(q, "The server is too busy to serve your request at this time.

\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, "\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, "\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "\r\n", - hostbuf, filename, info); - q += sprintf(q, "\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, "\r\n"); + q += sprintf(q, "\r\n"); + q += sprintf(q, "\r\n", + hostbuf, filename, info); + q += sprintf(q, "\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, "PathFormatBit rate (kbits/s)VideoAudioFeed\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, "%s ", - 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
\n", nb_connections, nb_max_connections); + q += sprintf(q, "Bandwidth in use: %dk / %dk
\n", + nb_bandwidth, nb_max_bandwidth); + q += sprintf(q, "\n"); q += sprintf(q, "
#FileIPStateSize\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;ifmt_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;istream->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_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, "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';