diff --git a/ffserver.c b/ffserver.c index d1597a8ad8..45250c6303 100644 --- a/ffserver.c +++ b/ffserver.c @@ -59,8 +59,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 @@ -87,6 +87,10 @@ 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; @@ -96,14 +100,17 @@ typedef struct HTTPContext { 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 */ @@ -120,6 +127,8 @@ typedef struct FFStream { 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 */ + pid_t pid; /* Of ffmpeg process */ + char **child_argv; struct FFStream *next; /* feed specific */ int feed_opened; /* true if someone if writing to feed */ @@ -151,6 +160,8 @@ 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; + int nb_max_connections; int nb_connections; @@ -196,9 +207,52 @@ static void log_connection(HTTPContext *c) p = buf2 + strlen(p) - 1; if (*p == '\n') *p = '\0'; - http_log("%s - - [%s] \"%s %s %s\" %d %lld %s\n", - buf1, buf2, c->method, c->url, c->protocol, (c->http_error ? c->http_error : 200), c->data_count, - c->stream ? c->stream->filename : ""); + 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); +} + +static void start_children(FFStream *feed) +{ + for (; feed; feed = feed->next) { + if (feed->child_argv) { + 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 = 0; i < 10; i++) { + close(i); + } + + i = open("/dev/null", O_RDWR); + if (i) + dup2(i, 0); + dup2(i, 1); + dup2(i, 2); + + 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 */ @@ -233,6 +287,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; @@ -305,6 +361,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 { @@ -325,20 +383,33 @@ 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; + nb_connections++; + } + } + } + if (!c) { + close(fd); } } } @@ -465,7 +536,7 @@ static int extract_rates(char *rates, int ratelen, const char *request) q += 20; - memset(rates, 0, ratelen); + memset(rates, 0xff, ratelen); while (1) { while (*q && *q != '\n' && *q != ':') @@ -496,82 +567,37 @@ static int extract_rates(char *rates, int ratelen, const char *request) return 0; } -static FFStream *find_optimal_stream(FFStream *req, char *rates) +static int find_stream_in_feed(FFStream *feed, AVCodecContext *codec, int bit_rate) { int i; - FFStream *rover; - int req_bitrate = 0; - int want_bitrate = 0; - FFStream *best = 0; - int best_bitrate; + int best_bitrate = 100000000; + int best = -1; - for (i = 0; i < req->nb_streams; i++) { - AVCodecContext *codec = &req->streams[i]->codec; + for (i = 0; i < feed->nb_streams; i++) { + AVCodecContext *feed_codec = &feed->streams[i]->codec; - req_bitrate += codec->bit_rate; - - switch(rates[i]) { - case 0: - want_bitrate += codec->bit_rate; - break; - case 1: - want_bitrate += codec->bit_rate / 2; - break; - case 2: - break; - } - } - - best_bitrate = req_bitrate; - if (best_bitrate <= want_bitrate) - return 0; /* We are OK */ - - /* Now we have the actual rates that we can use. Now find the stream that uses most of it! */ - - for (rover = first_stream; rover; rover = rover->next) { - if (rover->feed != req->feed || - rover->fmt != req->fmt || - rover->nb_streams != req->nb_streams || - rover == req) { + 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; } - /* Now see if the codecs all match */ + /* Potential stream */ - for (i = 0; i < req->nb_streams; i++) { - AVCodecContext *codec = &req->streams[i]->codec; - AVCodecContext *rovercodec = &rover->streams[i]->codec; + /* We want the fastest stream less than bit_rate, or the slowest + * faster than bit_rate + */ - if (rovercodec->codec_id != codec->codec_id || - rovercodec->sample_rate != codec->sample_rate) { - /* Does the video width and height have to match?? */ - break; + 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; } - } - - if (i == req->nb_streams) { - /* The rovercodec is another possible stream */ - int rover_bitrate = 0; - - for (i = 0; i < req->nb_streams; i++) { - AVCodecContext *codec = &rover->streams[i]->codec; - - rover_bitrate += codec->bit_rate; - } - - /* We want to choose the largest rover_bitrate <= want_bitrate, or the smallest - * rover_bitrate if none <= want_bitrate - */ - if (rover_bitrate <= want_bitrate) { - if (best_bitrate > want_bitrate || rover_bitrate > best_bitrate) { - best_bitrate = rover_bitrate; - best = rover; - } - } else { - if (rover_bitrate < best_bitrate) { - best_bitrate = rover_bitrate; - best = rover; - } + } else { + if (feed_codec->bit_rate < best_bitrate) { + best_bitrate = feed_codec->bit_rate; + best = i; } } } @@ -579,12 +605,60 @@ static FFStream *find_optimal_stream(FFStream *req, char *rates) 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) { char *p; int post; int doing_asx; + int doing_asf_redirector; int doing_ram; char cmd[32]; char info[1024], *filename; @@ -595,6 +669,7 @@ static int http_parse_request(HTTPContext *c) FFStream *stream; int i; char ratebuf[32]; + char *useragent = 0; p = c->buffer; q = cmd; @@ -651,6 +726,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'; @@ -658,6 +747,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)) { @@ -678,13 +775,36 @@ 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, "
"); + } q += sprintf(q, "
Stream | type | kbits/s | codec | Parameters\n");
for (i = 0; i < stream->nb_streams; i++) {
@@ -1055,8 +1215,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();
@@ -1110,17 +1270,28 @@ static void compute_stats(HTTPContext *c)
nb_bandwidth, nb_max_bandwidth);
q += sprintf(q, "
|
---|