Skip to content
Snippets Groups Projects
ffserver.c 81.2 KiB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
/*
 * Multiple format streaming server
 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * This library is distributed in the hope that it will be useful,
Fabrice Bellard's avatar
Fabrice Bellard committed
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Fabrice Bellard's avatar
Fabrice Bellard committed
 */
#define HAVE_AV_CONFIG_H
#include "avformat.h"

Fabrice Bellard's avatar
Fabrice Bellard committed
#include <stdarg.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <signal.h>

/* maximum number of simultaneous HTTP connections */
#define HTTP_MAX_CONNECTIONS 2000

enum HTTPState {
    HTTPSTATE_WAIT_REQUEST,
    HTTPSTATE_SEND_HEADER,
    HTTPSTATE_SEND_DATA_HEADER,
    HTTPSTATE_SEND_DATA,
    HTTPSTATE_SEND_DATA_TRAILER,
    HTTPSTATE_RECEIVE_DATA,
    HTTPSTATE_WAIT_FEED,
};

const char *http_state[] = {
    "WAIT_REQUEST",
    "SEND_HEADER",
    "SEND_DATA_HEADER",
    "SEND_DATA",
    "SEND_DATA_TRAILER",
    "RECEIVE_DATA",
    "WAIT_FEED",
};

#define IOBUFFER_INIT_SIZE 8192
#define PBUFFER_INIT_SIZE 8192
Fabrice Bellard's avatar
Fabrice Bellard committed

/* coef for exponential mean for bitrate estimation in statistics */
#define AVG_COEF 0.9

/* timeouts are in ms */
#define REQUEST_TIMEOUT (15 * 1000)
#define SYNC_TIMEOUT (10 * 1000)

/* context associated with one connection */
typedef struct HTTPContext {
    enum HTTPState state;
    int fd; /* socket file descriptor */
    struct sockaddr_in from_addr; /* origin */
    struct pollfd *poll_entry; /* used when polling */
    long timeout;
    UINT8 *buffer_ptr, *buffer_end;
    int http_error;
    struct HTTPContext *next;
    int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */
Fabrice Bellard's avatar
Fabrice Bellard committed
    INT64 data_count;
    /* feed input */
    int feed_fd;
    /* input format handling */
    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;
Fabrice Bellard's avatar
Fabrice Bellard committed
    AVFormatContext fmt_ctx;
    int last_packet_sent; /* true if last data packet was sent */
    int bandwidth;
    char protocol[16];
    char method[16];
    char url[128];
    int buffer_size;
    UINT8 *buffer;
    int pbuffer_size;
    UINT8 *pbuffer;
Fabrice Bellard's avatar
Fabrice Bellard committed
} HTTPContext;

/* each generated stream is described here */
enum StreamType {
    STREAM_TYPE_LIVE,
    STREAM_TYPE_STATUS,
    STREAM_TYPE_REDIRECT,
Fabrice Bellard's avatar
Fabrice Bellard committed
};

/* description of each stream of the ffserver.conf file */
typedef struct FFStream {
    enum StreamType stream_type;
    char filename[1024];     /* stream filename */
    struct FFStream *feed;
    AVOutputFormat *fmt;
Fabrice Bellard's avatar
Fabrice Bellard committed
    int nb_streams;
    int prebuffer;      /* Number of millseconds early to start */
Fabrice Bellard's avatar
Fabrice Bellard committed
    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
                                 input file name for a stream */
    pid_t pid;  /* Of ffmpeg process */
    char **child_argv;
Fabrice Bellard's avatar
Fabrice Bellard committed
    struct FFStream *next;
    /* feed specific */
    int feed_opened;     /* true if someone if writing to feed */
    int is_feed;         /* true if it is a feed */
Fabrice Bellard's avatar
Fabrice Bellard committed
    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 */
    struct FFStream *next_feed;
} FFStream;

typedef struct FeedData {
    long long data_count;
    float avg_frame_size;   /* frame size averraged over last frames with exponential mean */
} FeedData;

struct sockaddr_in my_addr;
char logfilename[1024];
HTTPContext *first_http_ctx;
FFStream *first_feed;   /* contains only feeds */
FFStream *first_stream; /* contains all streams, including feeds */

static int handle_http(HTTPContext *c, long cur_time);
static int http_parse_request(HTTPContext *c);
static int http_send_data(HTTPContext *c);
static void compute_stats(HTTPContext *c);
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;

Fabrice Bellard's avatar
Fabrice Bellard committed
int nb_max_connections;
int nb_connections;

int nb_max_bandwidth;
int nb_bandwidth;

Fabrice Bellard's avatar
Fabrice Bellard committed
static long gettime_ms(void)
{
    struct timeval tv;

    gettimeofday(&tv,NULL);
    return (long long)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
}

static FILE *logfile = NULL;

static void http_log(char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    
Fabrice Bellard's avatar
Fabrice Bellard committed
        vfprintf(logfile, fmt, ap);
Fabrice Bellard's avatar
Fabrice Bellard committed
    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);
}

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);
            }
        }
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
/* main loop of the http server */
static int http_server(struct sockaddr_in my_addr)
{
    int server_fd, tmp, ret;
    struct sockaddr_in from_addr;
    struct pollfd poll_table[HTTP_MAX_CONNECTIONS + 1], *poll_entry;
    HTTPContext *c, **cp;
    long cur_time;

    server_fd = socket(AF_INET,SOCK_STREAM,0);
    if (server_fd < 0) {
        perror ("socket");
        return -1;
    }
        
    tmp = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));

    if (bind (server_fd, (struct sockaddr *) &my_addr, sizeof (my_addr)) < 0) {
        perror ("bind");
        close(server_fd);
        return -1;
    }
  
    if (listen (server_fd, 5) < 0) {
        perror ("listen");
        close(server_fd);
        return -1;
    }

    http_log("ffserver started.\n");

    start_children(first_feed);

Fabrice Bellard's avatar
Fabrice Bellard committed
    fcntl(server_fd, F_SETFL, O_NONBLOCK);
    first_http_ctx = NULL;
    nb_connections = 0;
    first_http_ctx = NULL;
    for(;;) {
        poll_entry = poll_table;
        poll_entry->fd = server_fd;
        poll_entry->events = POLLIN;
        poll_entry++;

        /* wait for events on each HTTP handle */
        c = first_http_ctx;
        while (c != NULL) {
            int fd;
            fd = c->fd;
            switch(c->state) {
            case HTTPSTATE_WAIT_REQUEST:
                c->poll_entry = poll_entry;
                poll_entry->fd = fd;
                poll_entry->events = POLLIN;
                poll_entry++;
                break;
            case HTTPSTATE_SEND_HEADER:
            case HTTPSTATE_SEND_DATA_HEADER:
            case HTTPSTATE_SEND_DATA:
            case HTTPSTATE_SEND_DATA_TRAILER:
                c->poll_entry = poll_entry;
                poll_entry->fd = fd;
                poll_entry->events = POLLOUT;
                poll_entry++;
                break;
            case HTTPSTATE_RECEIVE_DATA:
                c->poll_entry = poll_entry;
                poll_entry->fd = fd;
                poll_entry->events = POLLIN;
                poll_entry++;
                break;
            case HTTPSTATE_WAIT_FEED:
                /* need to catch errors */
                c->poll_entry = poll_entry;
                poll_entry->fd = fd;
                poll_entry->events = POLLIN;/* Maybe this will work */
Fabrice Bellard's avatar
Fabrice Bellard committed
                poll_entry++;
                break;
            default:
                c->poll_entry = NULL;
                break;
            }
            c = c->next;
        }

        /* wait for an event on one connection. We poll at least every
           second to handle timeouts */
        do {
            ret = poll(poll_table, poll_entry - poll_table, 1000);
        } while (ret == -1);
        
        cur_time = gettime_ms();

        /* now handle the events */

        cp = &first_http_ctx;
        while ((*cp) != NULL) {
            c = *cp;
            if (handle_http (c, cur_time) < 0) {
                /* close and free the connection */
Fabrice Bellard's avatar
Fabrice Bellard committed
                close(c->fd);
                if (c->fmt_in)
                    av_close_input_file(c->fmt_in);
                *cp = c->next;
                nb_bandwidth -= c->bandwidth;
                av_free(c->buffer);
                av_free(c->pbuffer);
Fabrice Bellard's avatar
Fabrice Bellard committed
                av_free(c);
Fabrice Bellard's avatar
Fabrice Bellard committed
                nb_connections--;
            } else {
                cp = &c->next;
            }
        }

        /* new connection request ? */
        poll_entry = poll_table;
        if (poll_entry->revents & POLLIN) {
            int fd, len;

            len = sizeof(from_addr);
            fd = accept(server_fd, (struct sockaddr *)&from_addr, 
                        &len);
            if (fd >= 0) {
                fcntl(fd, F_SETFL, O_NONBLOCK);
                /* XXX: should output a warning page when coming
                   close to the connection limit */
                if (nb_connections >= nb_max_connections) {
                    c = NULL;
Fabrice Bellard's avatar
Fabrice Bellard committed
                } else {
                    /* add a new connection */
                    c = av_mallocz(sizeof(HTTPContext));
                    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);
Fabrice Bellard's avatar
Fabrice Bellard committed
                }
            }
        }
        poll_entry++;
    }
}

static int handle_http(HTTPContext *c, long cur_time)
{
    int len;
    
    switch(c->state) {
    case HTTPSTATE_WAIT_REQUEST:
        /* timeout ? */
        if ((c->timeout - cur_time) < 0)
            return -1;
        if (c->poll_entry->revents & (POLLERR | POLLHUP))
            return -1;

        /* no need to read if no events */
        if (!(c->poll_entry->revents & POLLIN))
            return 0;
        /* read the data */
        len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
        if (len < 0) {
            if (errno != EAGAIN && errno != EINTR)
                return -1;
        } else if (len == 0) {
            return -1;
        } else {
            /* search for end of request. XXX: not fully correct since garbage could come after the end */
            UINT8 *ptr;
            c->buffer_ptr += len;
            ptr = c->buffer_ptr;
            if ((ptr >= c->buffer + 2 && !memcmp(ptr-2, "\n\n", 2)) ||
                (ptr >= c->buffer + 4 && !memcmp(ptr-4, "\r\n\r\n", 4))) {
                /* request found : parse it and reply */
                if (http_parse_request(c) < 0)
                    return -1;
            } else if (ptr >= c->buffer_end) {
                /* request too long: cannot do anything */
                return -1;
            }
        }
        break;

    case HTTPSTATE_SEND_HEADER:
        if (c->poll_entry->revents & (POLLERR | POLLHUP))
            return -1;

        /* no need to read if no events */
        if (!(c->poll_entry->revents & POLLOUT))
            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;
            }
        } else {
            c->buffer_ptr += len;
            if (c->stream)
                c->stream->bytes_served += len;
Fabrice Bellard's avatar
Fabrice Bellard committed
            if (c->buffer_ptr >= c->buffer_end) {
                /* if error, exit */
                if (c->http_error)
                    return -1;
                /* all the buffer was send : synchronize to the incoming stream */
                c->state = HTTPSTATE_SEND_DATA_HEADER;
                c->buffer_ptr = c->buffer_end = c->buffer;
            }
        }
        break;

    case HTTPSTATE_SEND_DATA:
    case HTTPSTATE_SEND_DATA_HEADER:
    case HTTPSTATE_SEND_DATA_TRAILER:
        /* no need to read if no events */
        if (c->poll_entry->revents & (POLLERR | POLLHUP))
            return -1;
        
        if (!(c->poll_entry->revents & POLLOUT))
            return 0;
        if (http_send_data(c) < 0)
            return -1;
        break;
    case HTTPSTATE_RECEIVE_DATA:
        /* no need to read if no events */
        if (c->poll_entry->revents & (POLLERR | POLLHUP))
            return -1;
        if (!(c->poll_entry->revents & POLLIN))
            return 0;
        if (http_receive_data(c) < 0)
            return -1;
        break;
    case HTTPSTATE_WAIT_FEED:
        /* no need to read if no events */
        if (c->poll_entry->revents & (POLLIN | POLLERR | POLLHUP))
Fabrice Bellard's avatar
Fabrice Bellard committed
            return -1;

        /* nothing to do, we'll be waken up by incoming feed packets */
        break;
    default:
        return -1;
    }
    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, 0xff, 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 int find_stream_in_feed(FFStream *feed, AVCodecContext *codec, int bit_rate)
    int best_bitrate = 100000000;
    int best = -1;

    for (i = 0; i < feed->nb_streams; i++) {
        AVCodecContext *feed_codec = &feed->streams[i]->codec;

        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;
        }

        /* Potential stream */

        /* We want the fastest stream less than bit_rate, or the slowest 
         * faster than bit_rate
         */

        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;
            }
        } else {
            if (feed_codec->bit_rate < best_bitrate) {
                best_bitrate = feed_codec->bit_rate;
                best = i;
            }
        }
    }

    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];
                c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 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
        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;
Fabrice Bellard's avatar
Fabrice Bellard committed
/* parse http request and prepare header */
static int http_parse_request(HTTPContext *c)
{
    char *p;
    int post;
    int doing_asf_redirector;
    int doing_ram;
Fabrice Bellard's avatar
Fabrice Bellard committed
    char cmd[32];
    char info[1024], *filename;
    char url[1024], *q;
    char protocol[32];
    char msg[1024];
    const char *mime_type;
    FFStream *stream;
    char *useragent = 0;
Fabrice Bellard's avatar
Fabrice Bellard committed

    p = c->buffer;
    q = cmd;
    while (!isspace(*p) && *p != '\0') {
        if ((q - cmd) < sizeof(cmd) - 1)
            *q++ = *p;
        p++;
    }
    *q = '\0';
    pstrcpy(c->method, sizeof(c->method), cmd);
Fabrice Bellard's avatar
Fabrice Bellard committed
    if (!strcmp(cmd, "GET"))
        post = 0;
    else if (!strcmp(cmd, "POST"))
        post = 1;
    else
        return -1;

    while (isspace(*p)) p++;
    q = url;
    while (!isspace(*p) && *p != '\0') {
        if ((q - url) < sizeof(url) - 1)
            *q++ = *p;
        p++;
    }
    *q = '\0';

    pstrcpy(c->url, sizeof(c->url), url);
Fabrice Bellard's avatar
Fabrice Bellard committed
    while (isspace(*p)) p++;
    q = protocol;
    while (!isspace(*p) && *p != '\0') {
        if ((q - protocol) < sizeof(protocol) - 1)
            *q++ = *p;
        p++;
    }
    *q = '\0';
    if (strcmp(protocol, "HTTP/1.0") && strcmp(protocol, "HTTP/1.1"))
        return -1;
    pstrcpy(c->protocol, sizeof(c->protocol), protocol);
Fabrice Bellard's avatar
Fabrice Bellard committed
    
    /* find the filename and the optional info string in the request */
    p = url;
    if (*p == '/')
        p++;
    filename = p;
    p = strchr(p, '?');
    if (p) {
        pstrcpy(info, sizeof(info), p);
Fabrice Bellard's avatar
Fabrice Bellard committed
        *p = '\0';
    } else {
        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';
    } else {
        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)) {
        doing_ram = 1;
        strcpy(filename + strlen(filename)-2, "m");
    } else {
        doing_ram = 0;
    }

Fabrice Bellard's avatar
Fabrice Bellard committed
    stream = first_stream;
    while (stream != NULL) {
        if (!strcmp(stream->filename, filename))
            break;
        stream = stream->next;
    }
    if (stream == NULL) {
        sprintf(msg, "File '%s' not found", url);
        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, "<html><head><title>Moved</title></head><body>\r\n");
        q += sprintf(q, "You should be <a href=\"%s\">redirected</a>.\r\n", stream->feed_filename);
        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 this is WMP, get the rate information */
    if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) {
        if (modify_current_stream(c, ratebuf)) {
            for (i = 0; i < sizeof(c->feed_streams) / sizeof(c->feed_streams[0]); i++) {
                if (c->switch_feed_streams[i] >= 0)
                    do_switch_stream(c, i);
            }
        }
    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:
            }
        }
    }

    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 || doing_asf_redirector) {
        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 if (doing_asf_redirector) {
                        q += sprintf(q, "HTTP/1.0 200 ASF Redirect follows\r\n");
                        q += sprintf(q, "Content-type: video/x-ms-asf\r\n");
                        q += sprintf(q, "\r\n");
                        q += sprintf(q, "[Reference]\r\n");
                        q += sprintf(q, "Ref1=http://%s/%s%s\r\n", 
                                hostbuf, filename, info);

                    /* 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");
Fabrice Bellard's avatar
Fabrice Bellard committed
    }

Fabrice Bellard's avatar
Fabrice Bellard committed
    /* 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;
                }
                if (strncasecmp(p, "Pragma: client-id=", 18) == 0) {
                    client_id = strtol(p + 18, 0, 10);
                }
                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;
                }
            }
#ifdef DEBUG_WMP
            http_log("\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) {
                    if (modify_current_stream(wmpc, ratebuf)) {
                        wmpc->switch_pending = 1;
Fabrice Bellard's avatar
Fabrice Bellard committed
            sprintf(msg, "POST command not handled");
            goto send_error;
        }
        if (http_start_receive_data(c) < 0) {
            sprintf(msg, "could not open feed");
            goto send_error;
        }
        c->http_error = 0;
        c->state = HTTPSTATE_RECEIVE_DATA;
        return 0;
    }

#ifdef DEBUG_WMP
    if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) {
        http_log("\nGot request:\n%s\n", c->buffer);
Fabrice Bellard's avatar
Fabrice Bellard committed
    if (c->stream->stream_type == STREAM_TYPE_STATUS)