Skip to content
Snippets Groups Projects
ffserver.c 66.3 KiB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
/*
 * Multiple format streaming server
Fabrice Bellard's avatar
Fabrice Bellard committed
 * Copyright (c) 2000, 2001, 2002 Gerard Lantau.
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.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>
Michael Niedermayer's avatar
Michael Niedermayer committed
#include <assert.h>
Fabrice Bellard's avatar
Fabrice Bellard committed

#include "bswap.h" // needed for the bitstream writer in common.h which is included in avformat.h
Fabrice Bellard's avatar
Fabrice Bellard committed
#include "avformat.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_MAX_SIZE 32768
#define PACKET_MAX_SIZE 16384
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;
    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];
    UINT8 buffer[IOBUFFER_MAX_SIZE];
    UINT8 pbuffer[PACKET_MAX_SIZE];
Fabrice Bellard's avatar
Fabrice Bellard committed
} HTTPContext;

/* each generated stream is described here */
enum StreamType {
    STREAM_TYPE_LIVE,
    STREAM_TYPE_STATUS,
};

/* 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 */
    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);

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

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");

    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;
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) {
                    close(fd);
                } 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++;
                }
            }
        }
        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;
}

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

    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(".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;
    }

    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) {
        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

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

    if (c->stream->stream_type == STREAM_TYPE_STATUS)
        goto send_stats;

    /* open input stream */
    if (open_input_stream(c, info) < 0) {
        sprintf(msg, "Input stream corresponding to '%s' not found", url);
        goto send_error;
    }

    /* prepare http header */
    q = c->buffer;
    q += sprintf(q, "HTTP/1.0 200 OK\r\n");
    mime_type = c->stream->fmt->mime_type;
    if (!mime_type)
        mime_type = "application/x-octet_stream";
    q += sprintf(q, "Pragma: no-cache\r\n");

    /* 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");
        /* mime_type = "application/octet-stream"; */
        /* video/x-ms-asf seems better -- netscape doesn't crash any more! */
        mime_type = "video/x-ms-asf";
Fabrice Bellard's avatar
Fabrice Bellard committed
    }
    q += sprintf(q, "Content-Type: %s\r\n", mime_type);
Fabrice Bellard's avatar
Fabrice Bellard committed
    q += sprintf(q, "\r\n");
    
    /* prepare output buffer */
    c->http_error = 0;
    c->buffer_ptr = c->buffer;
    c->buffer_end = q;
    c->state = HTTPSTATE_SEND_HEADER;
    return 0;
 send_error:
    c->http_error = 404;
    q = c->buffer;
    q += sprintf(q, "HTTP/1.0 404 Not Found\r\n");
    q += sprintf(q, "Content-type: %s\r\n", "text/html");
    q += sprintf(q, "\r\n");
    q += sprintf(q, "<HTML>\n");
    q += sprintf(q, "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n");
    q += sprintf(q, "<BODY>%s</BODY>\n", msg);
    q += sprintf(q, "</HTML>\n");

    /* prepare output buffer */
    c->buffer_ptr = c->buffer;
    c->buffer_end = q;
    c->state = HTTPSTATE_SEND_HEADER;
    return 0;
 send_stats:
    compute_stats(c);
    c->http_error = 200; /* horrible : we use this value to avoid
                            going to the send data state */
    c->state = HTTPSTATE_SEND_HEADER;
    return 0;
}

static void compute_stats(HTTPContext *c)
{
    HTTPContext *c1;
    FFStream *stream;
    char *q, *p;
    time_t ti;
    int i;

    q = c->buffer;
    q += sprintf(q, "HTTP/1.0 200 OK\r\n");
    q += sprintf(q, "Content-type: %s\r\n", "text/html");
    q += sprintf(q, "Pragma: no-cache\r\n");
    q += sprintf(q, "\r\n");
    
    q += sprintf(q, "<HEAD><TITLE>FFServer Status</TITLE></HEAD>\n<BODY>");
    q += sprintf(q, "<H1>FFServer Status</H1>\n");
    /* format status */
    q += sprintf(q, "<H2>Available Streams</H2>\n");
    q += sprintf(q, "<TABLE cellspacing=0 cellpadding=4>\n");
    q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>kbytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n");
Fabrice Bellard's avatar
Fabrice Bellard committed
    stream = first_stream;
    while (stream != NULL) {
        char sfilename[1024];
        char *eosf;

            pstrcpy(sfilename, sizeof(sfilename) - 1, stream->filename);
            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> ", 
                         sfilename, stream->filename);
            q += sprintf(q, "<td align=right> %d <td align=right> %lld",
                        stream->conns_served, stream->bytes_served / 1000);
            switch(stream->stream_type) {
            case STREAM_TYPE_LIVE:
                {
                    int audio_bit_rate = 0;
                    int video_bit_rate = 0;
                    char *audio_codec_name = "";
                    char *video_codec_name = "";
                    char *audio_codec_name_extra = "";
                    char *video_codec_name_extra = "";

                    for(i=0;i<stream->nb_streams;i++) {
                        AVStream *st = stream->streams[i];
                        AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
                        switch(st->codec.codec_type) {
                        case CODEC_TYPE_AUDIO:
                            audio_bit_rate += st->codec.bit_rate;
                            if (codec) {
                                if (*audio_codec_name)
                                    audio_codec_name_extra = "...";
                                audio_codec_name = codec->name;
                            }
                            break;
                        case CODEC_TYPE_VIDEO:
                            video_bit_rate += st->codec.bit_rate;
                            if (codec) {
                                if (*video_codec_name)
                                    video_codec_name_extra = "...";
                                video_codec_name = codec->name;
                            }
                            break;
                        default:
Fabrice Bellard's avatar
Fabrice Bellard committed
                    }
                    q += sprintf(q, "<TD align=center> %s <TD align=right> %d <TD align=right> %d <TD> %s %s <TD align=right> %d <TD> %s %s", 
                                 stream->fmt->name,
                                 (audio_bit_rate + video_bit_rate) / 1000,
                                 video_bit_rate / 1000, video_codec_name, video_codec_name_extra,
                                 audio_bit_rate / 1000, audio_codec_name, audio_codec_name_extra);
                    if (stream->feed) {
                        q += sprintf(q, "<TD>%s", stream->feed->filename);
                    } else {
                        q += sprintf(q, "<TD>%s", stream->feed_filename);
                    }
                    q += sprintf(q, "\n");
Fabrice Bellard's avatar
Fabrice Bellard committed
                }
                break;
            default:
                q += sprintf(q, "<TD align=center> - <TD align=right> - <TD align=right> - <td><td align=right> - <TD>\n");
                break;
Fabrice Bellard's avatar
Fabrice Bellard committed
            }
        }
        stream = stream->next;
    }
    q += sprintf(q, "</TABLE>\n");

    stream = first_stream;
    while (stream != NULL) {
        if (stream->feed == stream) {
            q += sprintf(q, "<h2>Feed %s</h2>", stream->filename);
            q += sprintf(q, "<table cellspacing=0 cellpadding=4><tr><th>Stream<th>type<th>kbits/s<th align=left>codec<th align=left>Parameters\n");

            for (i = 0; i < stream->nb_streams; i++) {
                AVStream *st = stream->streams[i];
                AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
                char *type = "unknown";

                switch(st->codec.codec_type) {
                case CODEC_TYPE_AUDIO:
                    type = "audio";
                    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);
                q += sprintf(q, "<tr><td align=right>%d<td>%s<td align=right>%d<td>%s<td>%s\n",
                        i, type, st->codec.bit_rate/1000, codec ? codec->name : "", parameters);
            }
            q += sprintf(q, "</table>\n");

        }       
        stream = stream->next;
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
    
#if 0
    {
        float avg;
        AVCodecContext *enc;
        char buf[1024];
        
        /* feed status */
        stream = first_feed;
        while (stream != NULL) {
            q += sprintf(q, "<H1>Feed '%s'</H1>\n", stream->filename);
            q += sprintf(q, "<TABLE>\n");
            q += sprintf(q, "<TR><TD>Parameters<TD>Frame count<TD>Size<TD>Avg bitrate (kbits/s)\n");
            for(i=0;i<stream->nb_streams;i++) {
                AVStream *st = stream->streams[i];
                FeedData *fdata = st->priv_data;
                enc = &st->codec;
            
                avcodec_string(buf, sizeof(buf), enc);
                avg = fdata->avg_frame_size * (float)enc->rate * 8.0;
                if (enc->codec->type == CODEC_TYPE_AUDIO && enc->frame_size > 0)
                    avg /= enc->frame_size;
                q += sprintf(q, "<TR><TD>%s <TD> %d <TD> %Ld <TD> %0.1f\n", 
                             buf, enc->frame_number, fdata->data_count, avg / 1000.0);
            }
            q += sprintf(q, "</TABLE>\n");
            stream = stream->next_feed;
        }
    }
#endif

    /* connection status */
    q += sprintf(q, "<H2>Connection Status</H2>\n");
Fabrice Bellard's avatar
Fabrice Bellard committed

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

Fabrice Bellard's avatar
Fabrice Bellard committed
    q += sprintf(q, "<TABLE>\n");
    q += sprintf(q, "<TR><TD>#<TD>File<TD>IP<TD>State<TD>Size\n");
    c1 = first_http_ctx;
    i = 0;
    while (c1 != NULL && q < (char *) c->buffer + sizeof(c->buffer) - 2048) {
Fabrice Bellard's avatar
Fabrice Bellard committed
        i++;
        p = inet_ntoa(c1->from_addr.sin_addr);
        q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <TD> %Ld\n", 
                     i, c1->stream->filename, 
                     c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "",
                     p, 
                     http_state[c1->state],
                     c1->data_count);
        c1 = c1->next;
    }
    q += sprintf(q, "</TABLE>\n");
    
    /* date */
    ti = time(NULL);
    p = ctime(&ti);
    q += sprintf(q, "<HR size=1 noshade>Generated at %s", p);
Fabrice Bellard's avatar
Fabrice Bellard committed
    q += sprintf(q, "</BODY>\n</HTML>\n");

    c->buffer_ptr = c->buffer;
    c->buffer_end = q;
}


static void http_write_packet(void *opaque, 
                              unsigned char *buf, int size)
{
    HTTPContext *c = opaque;

    if (c->buffer_ptr == c->buffer_end || !c->buffer_ptr)
        c->buffer_ptr = c->buffer_end = c->buffer;

    if (c->buffer_end - c->buffer + size > IOBUFFER_MAX_SIZE)

    memcpy(c->buffer_end, buf, size);
    c->buffer_end += size;
Fabrice Bellard's avatar
Fabrice Bellard committed
}

static int open_input_stream(HTTPContext *c, const char *info)
{
    char buf[128];
    char input_filename[1024];
    AVFormatContext *s;
    int buf_size;
    INT64 stream_pos;

    /* find file name */
    if (c->stream->feed) {
        strcpy(input_filename, c->stream->feed->feed_filename);
        buf_size = FFM_PACKET_SIZE;
        /* compute position (absolute time) */
        if (find_info_tag(buf, sizeof(buf), "date", info)) {
            stream_pos = parse_date(buf, 0);
        } else if (find_info_tag(buf, sizeof(buf), "buffer", info)) {
            int prebuffer = strtol(buf, 0, 10);
            stream_pos = gettime() - prebuffer * 1000000;
Fabrice Bellard's avatar
Fabrice Bellard committed
        } else {
            stream_pos = gettime() - c->stream->prebuffer * 1000;
Fabrice Bellard's avatar
Fabrice Bellard committed
        }
    } else {
        strcpy(input_filename, c->stream->feed_filename);
        buf_size = 0;
        /* compute position (relative time) */