Skip to content
Snippets Groups Projects
ffserver.c 66.1 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 
Loading
Loading full blame...