]> git.sesse.net Git - ffmpeg/blobdiff - ffserver.c
reverted to inttypes.h since it gives problems on some unixes
[ffmpeg] / ffserver.c
index 86e21d051d9d61e94fb3dadf99d94e64e62d175a..e11f8763bbb840c3aa192c83fc1677ca866acf6f 100644 (file)
@@ -31,6 +31,7 @@
 #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>
@@ -69,6 +70,11 @@ const char *http_state[] = {
 #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;
@@ -95,7 +101,8 @@ typedef struct HTTPContext {
     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];
@@ -121,13 +128,18 @@ typedef struct FFStream {
     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 */
@@ -152,7 +164,7 @@ 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 handle_http(HTTPContext *c);
 static int http_parse_request(HTTPContext *c);
 static int http_send_data(HTTPContext *c);
 static void compute_stats(HTTPContext *c);
@@ -162,12 +174,18 @@ 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;
@@ -211,10 +229,39 @@ static void log_connection(HTTPContext *c)
              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) {
@@ -227,15 +274,19 @@ static void start_children(FFStream *feed)
                 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);
 
@@ -262,7 +313,6 @@ 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) {
@@ -348,12 +398,17 @@ static int http_server(struct sockaddr_in my_addr)
         
         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);
@@ -404,6 +459,7 @@ static int http_server(struct sockaddr_in my_addr)
                             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++;
                         }
                     }
@@ -417,7 +473,7 @@ static int http_server(struct sockaddr_in my_addr)
     }
 }
 
-static int handle_http(HTTPContext *c, long cur_time)
+static int handle_http(HTTPContext *c)
 {
     int len;
     
@@ -1060,6 +1116,17 @@ static int http_parse_request(HTTPContext *c)
     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;
@@ -1093,7 +1160,7 @@ static void compute_stats(HTTPContext *c)
     /* 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];
@@ -1112,8 +1179,9 @@ static void compute_stats(HTTPContext *c)
             
             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:
                 {
@@ -1182,7 +1250,7 @@ static void compute_stats(HTTPContext *c)
 
 #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) {
@@ -1270,7 +1338,7 @@ static void compute_stats(HTTPContext *c)
                  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) {
@@ -1286,13 +1354,17 @@ static void compute_stats(HTTPContext *c)
 
         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");
@@ -1395,6 +1467,11 @@ static int http_prepare_data(HTTPContext *c)
     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;
@@ -1504,7 +1581,7 @@ static int http_prepare_data(HTTPContext *c)
             }
 
             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) {
@@ -1617,6 +1694,7 @@ static int http_send_data(HTTPContext *c)
         } else {
             c->buffer_ptr += len;
             c->data_count += len;
+            update_datarate(&c->datarate, c->data_count);
             if (c->stream)
                 c->stream->bytes_served += len;
         }
@@ -1667,6 +1745,7 @@ static int http_receive_data(HTTPContext *c)
         } else {
             c->buffer_ptr += len;
             c->data_count += len;
+            update_datarate(&c->datarate, c->data_count);
         }
     }
 
@@ -2223,6 +2302,22 @@ int parse_ffconfig(const char *filename)
                             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) {
@@ -2251,7 +2346,7 @@ int parse_ffconfig(const char *filename)
         } 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);
@@ -2458,10 +2553,37 @@ void licence(void)
     );
 }
 
+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();
 
@@ -2470,7 +2592,7 @@ int main(int argc, char **argv)
     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) {
@@ -2481,6 +2603,12 @@ int main(int argc, char **argv)
         case 'h':
             help();
             exit(1);
+        case 'n':
+            no_launch = 1;
+            break;
+        case 'd':
+            ffserver_debug = 1;
+            break;
         case 'f':
             config_filename = optarg;
             break;
@@ -2500,6 +2628,11 @@ int main(int argc, char **argv)
     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);