#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>
#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;
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];
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 */
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 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) {
+ if (feed->child_argv && !feed->pid) {
+ feed->pid_start = time(0);
+
feed->pid = fork();
if (feed->pid < 0) {
char *slash;
int i;
- for (i = 0; i < 10; i++) {
+ for (i = 3; i < 256; i++) {
close(i);
}
- i = open("/dev/null", O_RDWR);
- if (i)
- dup2(i, 0);
- dup2(i, 1);
- dup2(i, 2);
+ 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);
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) {
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);
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++;
}
}
}
}
-static int handle_http(HTTPContext *c, long cur_time)
+static int handle_http(HTTPContext *c)
{
int len;
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;
/* 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:
{
#ifdef linux
/* This is somewhat linux specific I guess */
- snprintf(ps_cmd, sizeof(ps_cmd), "ps -o \"%%cpu,cputime\" --no-headers %d", stream->pid);
+ snprintf(ps_cmd, sizeof(ps_cmd), "ps -o \"%%cpu,bsdtime\" --no-headers %d", stream->pid);
pid_stat = popen(ps_cmd, "r");
if (pid_stat) {
nb_bandwidth, nb_max_bandwidth);
q += sprintf(q, "<TABLE>\n");
- q += sprintf(q, "<TR><Th>#<Th>File<Th>IP<Th>State<Th>kbits/sec<Th>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 + c->buffer_size - 2048) {
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 align=right> %d <TD align=right> %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],
- bitrate / 1000,
- 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");
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;
}
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) {
} 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);
}
}
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);
);
}
+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();
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;
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);