Skip to content
Snippets Groups Projects
ffserver.c 158 KiB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
/*
 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg 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.1 of the License, or (at your option) any later version.
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * FFmpeg 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 FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Fabrice Bellard's avatar
Fabrice Bellard committed
 */
/**
 * @file
 * multiple format streaming server based on the FFmpeg libraries
 */

#include "config.h"
#if !HAVE_CLOSESOCKET
#define closesocket close
#endif
#include <string.h>
#include <stdlib.h>
#include "libavformat/avformat.h"
// FIXME those are internal headers, ffserver _really_ shouldn't use them
#include "libavformat/ffm.h"
#include "libavformat/network.h"
#include "libavformat/os_support.h"
#include "libavformat/rtpdec.h"
#include "libavformat/rtsp.h"
#include "libavformat/rtspcodes.h"
#include "libavformat/avio_internal.h"
#include "libavformat/internal.h"
#include "libavformat/url.h"

#include "libavutil/avassert.h"
#include "libavutil/dict.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/random_seed.h"
#include "libavutil/parseutils.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"

Fabrice Bellard's avatar
Fabrice Bellard committed
#include <stdarg.h>
Fabrice Bellard's avatar
Fabrice Bellard committed
#include <unistd.h>
Fabrice Bellard's avatar
Fabrice Bellard committed
#include <fcntl.h>
#include <sys/ioctl.h>
#include <poll.h>
Fabrice Bellard's avatar
Fabrice Bellard committed
#include <errno.h>
#include <time.h>
#include <sys/wait.h>
Fabrice Bellard's avatar
Fabrice Bellard committed
#include <signal.h>
#include "cmdutils.h"
Fabrice Bellard's avatar
Fabrice Bellard committed

const char program_name[] = "ffserver";
const int program_birth_year = 2000;
static const OptionDef options[];

Fabrice Bellard's avatar
Fabrice Bellard committed
enum HTTPState {
    HTTPSTATE_WAIT_REQUEST,
    HTTPSTATE_SEND_HEADER,
    HTTPSTATE_SEND_DATA_HEADER,
    HTTPSTATE_SEND_DATA,          /* sending TCP or UDP data */
Fabrice Bellard's avatar
Fabrice Bellard committed
    HTTPSTATE_SEND_DATA_TRAILER,
    HTTPSTATE_RECEIVE_DATA,
    HTTPSTATE_WAIT_FEED,          /* wait for data from the feed */
    HTTPSTATE_READY,

    RTSPSTATE_WAIT_REQUEST,
    RTSPSTATE_SEND_REPLY,
    RTSPSTATE_SEND_PACKET,
Fabrice Bellard's avatar
Fabrice Bellard committed
};

Baptiste Coudurier's avatar
Baptiste Coudurier committed
static const char *http_state[] = {
Fabrice Bellard's avatar
Fabrice Bellard committed
    "SEND_DATA_HEADER",
    "SEND_DATA",
    "SEND_DATA_TRAILER",
    "RECEIVE_DATA",
    "WAIT_FEED",
    "RTSP_SEND_PACKET",
Fabrice Bellard's avatar
Fabrice Bellard committed
};

#define MAX_STREAMS 20

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

/* timeouts are in ms */
#define HTTP_REQUEST_TIMEOUT (15 * 1000)
#define RTSP_REQUEST_TIMEOUT (3600 * 24 * 1000)

Fabrice Bellard's avatar
Fabrice Bellard committed
#define SYNC_TIMEOUT (10 * 1000)

typedef struct RTSPActionServerSetup {
    uint32_t ipaddr;
    char transport_option[512];
} RTSPActionServerSetup;

    int64_t count1, count2;
    int64_t time1, time2;
Fabrice Bellard's avatar
Fabrice Bellard committed
/* 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 */
    uint8_t *buffer_ptr, *buffer_end;
Fabrice Bellard's avatar
Fabrice Bellard committed
    int http_error;
    int chunked_encoding;
    int chunk_size;               /* 0 if it needs to be read */
Fabrice Bellard's avatar
Fabrice Bellard committed
    struct HTTPContext *next;
    int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */
    int64_t data_count;
Fabrice Bellard's avatar
Fabrice Bellard committed
    /* feed input */
    int feed_fd;
    /* input format handling */
    AVFormatContext *fmt_in;
    int64_t start_time;            /* In milliseconds - this wraps fairly often */
    int64_t first_pts;            /* initial pts value */
    int64_t cur_pts;             /* current pts value from the stream in us */
    int64_t cur_frame_duration;  /* duration of the current frame in us */
    int cur_frame_bytes;       /* output frame size, needed to compute
                                  the time at which we send each
                                  packet */
    int pts_stream_index;        /* stream we choose as clock reference */
    int64_t cur_clock;           /* current clock reference value in us */
Fabrice Bellard's avatar
Fabrice Bellard committed
    /* 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; /* instance of FFStream for one user */
Fabrice Bellard's avatar
Fabrice Bellard committed
    int last_packet_sent; /* true if last data packet was sent */
    DataRateData datarate;
    char protocol[16];
    char method[16];
    char url[128];
    int buffer_size;
    uint8_t *buffer;
    int is_packetized; /* if true, the stream is packetized */
    int packet_stream_index; /* current stream for output in state machine */
    uint8_t *pb_buffer; /* XXX: use that in all the code */
    AVIOContext *pb;
    char session_id[32]; /* session id */
    AVFormatContext *rtp_ctx[MAX_STREAMS];
    /* RTP/UDP specific */
    URLContext *rtp_handles[MAX_STREAMS];

    /* RTP/TCP specific */
    struct HTTPContext *rtsp_c;
    uint8_t *packet_buffer, *packet_buffer_ptr, *packet_buffer_end;
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
};

enum IPAddressAction {
    IP_ALLOW = 1,
    IP_DENY,
};

typedef struct IPAddressACL {
    struct IPAddressACL *next;
    enum IPAddressAction action;
    struct in_addr first;
    struct in_addr last;
} IPAddressACL;

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;   /* feed we are using (can be null if
                                coming from file) */
    AVDictionary *in_opts;   /* input parameters */
    AVDictionary *metadata;  /* metadata to set on the stream */
    AVInputFormat *ifmt;       /* if non NULL, force input format */
    AVOutputFormat *fmt;
    char dynamic_acl[1024];
Fabrice Bellard's avatar
Fabrice Bellard committed
    int nb_streams;
    int prebuffer;      /* Number of milliseconds early to start */
    int64_t max_time;      /* Number of milliseconds to run */
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 */
    time_t pid_start;  /* Of ffmpeg process */
    char **child_argv;
Fabrice Bellard's avatar
Fabrice Bellard committed
    struct FFStream *next;
    unsigned bandwidth; /* bandwidth, in kbits/s */
    /* multicast specific */
    int is_multicast;
    struct in_addr multicast_ip;
    int multicast_port; /* first port used for multicast */
    int multicast_ttl;
    int loop; /* if true, send the stream in loops (only meaningful if file) */
Fabrice Bellard's avatar
Fabrice Bellard committed
    /* feed specific */
    int feed_opened;     /* true if someone is writing to the feed */
Fabrice Bellard's avatar
Fabrice Bellard committed
    int is_feed;         /* true if it is a feed */
    int readonly;        /* True if writing is prohibited to the file */
    int truncate;        /* True if feeder connection truncate the feed file */
    int64_t bytes_served;
    int64_t feed_max_size;      /* maximum storage size, zero means unlimited */
Diego Biurrun's avatar
Diego Biurrun committed
    int64_t feed_write_index;   /* current write position in feed (it wraps around) */
    int64_t feed_size;          /* current size of feed */
Fabrice Bellard's avatar
Fabrice Bellard committed
    struct FFStream *next_feed;
} FFStream;

typedef struct FeedData {
    long long data_count;
Diego Biurrun's avatar
Diego Biurrun committed
    float avg_frame_size;   /* frame size averaged over last frames with exponential mean */
Fabrice Bellard's avatar
Fabrice Bellard committed
} FeedData;

static struct sockaddr_in my_http_addr;
static struct sockaddr_in my_rtsp_addr;
static char logfilename[1024];
static HTTPContext *first_http_ctx;
static FFStream *first_feed;   /* contains only feeds */
static FFStream *first_stream; /* contains all streams, including feeds */
Fabrice Bellard's avatar
Fabrice Bellard committed

static void new_connection(int server_fd, int is_rtsp);
static void close_connection(HTTPContext *c);

/* HTTP handling */
static int handle_connection(HTTPContext *c);
Fabrice Bellard's avatar
Fabrice Bellard committed
static int http_parse_request(HTTPContext *c);
static int http_send_data(HTTPContext *c);
static void compute_status(HTTPContext *c);
Fabrice Bellard's avatar
Fabrice Bellard committed
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);

/* RTSP handling */
static int rtsp_parse_request(HTTPContext *c);
static void rtsp_cmd_describe(HTTPContext *c, const char *url);
static void rtsp_cmd_options(HTTPContext *c, const char *url);
static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPMessageHeader *h);
static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPMessageHeader *h);
static void rtsp_cmd_interrupt(HTTPContext *c, const char *url, RTSPMessageHeader *h, int pause_only);
static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer,
static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr,
                                       FFStream *stream, const char *session_id,
static int rtp_new_av_stream(HTTPContext *c,
                             int stream_index, struct sockaddr_in *dest_addr,
                             HTTPContext *rtsp_c);
Fabrice Bellard's avatar
Fabrice Bellard committed

static const char *my_program_name;

static const char *config_filename;
static int ffserver_debug;
static int no_launch;
static int need_to_start_children;
/* maximum number of simultaneous HTTP connections */
static unsigned int nb_max_http_connections = 2000;
static unsigned int nb_max_connections = 5;
static unsigned int nb_connections;
Fabrice Bellard's avatar
Fabrice Bellard committed

static uint64_t max_bandwidth = 1000;
static uint64_t current_bandwidth;
static int64_t cur_time;           // Making this global saves on passing it around everywhere
Fabrice Bellard's avatar
Fabrice Bellard committed
static FILE *logfile = NULL;

static void htmlstrip(char *s) {
    while (s && *s) {
        s += strspn(s, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,. ");
        if (*s)
            *s++ = '?';
    }
}

static int64_t ffm_read_write_index(int fd)
{
    uint8_t buf[8];

    if (lseek(fd, 8, SEEK_SET) < 0)
        return AVERROR(EIO);
    if (read(fd, buf, 8) != 8)
        return AVERROR(EIO);
    return AV_RB64(buf);
}

static int ffm_write_write_index(int fd, int64_t pos)
{
    uint8_t buf[8];
    int i;

    for(i=0;i<8;i++)
        buf[i] = (pos >> (56 - i * 8)) & 0xff;
    if (lseek(fd, 8, SEEK_SET) < 0)
        return AVERROR(EIO);
    if (write(fd, buf, 8) != 8)
        return AVERROR(EIO);
    return 8;
}

static void ffm_set_write_index(AVFormatContext *s, int64_t pos,
                                int64_t file_size)
{
    FFMContext *ffm = s->priv_data;
    ffm->write_index = pos;
    ffm->file_size = file_size;
}

/* FIXME: make ffserver work with IPv6 */
/* resolve host with also IP address parsing */
static int resolve_host(struct in_addr *sin_addr, const char *hostname)
{

    if (!ff_inet_aton(hostname, sin_addr)) {
#if HAVE_GETADDRINFO
        struct addrinfo *ai, *cur;
        struct addrinfo hints = { 0 };
        hints.ai_family = AF_INET;
        if (getaddrinfo(hostname, NULL, &hints, &ai))
            return -1;
        /* getaddrinfo returns a linked list of addrinfo structs.
         * Even if we set ai_family = AF_INET above, make sure
         * that the returned one actually is of the correct type. */
        for (cur = ai; cur; cur = cur->ai_next) {
            if (cur->ai_family == AF_INET) {
                *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
                freeaddrinfo(ai);
                return 0;
            }
        }
        freeaddrinfo(ai);
        return -1;
#else
        struct hostent *hp;
        hp = gethostbyname(hostname);
        if (!hp)
            return -1;
        memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr));
#endif
    }
    return 0;
}

static char *ctime1(char *buf2, int buf_size)
Baptiste Coudurier's avatar
Baptiste Coudurier committed
{
    time_t ti;
    char *p;

    ti = time(NULL);
    p = ctime(&ti);
    av_strlcpy(buf2, p, buf_size);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
    p = buf2 + strlen(p) - 1;
    if (*p == '\n')
        *p = '\0';
    return buf2;
}

static void http_vlog(const char *fmt, va_list vargs)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    static int print_prefix = 1;
        if (print_prefix) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            char buf[32];
            ctime1(buf, sizeof(buf));
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            fprintf(logfile, "%s ", buf);
        }
        print_prefix = strstr(fmt, "\n") != NULL;
        vfprintf(logfile, fmt, vargs);
#ifdef __GNUC__
__attribute__ ((format (printf, 1, 2)))
#endif
static void http_log(const char *fmt, ...)
{
    va_list vargs;
    va_start(vargs, fmt);
    http_vlog(fmt, vargs);
    va_end(vargs);
}

static void http_av_log(void *ptr, int level, const char *fmt, va_list vargs)
{
    static int print_prefix = 1;
    AVClass *avc = ptr ? *(AVClass**)ptr : NULL;
    if (level > av_log_get_level())
        return;
    if (print_prefix && avc)
        http_log("[%s @ %p]", avc->item_name(ptr), ptr);
    print_prefix = strstr(fmt, "\n") != NULL;
    http_vlog(fmt, vargs);
Fabrice Bellard's avatar
Fabrice Bellard committed
}

static void log_connection(HTTPContext *c)
{
    if (c->suppress_log)
    http_log("%s - - [%s] \"%s %s\" %d %"PRId64"\n",
             inet_ntoa(c->from_addr.sin_addr), c->method, c->url,
             c->protocol, (c->http_error ? c->http_error : 200), c->data_count);
static void update_datarate(DataRateData *drd, int64_t count)
{
    if (!drd->time1 && !drd->count1) {
        drd->time1 = drd->time2 = cur_time;
        drd->count1 = drd->count2 = count;
    } else if (cur_time - drd->time2 > 5000) {
Alex Beregszaszi's avatar
Alex Beregszaszi committed
        drd->time1 = drd->time2;
        drd->count1 = drd->count2;
        drd->time2 = cur_time;
        drd->count2 = count;
static int compute_datarate(DataRateData *drd, int64_t count)
{
    if (cur_time == drd->time1)
        return 0;
    return ((count - drd->count1) * 1000) / (cur_time - drd->time1);
}

static void start_children(FFStream *feed)
{
    for (; feed; feed = feed->next) {
        if (feed->child_argv && !feed->pid) {
            feed->pid_start = time(0);

            feed->pid = fork();

            if (feed->pid < 0) {
                http_log("Unable to create children\n");
                exit(1);
            }
            if (!feed->pid) {
                /* In child */
                char pathname[1024];
                char *slash;
                int i;

                /* replace "ffserver" with "ffmpeg" in the path of current
                 * program. Ignore user provided path */
                av_strlcpy(pathname, my_program_name, sizeof(pathname));
                slash = strrchr(pathname, '/');
                if (!slash)
                    slash = pathname;
                else
                    slash++;
                strcpy(slash, "ffmpeg");

                http_log("Launch command line: ");
Stefano Sabatini's avatar
Stefano Sabatini committed
                http_log("%s ", pathname);
                for (i = 1; feed->child_argv[i] && feed->child_argv[i][0]; i++)
                    http_log("%s ", feed->child_argv[i]);
                http_log("\n");
                for (i = 3; i < 256; i++)
                if (!ffserver_debug) {
                    if (!freopen("/dev/null", "r", stdin))
                        http_log("failed to redirect STDIN to /dev/null\n;");
                    if (!freopen("/dev/null", "w", stdout))
                        http_log("failed to redirect STDOUT to /dev/null\n;");
                    if (!freopen("/dev/null", "w", stderr))
                        http_log("failed to redirect STDERR to /dev/null\n;");
                execvp(pathname, feed->child_argv);

                _exit(1);
            }
        }
    }
/* open a listening socket */
static int socket_open_listen(struct sockaddr_in *my_addr)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
Fabrice Bellard's avatar
Fabrice Bellard committed

    server_fd = socket(AF_INET,SOCK_STREAM,0);
    if (server_fd < 0) {
        perror ("socket");
        return -1;
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
    tmp = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)))
        av_log(NULL, AV_LOG_WARNING, "setsockopt SO_REUSEADDR failed\n");
Fabrice Bellard's avatar
Fabrice Bellard committed

    if (bind (server_fd, (struct sockaddr *) my_addr, sizeof (*my_addr)) < 0) {
        char bindmsg[32];
        snprintf(bindmsg, sizeof(bindmsg), "bind(port %d)", ntohs(my_addr->sin_port));
        perror (bindmsg);
Fabrice Bellard's avatar
Fabrice Bellard committed
        return -1;
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
    if (listen (server_fd, 5) < 0) {
        perror ("listen");
Fabrice Bellard's avatar
Fabrice Bellard committed
        return -1;
    }

    if (ff_socket_nonblock(server_fd, 1) < 0)
        av_log(NULL, AV_LOG_WARNING, "ff_socket_nonblock failed\n");
/* start all multicast streams */
static void start_multicast(void)
{
    FFStream *stream;
    char session_id[32];
    HTTPContext *rtp_c;
    struct sockaddr_in dest_addr = {0};
    int default_port, stream_index;

    default_port = 6000;
    for(stream = first_stream; stream != NULL; stream = stream->next) {
        if (stream->is_multicast) {
            unsigned random0 = av_lfg_get(&random_state);
            unsigned random1 = av_lfg_get(&random_state);
            snprintf(session_id, sizeof(session_id), "%08x%08x",
                     random0, random1);

            /* choose a port if none given */
            if (stream->multicast_port == 0) {
                stream->multicast_port = default_port;
                default_port += 100;
            }

            dest_addr.sin_family = AF_INET;
            dest_addr.sin_addr = stream->multicast_ip;
            dest_addr.sin_port = htons(stream->multicast_port);

            rtp_c = rtp_new_connection(&dest_addr, stream, session_id,
            if (open_input_stream(rtp_c, "") < 0) {
                http_log("Could not open input stream for stream '%s'\n",
                         stream->filename);
            for(stream_index = 0; stream_index < stream->nb_streams;
                dest_addr.sin_port = htons(stream->multicast_port +
                if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, NULL) < 0) {
                    http_log("Could not open output stream '%s/streamid=%d'\n",
                             stream->filename, stream_index);
/* main loop of the HTTP server */
    int server_fd = 0, rtsp_server_fd = 0;
    struct pollfd *poll_table, *poll_entry;
    if(!(poll_table = av_mallocz_array(nb_max_http_connections + 2, sizeof(*poll_table)))) {
        http_log("Impossible to allocate a poll table handling %d connections.\n", nb_max_http_connections);
        return -1;
    }

    if (my_http_addr.sin_port) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
        server_fd = socket_open_listen(&my_http_addr);
        if (server_fd < 0) {
            av_free(poll_table);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            return -1;
Fabrice Bellard's avatar
Fabrice Bellard committed

    if (my_rtsp_addr.sin_port) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
        rtsp_server_fd = socket_open_listen(&my_rtsp_addr);
        if (rtsp_server_fd < 0) {
            av_free(poll_table);
            closesocket(server_fd);
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            return -1;
    }

    if (!rtsp_server_fd && !server_fd) {
        http_log("HTTP and RTSP disabled.\n");
        return -1;
    }
    http_log("FFserver started.\n");
Fabrice Bellard's avatar
Fabrice Bellard committed

    start_children(first_feed);

Fabrice Bellard's avatar
Fabrice Bellard committed
    for(;;) {
        poll_entry = poll_table;
        if (server_fd) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            poll_entry->fd = server_fd;
            poll_entry->events = POLLIN;
            poll_entry++;
        }
        if (rtsp_server_fd) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            poll_entry->fd = rtsp_server_fd;
            poll_entry->events = POLLIN;
            poll_entry++;
Fabrice Bellard's avatar
Fabrice Bellard committed
        /* wait for events on each HTTP handle */
        c = first_http_ctx;
Fabrice Bellard's avatar
Fabrice Bellard committed
        while (c != NULL) {
            int fd;
            fd = c->fd;
            switch(c->state) {
            case HTTPSTATE_SEND_HEADER:
            case RTSPSTATE_SEND_REPLY:
            case RTSPSTATE_SEND_PACKET:
Fabrice Bellard's avatar
Fabrice Bellard committed
                c->poll_entry = poll_entry;
                poll_entry->fd = fd;
Fabrice Bellard's avatar
Fabrice Bellard committed
                poll_entry++;
                break;
            case HTTPSTATE_SEND_DATA_HEADER:
            case HTTPSTATE_SEND_DATA:
            case HTTPSTATE_SEND_DATA_TRAILER:
                    /* for TCP, we output as much as we can
                     * (may need to put a limit) */
                    c->poll_entry = poll_entry;
                    poll_entry->fd = fd;
                    poll_entry->events = POLLOUT;
                    poll_entry++;
                } else {
                    /* when ffserver is doing the timing, we work by
                       looking at which packet needs to be sent every
                    /* one tick wait XXX: 10 ms assumed */
                    if (delay > 10)
                        delay = 10;
Fabrice Bellard's avatar
Fabrice Bellard committed
                break;
Fabrice Bellard's avatar
Fabrice Bellard committed
            case HTTPSTATE_RECEIVE_DATA:
            case HTTPSTATE_WAIT_FEED:
Fabrice Bellard's avatar
Fabrice Bellard committed
                /* 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, delay);
            if (ret < 0 && ff_neterrno() != AVERROR(EAGAIN) &&
                ff_neterrno() != AVERROR(EINTR)) {
                av_free(poll_table);
        cur_time = av_gettime() / 1000;
Fabrice Bellard's avatar
Fabrice Bellard committed

        if (need_to_start_children) {
            need_to_start_children = 0;
            start_children(first_feed);
        }

Fabrice Bellard's avatar
Fabrice Bellard committed
        /* now handle the events */
        for(c = first_http_ctx; c != NULL; c = c_next) {
            c_next = c->next;
            if (handle_connection(c) < 0) {
                /* close and free the connection */
Fabrice Bellard's avatar
Fabrice Bellard committed
            }
        }

        poll_entry = poll_table;
        if (server_fd) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            /* new HTTP connection request ? */
            if (poll_entry->revents & POLLIN)
                new_connection(server_fd, 0);
            poll_entry++;
        }
        if (rtsp_server_fd) {
Baptiste Coudurier's avatar
Baptiste Coudurier committed
            /* new RTSP connection request ? */
            if (poll_entry->revents & POLLIN)
                new_connection(rtsp_server_fd, 1);
Fabrice Bellard's avatar
Fabrice Bellard committed
    }
}

/* start waiting for a new HTTP/RTSP request */
static void start_wait_request(HTTPContext *c, int is_rtsp)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    c->buffer_ptr = c->buffer;
    c->buffer_end = c->buffer + c->buffer_size - 1; /* leave room for '\0' */

    if (is_rtsp) {
        c->timeout = cur_time + RTSP_REQUEST_TIMEOUT;
        c->state = RTSPSTATE_WAIT_REQUEST;
    } else {
        c->timeout = cur_time + HTTP_REQUEST_TIMEOUT;
        c->state = HTTPSTATE_WAIT_REQUEST;
    }
}

static void http_send_too_busy_reply(int fd)
{
    int len = snprintf(buffer, sizeof(buffer),
                       "HTTP/1.0 503 Server too busy\r\n"
                       "Content-type: text/html\r\n"
                       "\r\n"
                       "<html><head><title>Too busy</title></head><body>\r\n"
                       "<p>The server is too busy to serve your request at this time.</p>\r\n"
                       "<p>The number of current connections is %u, and this exceeds the limit of %u.</p>\r\n"
                       "</body></html>\r\n",
                       nb_connections, nb_max_connections);
    av_assert0(len < sizeof(buffer));
    if (send(fd, buffer, len, 0) < len)
        av_log(NULL, AV_LOG_WARNING, "Could not send too-busy reply, send() failed\n");
static void new_connection(int server_fd, int is_rtsp)
{
    struct sockaddr_in from_addr;
    socklen_t len;
    int fd;
    fd = accept(server_fd, (struct sockaddr *)&from_addr,
Baptiste Coudurier's avatar
Baptiste Coudurier committed
    if (fd < 0) {
        http_log("error during accept %s\n", strerror(errno));
Baptiste Coudurier's avatar
Baptiste Coudurier committed
    }
    if (ff_socket_nonblock(fd, 1) < 0)
        av_log(NULL, AV_LOG_WARNING, "ff_socket_nonblock failed\n");
    if (nb_connections >= nb_max_connections) {
        http_send_too_busy_reply(fd);
    /* add a new connection */
    c = av_mallocz(sizeof(HTTPContext));
    if (!c)
        goto fail;
    c->fd = fd;
    c->poll_entry = NULL;
    c->from_addr = from_addr;
    c->buffer_size = IOBUFFER_INIT_SIZE;
    c->buffer = av_malloc(c->buffer_size);
    if (!c->buffer)
        goto fail;

    c->next = first_http_ctx;
    first_http_ctx = c;
    start_wait_request(c, is_rtsp);

    return;

 fail:
    if (c) {
        av_free(c->buffer);
        av_free(c);
    }
}

static void close_connection(HTTPContext *c)
{
    HTTPContext **cp, *c1;
    int i, nb_streams;
    AVFormatContext *ctx;
    URLContext *h;
    AVStream *st;

    /* remove connection from list */
    cp = &first_http_ctx;
    while ((*cp) != NULL) {
        c1 = *cp;
    /* remove references, if any (XXX: do it faster) */
    for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) {
        if (c1->rtsp_c == c)
            c1->rtsp_c = NULL;
    }

    /* remove connection associated resources */
    if (c->fd >= 0)
    if (c->fmt_in) {
        /* close each frame parser */
        for(i=0;i<c->fmt_in->nb_streams;i++) {
            st = c->fmt_in->streams[i];
            if (st->codec->codec)
        avformat_close_input(&c->fmt_in);
    }

    /* free RTP output streams if any */
    nb_streams = 0;
    for(i=0;i<nb_streams;i++) {
        ctx = c->rtp_ctx[i];
        if (ctx) {
            av_write_trailer(ctx);
            av_dict_free(&ctx->metadata);
            av_free(ctx->streams[0]);
            ffurl_close(h);
    if (!c->last_packet_sent && c->state == HTTPSTATE_SEND_DATA_TRAILER) {
            if (avio_open_dyn_buf(&ctx->pb) >= 0) {
                avio_close_dyn_buf(ctx->pb, &c->pb_buffer);
    for(i=0; i<ctx->nb_streams; i++)
Alex Beregszaszi's avatar
Alex Beregszaszi committed
        av_free(ctx->streams[i]);
    av_freep(&ctx->streams);
    av_freep(&ctx->priv_data);
    if (c->stream && !c->post && c->stream->stream_type == STREAM_TYPE_LIVE)
        current_bandwidth -= c->stream->bandwidth;

    /* signal that there is no feed if we are the feeder socket */
    if (c->state == HTTPSTATE_RECEIVE_DATA && c->stream) {
        c->stream->feed_opened = 0;
        close(c->feed_fd);
    }

    av_freep(&c->packet_buffer);
    av_free(c->buffer);
    av_free(c);
    nb_connections--;
}

static int handle_connection(HTTPContext *c)
{
    int len, ret;
Fabrice Bellard's avatar
Fabrice Bellard committed
    switch(c->state) {
    case HTTPSTATE_WAIT_REQUEST:
Fabrice Bellard's avatar
Fabrice Bellard committed
        /* 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 = recv(c->fd, c->buffer_ptr, 1, 0);
Fabrice Bellard's avatar
Fabrice Bellard committed
        if (len < 0) {
            if (ff_neterrno() != AVERROR(EAGAIN) &&
                ff_neterrno() != AVERROR(EINTR))
Fabrice Bellard's avatar
Fabrice Bellard committed
                return -1;
        } else if (len == 0) {
            return -1;
        } else {
            /* search for end of request. */
            uint8_t *ptr;
Fabrice Bellard's avatar
Fabrice Bellard committed
            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 (c->state == HTTPSTATE_WAIT_REQUEST) {
                    ret = http_parse_request(c);
                } else {
                    ret = rtsp_parse_request(c);
                }