From 3120d2a265553a87213eaf1bef1837af90f3fdc6 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 26 May 2002 03:36:34 +0000 Subject: [PATCH] * Add first cut of code to handle Windows Media Player rate switching requests. The current state is that at startup, WMP will get the best stream that it can handle. However, subsequent rate switching only puts a message in the log saying what the new stream ought to be. Solving this will be tricky. I guess that we would have to wait for key frames to appear in the new stream, and then switch over to it. Some care would be needed to deal with the PTS of the new stream versus the old stream. Originally committed as revision 602 to svn://svn.ffmpeg.org/ffmpeg/trunk --- ffserver.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 3 deletions(-) diff --git a/ffserver.c b/ffserver.c index 037c407e34..d1597a8ad8 100644 --- a/ffserver.c +++ b/ffserver.c @@ -92,6 +92,7 @@ typedef struct HTTPContext { int suppress_log; int bandwidth; time_t start_time; + int wmp_client_id; char protocol[16]; char method[16]; char url[128]; @@ -195,8 +196,9 @@ 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\n", - buf1, buf2, c->method, c->url, c->protocol, (c->http_error ? c->http_error : 200), c->data_count); + 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 : ""); } /* main loop of the http server */ @@ -446,6 +448,136 @@ 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, 0, 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 FFStream *find_optimal_stream(FFStream *req, char *rates) +{ + int i; + FFStream *rover; + int req_bitrate = 0; + int want_bitrate = 0; + FFStream *best = 0; + int best_bitrate; + + for (i = 0; i < req->nb_streams; i++) { + AVCodecContext *codec = &req->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) { + continue; + } + + /* Now see if the codecs all match */ + + for (i = 0; i < req->nb_streams; i++) { + AVCodecContext *codec = &req->streams[i]->codec; + AVCodecContext *rovercodec = &rover->streams[i]->codec; + + if (rovercodec->codec_id != codec->codec_id || + rovercodec->sample_rate != codec->sample_rate) { + /* Does the video width and height have to match?? */ + break; + } + } + + 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; + } + } + } + } + + return best; +} /* parse http request and prepare header */ static int http_parse_request(HTTPContext *c) @@ -462,6 +594,7 @@ static int http_parse_request(HTTPContext *c) const char *mime_type; FFStream *stream; int i; + char ratebuf[32]; p = c->buffer; q = cmd; @@ -545,6 +678,15 @@ static int http_parse_request(HTTPContext *c) goto send_error; } + /* If this is WMP, get the rate information */ + if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) { + FFStream *optimal; + + optimal = find_optimal_stream(stream, ratebuf); + if (optimal) + stream = optimal; + } + if (post == 0 && stream->stream_type == STREAM_TYPE_LIVE) { /* See if we meet the bandwidth requirements */ for(i=0;inb_streams;i++) { @@ -661,12 +803,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 +832,29 @@ static int http_parse_request(HTTPContext *c) c->suppress_log = 1; } } + +#ifdef DEBUG + fprintf(stderr, "\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) { + FFStream *optimal; + optimal = find_optimal_stream(wmpc->stream, ratebuf); + if (optimal) { + fprintf(stderr, "Would like to switch stream from %s to %s\n", + wmpc->stream->filename, optimal->filename); + } + } + } sprintf(msg, "POST command not handled"); goto send_error; @@ -699,6 +868,12 @@ static int http_parse_request(HTTPContext *c) return 0; } +#ifdef DEBUG + if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) { + fprintf(stderr, "\nGot request:\n%s\n", c->buffer); + } +#endif + if (c->stream->stream_type == STREAM_TYPE_STATUS) goto send_stats; @@ -718,7 +893,15 @@ 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"); + /* 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"; */ /* video/x-ms-asf seems better -- netscape doesn't crash any more! */ mime_type = "video/x-ms-asf";