#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/wait.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
"WAIT_FEED",
};
-#define IOBUFFER_MAX_SIZE 32768
-#define PACKET_MAX_SIZE 16384
+#define IOBUFFER_INIT_SIZE 8192
+#define PBUFFER_INIT_SIZE 8192
/* coef for exponential mean for bitrate estimation in statistics */
#define AVG_COEF 0.9
#define REQUEST_TIMEOUT (15 * 1000)
#define SYNC_TIMEOUT (10 * 1000)
+typedef struct {
+ INT64 count1, count2;
+ long time1, time2;
+} DataRateData;
+
/* context associated with one connection */
typedef struct HTTPContext {
enum HTTPState state;
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;
AVFormatContext fmt_ctx;
int last_packet_sent; /* true if last data packet was sent */
int suppress_log;
int bandwidth;
- time_t start_time;
+ long start_time; /* In milliseconds - this wraps fairly often */
+ DataRateData datarate;
+ int wmp_client_id;
char protocol[16];
char method[16];
char url[128];
- UINT8 buffer[IOBUFFER_MAX_SIZE];
- UINT8 pbuffer[PACKET_MAX_SIZE];
+ int buffer_size;
+ UINT8 *buffer;
+ int pbuffer_size;
+ UINT8 *pbuffer;
} HTTPContext;
/* each generated stream is described here */
enum StreamType {
STREAM_TYPE_LIVE,
STREAM_TYPE_STATUS,
+ STREAM_TYPE_REDIRECT,
};
/* description of each stream of the ffserver.conf file */
AVOutputFormat *fmt;
int nb_streams;
int prebuffer; /* Number of millseconds early to start */
- time_t max_time;
+ long max_time; /* Number of milliseconds to run */
int send_on_key;
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 */
+ char author[512];
+ char title[512];
+ char copyright[512];
+ char comment[512];
+ pid_t pid; /* Of ffmpeg process */
+ time_t pid_start; /* Of ffmpeg process */
+ char **child_argv;
struct FFStream *next;
/* feed specific */
int feed_opened; /* true if someone if writing to feed */
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 handle_http(HTTPContext *c);
static int http_parse_request(HTTPContext *c);
static int http_send_data(HTTPContext *c);
static void compute_stats(HTTPContext *c);
static int http_start_receive_data(HTTPContext *c);
static int http_receive_data(HTTPContext *c);
+static const char *my_program_name;
+
+static int ffserver_debug;
+static int no_launch;
+static int need_to_start_children;
+
int nb_max_connections;
int nb_connections;
int nb_max_bandwidth;
int nb_bandwidth;
+static long cur_time; // Making this global saves on passing it around everywhere
+
static long gettime_ms(void)
{
struct timeval tv;
buf1, buf2, c->method, c->url, c->protocol, (c->http_error ? c->http_error : 200), c->data_count);
}
+static void update_datarate(DataRateData *drd, INT64 count)
+{
+ if (!drd->time1 && !drd->count1) {
+ drd->time1 = drd->time2 = cur_time;
+ drd->count1 = drd->count2 = count;
+ } else {
+ if (cur_time - drd->time2 > 5000) {
+ drd->time1 = drd->time2;
+ drd->count1 = drd->count2;
+ drd->time2 = cur_time;
+ drd->count2 = count;
+ }
+ }
+}
+
+/* In bytes per second */
+static int compute_datarate(DataRateData *drd, INT64 count)
+{
+ if (cur_time == drd->time1)
+ return 0;
+
+ return ((count - drd->count1) * 1000) / (cur_time - drd->time1);
+}
+
+static void start_children(FFStream *feed)
+{
+ if (no_launch)
+ return;
+
+ for (; feed; feed = feed->next) {
+ if (feed->child_argv && !feed->pid) {
+ feed->pid_start = time(0);
+
+ 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 = 3; i < 256; i++) {
+ close(i);
+ }
+
+ if (!ffserver_debug) {
+ i = open("/dev/null", O_RDWR);
+ if (i)
+ dup2(i, 0);
+ dup2(i, 1);
+ dup2(i, 2);
+ if (i)
+ close(i);
+ }
+
+ 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);
+ }
+ }
+ }
+}
+
/* main loop of the http server */
static int http_server(struct sockaddr_in my_addr)
{
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) {
http_log("ffserver started.\n");
+ start_children(first_feed);
+
fcntl(server_fd, F_SETFL, O_NONBLOCK);
first_http_ctx = NULL;
nb_connections = 0;
cur_time = gettime_ms();
+ if (need_to_start_children) {
+ need_to_start_children = 0;
+ start_children(first_feed);
+ }
+
/* now handle the events */
cp = &first_http_ctx;
while ((*cp) != NULL) {
c = *cp;
- if (handle_http (c, cur_time) < 0) {
+ if (handle_http (c) < 0) {
/* close and free the connection */
log_connection(c);
close(c->fd);
av_close_input_file(c->fmt_in);
*cp = c->next;
nb_bandwidth -= c->bandwidth;
+ av_free(c->buffer);
+ av_free(c->pbuffer);
av_free(c);
nb_connections--;
} else {
/* XXX: should output a warning page when coming
close to the connection limit */
if (nb_connections >= nb_max_connections) {
- close(fd);
+ c = NULL;
} 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++;
+ 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;
+ c->start_time = cur_time;
+ nb_connections++;
+ }
+ }
+ }
+ if (!c) {
+ close(fd);
}
}
}
}
}
-static int handle_http(HTTPContext *c, long cur_time)
+static int handle_http(HTTPContext *c)
{
int len;
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 i;
+ 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];
+ break;
+ case 1:
+ c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 2);
+ break;
+ case 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
+ break;
+ }
+
+ 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;
+}
/* parse http request and prepare header */
static int http_parse_request(HTTPContext *c)
char *p;
int post;
int doing_asx;
+ int doing_asf_redirector;
int doing_ram;
char cmd[32];
char info[1024], *filename;
const char *mime_type;
FFStream *stream;
int i;
+ char ratebuf[32];
+ char *useragent = 0;
p = c->buffer;
q = cmd;
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';
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)) {
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++) {
return 0;
}
- if (doing_asx || doing_ram) {
+ if (doing_asx || doing_ram || doing_asf_redirector) {
char *hostinfo = 0;
for (p = c->buffer; *p && *p != '\r' && *p != '\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);
} else
av_abort();
goto send_error;
}
- c->stream = stream;
stream->conns_served++;
/* XXX: add there authenticate and IP match */
* as it might come in handy one day
*/
char *logline = 0;
+ int client_id = 0;
for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
if (strncasecmp(p, "Pragma: log-line=", 17) == 0) {
logline = p;
break;
}
+ if (strncasecmp(p, "Pragma: client-id=", 18) == 0) {
+ client_id = strtol(p + 18, 0, 10);
+ }
p = strchr(p, '\n');
if (!p)
break;
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;
+ }
+ }
+ }
sprintf(msg, "POST command not handled");
goto send_error;
return 0;
}
+#ifdef DEBUG_WMP
+ if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) {
+ http_log("\nGot request:\n%s\n", c->buffer);
+ }
+#endif
+
if (c->stream->stream_type == STREAM_TYPE_STATUS)
goto send_stats;
/* 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";
+ /* Need to allocate a client id */
+ static int wmp_session;
+
+ if (!wmp_session)
+ wmp_session = time(0) & 0xffffff;
+
+ c->wmp_client_id = ++wmp_session;
+
+ q += sprintf(q, "Server: Cougar 4.1.0.3923\r\nCache-Control: no-cache\r\nPragma: client-id=%d\r\nPragma: features=\"broadcast\"\r\n", c->wmp_client_id);
+ mime_type = "application/octet-stream";
}
q += sprintf(q, "Content-Type: %s\r\n", mime_type);
q += sprintf(q, "\r\n");
return 0;
}
+static int fmt_bytecount(char *q, INT64 count)
+{
+ static const char *suffix = " kMGTP";
+ const char *s;
+
+ for (s = suffix; count >= 100000 && s[1]; count /= 1000, s++) {
+ }
+
+ return sprintf(q, "%lld%c", count, *s);
+}
+
static void compute_stats(HTTPContext *c)
{
HTTPContext *c1;
char *q, *p;
time_t ti;
int i;
+ char *new_buffer;
+
+ new_buffer = av_malloc(65536);
+ if (new_buffer) {
+ av_free(c->buffer);
+ c->buffer_size = 65536;
+ c->buffer = new_buffer;
+ c->buffer_ptr = c->buffer;
+ c->buffer_end = c->buffer + c->buffer_size;
+ }
q = c->buffer;
q += sprintf(q, "HTTP/1.0 200 OK\r\n");
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, "<HEAD><TITLE>FFServer Status</TITLE>\n");
+ if (c->stream->feed_filename) {
+ q += sprintf(q, "<link rel=\"shortcut icon\" href=\"%s\">\n", c->stream->feed_filename);
+ }
+ q += sprintf(q, "</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");
+ q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>bytes<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");
stream = first_stream;
while (stream != NULL) {
char sfilename[1024];
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);
+ q += sprintf(q, "<td align=right> %d <td align=right> ",
+ stream->conns_served);
+ q += fmt_bytecount(q, stream->bytes_served);
switch(stream->stream_type) {
case STREAM_TYPE_LIVE:
{
while (stream != NULL) {
if (stream->feed == stream) {
q += sprintf(q, "<h2>Feed %s</h2>", stream->filename);
+ if (stream->pid) {
+ FILE *pid_stat;
+ char ps_cmd[64];
+
+ q += sprintf(q, "Running as pid %d.\n", stream->pid);
+
+#ifdef linux
+ /* This is somewhat linux specific I guess */
+ snprintf(ps_cmd, sizeof(ps_cmd), "ps -o \"%%cpu,bsdtime\" --no-headers %d", stream->pid);
+
+ pid_stat = popen(ps_cmd, "r");
+ if (pid_stat) {
+ char cpuperc[10];
+ char cpuused[64];
+
+ if (fscanf(pid_stat, "%10s %64s", cpuperc, cpuused) == 2) {
+ q += sprintf(q, "Currently using %s%% of the cpu. Total time used %s.\n",
+ cpuperc, cpuused);
+ }
+ fclose(pid_stat);
+ }
+#endif
+
+ q += sprintf(q, "<p>");
+ }
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++) {
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);
+ sprintf(parameters, "%dx%d, q=%d-%d, fps=%d", st->codec.width, st->codec.height,
+ st->codec.qmin, st->codec.qmax, st->codec.frame_rate / FRAME_RATE_BASE);
break;
default:
av_abort();
nb_bandwidth, nb_max_bandwidth);
q += sprintf(q, "<TABLE>\n");
- q += sprintf(q, "<TR><TD>#<TD>File<TD>IP<TD>State<TD>Size\n");
+ q += sprintf(q, "<TR><Th>#<Th>File<Th>IP<Th>State<Th>Target bits/sec<Th>Actual bits/sec<Th>Bytes transferred\n");
c1 = first_http_ctx;
i = 0;
- while (c1 != NULL && q < (char *) c->buffer + sizeof(c->buffer) - 2048) {
+ while (c1 != NULL && q < (char *) c->buffer + c->buffer_size - 2048) {
+ int bitrate;
+ int j;
+
+ bitrate = 0;
+ for (j = 0; j < c1->stream->nb_streams; j++) {
+ if (c1->feed_streams[j] >= 0) {
+ bitrate += c1->stream->feed->streams[c1->feed_streams[j]]->codec.bit_rate;
+ }
+ }
+
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",
+ q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <td align=right>",
i, c1->stream->filename,
c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "",
p,
- http_state[c1->state],
- c1->data_count);
+ http_state[c1->state]);
+ q += fmt_bytecount(q, bitrate);
+ q += sprintf(q, "<td align=right>");
+ q += fmt_bytecount(q, compute_datarate(&c1->datarate, c1->data_count) * 8);
+ q += sprintf(q, "<td align=right>");
+ q += fmt_bytecount(q, c1->data_count);
+ *q++ = '\n';
c1 = c1->next;
}
q += sprintf(q, "</TABLE>\n");
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)
- av_abort();
+ if (c->buffer_end - c->buffer + size > c->buffer_size) {
+ int new_buffer_size = c->buffer_size * 2;
+ UINT8 *new_buffer;
+
+ if (new_buffer_size <= c->buffer_end - c->buffer + size) {
+ new_buffer_size = c->buffer_end - c->buffer + size + c->buffer_size;
+ }
+
+ new_buffer = av_malloc(new_buffer_size);
+ if (new_buffer) {
+ memcpy(new_buffer, c->buffer, c->buffer_end - c->buffer);
+ c->buffer_end += (new_buffer - c->buffer);
+ c->buffer_ptr += (new_buffer - c->buffer);
+ av_free(c->buffer);
+ c->buffer = new_buffer;
+ c->buffer_size = new_buffer_size;
+ } else {
+ av_abort();
+ }
+ }
memcpy(c->buffer_end, buf, size);
c->buffer_end += size;
switch(c->state) {
case HTTPSTATE_SEND_DATA_HEADER:
memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx));
+ pstrcpy(c->fmt_ctx.author, sizeof(c->fmt_ctx.author), c->stream->author);
+ pstrcpy(c->fmt_ctx.comment, sizeof(c->fmt_ctx.comment), c->stream->comment);
+ pstrcpy(c->fmt_ctx.copyright, sizeof(c->fmt_ctx.copyright), c->stream->copyright);
+ pstrcpy(c->fmt_ctx.title, sizeof(c->fmt_ctx.title), c->stream->title);
+
if (c->stream->feed) {
/* open output stream by using specified codecs */
c->fmt_ctx.oformat = c->stream->fmt;
}
c->got_key_frame = 0;
}
- init_put_byte(&c->fmt_ctx.pb, c->pbuffer, PACKET_MAX_SIZE,
+ init_put_byte(&c->fmt_ctx.pb, c->pbuffer, c->pbuffer_size,
1, c, NULL, http_write_packet, NULL);
c->fmt_ctx.pb.is_streamed = 1;
/* prepare header */
}
if (c->stream->max_time &&
- c->stream->max_time + c->start_time > time(0)) {
+ c->stream->max_time + c->start_time - cur_time < 0) {
/* We have timed out */
c->state = HTTPSTATE_SEND_DATA_TRAILER;
} else if (av_read_packet(c->fmt_in, &pkt) < 0) {
/* send it to the appropriate stream */
if (c->stream->feed) {
/* if coming from a feed, select the right stream */
+ if (c->switch_pending) {
+ c->switch_pending = 0;
+ for(i=0;i<c->stream->nb_streams;i++) {
+ if (c->switch_feed_streams[i] == pkt.stream_index) {
+ if (pkt.flags & PKT_FLAG_KEY) {
+ do_switch_stream(c, i);
+ }
+ }
+ if (c->switch_feed_streams[i] >= 0) {
+ c->switch_pending = 1;
+ }
+ }
+ }
for(i=0;i<c->stream->nb_streams;i++) {
- if (c->stream->feed_streams[i] == pkt.stream_index) {
+ if (c->feed_streams[i] == pkt.stream_index) {
pkt.stream_index = i;
if (pkt.flags & PKT_FLAG_KEY) {
c->got_key_frame |= 1 << i;
} else {
c->buffer_ptr += len;
c->data_count += len;
+ update_datarate(&c->datarate, c->data_count);
if (c->stream)
c->stream->bytes_served += len;
}
} else {
c->buffer_ptr += len;
c->data_count += len;
+ update_datarate(&c->datarate, c->data_count);
}
}
for(i=0;i<stream->nb_streams;i++) {
stream->feed_streams[i] = add_av_stream(feed, stream->streams[i]);
}
- } else {
+ }
+ }
+ }
+
+ /* gather all streams */
+ for(stream = first_stream; stream != NULL; stream = stream->next) {
+ feed = stream->feed;
+ if (feed) {
+ if (stream->is_feed) {
for(i=0;i<stream->nb_streams;i++) {
stream->feed_streams[i] = i;
}
char arg[1024];
const char *p;
int val, errors, line_num;
- FFStream **last_stream, *stream;
+ FFStream **last_stream, *stream, *redirect;
FFStream **last_feed, *feed;
AVCodecContext audio_enc, video_enc;
int audio_id, video_id;
last_feed = &first_feed;
stream = NULL;
feed = NULL;
+ redirect = NULL;
audio_id = CODEC_ID_NONE;
video_id = CODEC_ID_NONE;
for(;;) {
feed->is_feed = 1;
feed->feed = feed; /* self feeding :-) */
}
+ } else if (!strcasecmp(cmd, "Launch")) {
+ if (feed) {
+ int i;
+
+ feed->child_argv = (char **) av_mallocz(64 * sizeof(char *));
+
+ feed->child_argv[0] = av_malloc(7);
+ strcpy(feed->child_argv[0], "ffmpeg");
+
+ for (i = 1; i < 62; i++) {
+ char argbuf[256];
+
+ get_arg(argbuf, sizeof(argbuf), &p);
+ if (!argbuf[0])
+ break;
+
+ feed->child_argv[i] = av_malloc(strlen(argbuf + 1));
+ strcpy(feed->child_argv[i], argbuf);
+ }
+
+ feed->child_argv[i] = av_malloc(30 + strlen(feed->filename));
+
+ snprintf(feed->child_argv[i], 256, "http://127.0.0.1:%d/%s",
+ ntohs(my_addr.sin_port), feed->filename);
+ }
} else if (!strcasecmp(cmd, "File")) {
if (feed) {
get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p);
audio_id = stream->fmt->audio_codec;
video_id = stream->fmt->video_codec;
}
+ } else if (!strcasecmp(cmd, "FaviconURL")) {
+ if (stream && stream->stream_type == STREAM_TYPE_STATUS) {
+ get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p);
+ } else {
+ fprintf(stderr, "%s:%d: FaviconURL only permitted for status streams\n",
+ filename, line_num);
+ errors++;
+ }
+ } else if (!strcasecmp(cmd, "Author")) {
+ if (stream) {
+ get_arg(stream->author, sizeof(stream->author), &p);
+ }
+ } else if (!strcasecmp(cmd, "Comment")) {
+ if (stream) {
+ get_arg(stream->comment, sizeof(stream->comment), &p);
+ }
+ } else if (!strcasecmp(cmd, "Copyright")) {
+ if (stream) {
+ get_arg(stream->copyright, sizeof(stream->copyright), &p);
+ }
+ } else if (!strcasecmp(cmd, "Title")) {
+ if (stream) {
+ get_arg(stream->title, sizeof(stream->title), &p);
+ }
} else if (!strcasecmp(cmd, "Preroll")) {
get_arg(arg, sizeof(arg), &p);
if (stream) {
} else if (!strcasecmp(cmd, "MaxTime")) {
get_arg(arg, sizeof(arg), &p);
if (stream) {
- stream->max_time = atoi(arg);
+ stream->max_time = atoi(arg) * 1000;
}
} else if (!strcasecmp(cmd, "AudioBitRate")) {
get_arg(arg, sizeof(arg), &p);
}
}
stream = NULL;
+ } else if (!strcasecmp(cmd, "<Redirect")) {
+ /*********************************************/
+ char *q;
+ if (stream || feed || redirect) {
+ fprintf(stderr, "%s:%d: Already in a tag\n",
+ filename, line_num);
+ errors++;
+ } else {
+ redirect = av_mallocz(sizeof(FFStream));
+ *last_stream = redirect;
+ last_stream = &redirect->next;
+
+ get_arg(redirect->filename, sizeof(redirect->filename), &p);
+ q = strrchr(redirect->filename, '>');
+ if (*q)
+ *q = '\0';
+ redirect->stream_type = STREAM_TYPE_REDIRECT;
+ }
+ } else if (!strcasecmp(cmd, "URL")) {
+ if (redirect) {
+ get_arg(redirect->feed_filename, sizeof(redirect->feed_filename), &p);
+ }
+ } else if (!strcasecmp(cmd, "</Redirect>")) {
+ if (!redirect) {
+ fprintf(stderr, "%s:%d: No corresponding <Redirect> for </Redirect>\n",
+ filename, line_num);
+ errors++;
+ }
+ if (!redirect->feed_filename[0]) {
+ fprintf(stderr, "%s:%d: No URL found for <Redirect>\n",
+ filename, line_num);
+ errors++;
+ }
+ redirect = NULL;
} else {
fprintf(stderr, "%s:%d: Incorrect keyword: '%s'\n",
filename, line_num, cmd);
);
}
+static void handle_child_exit(int sig)
+{
+ pid_t pid;
+ int status;
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ FFStream *feed;
+
+ for (feed = first_feed; feed; feed = feed->next) {
+ if (feed->pid == pid) {
+ int uptime = time(0) - feed->pid_start;
+
+ feed->pid = 0;
+ fprintf(stderr, "%s: Pid %d exited with status %d after %d seconds\n", feed->filename, pid, status, uptime);
+
+ if (uptime < 30) {
+ /* Turn off any more restarts */
+ feed->child_argv = 0;
+ }
+ }
+ }
+ }
+
+ need_to_start_children = 1;
+}
+
int main(int argc, char **argv)
{
const char *config_filename;
int c;
+ struct sigaction sigact;
register_all();
config_filename = "/etc/ffserver.conf";
+ my_program_name = argv[0];
+
for(;;) {
- c = getopt_long_only(argc, argv, "Lh?f:", NULL, NULL);
+ c = getopt_long_only(argc, argv, "ndLh?f:", NULL, NULL);
if (c == -1)
break;
switch(c) {
case 'h':
help();
exit(1);
+ case 'n':
+ no_launch = 1;
+ break;
+ case 'd':
+ ffserver_debug = 1;
+ break;
case 'f':
config_filename = optarg;
break;
}
}
+ putenv("http_proxy"); /* Kill the http_proxy */
+
/* address on which the server will handle connections */
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons (8080);
first_stream = NULL;
logfilename[0] = '\0';
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_handler = handle_child_exit;
+ sigact.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ sigaction(SIGCHLD, &sigact, 0);
+
if (parse_ffconfig(config_filename) < 0) {
fprintf(stderr, "Incorrect config file - exiting.\n");
exit(1);