* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#define _XOPEN_SOURCE 600
+
#include "config.h"
#ifndef HAVE_CLOSESOCKET
#define closesocket close
#endif
#include <string.h>
+#include <strings.h>
#include <stdlib.h>
#include "libavutil/random.h"
#include "libavutil/avstring.h"
static const OptionDef options[];
-/* maximum number of simultaneous HTTP connections */
-#define HTTP_MAX_CONNECTIONS 2000
-
enum HTTPState {
HTTPSTATE_WAIT_REQUEST,
HTTPSTATE_SEND_HEADER,
static int no_launch;
static int need_to_start_children;
-static int nb_max_connections = 5;
-static int nb_connections;
+/* 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;
static uint64_t max_bandwidth = 1000;
static uint64_t current_bandwidth;
if (level > av_log_level)
return;
if (print_prefix && avc)
- http_log("[%s @ %p]", avc->item_name(ptr), avc);
+ http_log("[%s @ %p]", avc->item_name(ptr), ptr);
print_prefix = strstr(fmt, "\n") != NULL;
http_vlog(fmt, vargs);
}
char *slash;
int i;
+ av_strlcpy(pathname, my_program_name, sizeof(pathname));
+
+ slash = strrchr(pathname, '/');
+ if (!slash)
+ slash = pathname;
+ else
+ slash++;
+ strcpy(slash, "ffmpeg");
+
+ http_log("Launch commandline: ");
+ 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++)
close(i);
}
}
- av_strlcpy(pathname, my_program_name, sizeof(pathname));
-
- slash = strrchr(pathname, '/');
- if (!slash)
- slash = pathname;
- else
- slash++;
- strcpy(slash, "ffmpeg");
-
/* This is needed to make relative pathnames work */
chdir(my_program_dir);
{
int server_fd = 0, rtsp_server_fd = 0;
int ret, delay, delay1;
- struct pollfd poll_table[HTTP_MAX_CONNECTIONS + 2], *poll_entry;
+ struct pollfd *poll_table, *poll_entry;
HTTPContext *c, *c_next;
+ if(!(poll_table = av_mallocz((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) {
server_fd = socket_open_listen(&my_http_addr);
if (server_fd < 0)
start_children(first_feed);
- first_http_ctx = NULL;
- nb_connections = 0;
-
start_multicast();
for(;;) {
ctx = &c->fmt_ctx;
- if (!c->last_packet_sent) {
+ if (!c->last_packet_sent && c->state == HTTPSTATE_SEND_DATA_TRAILER) {
if (ctx->oformat) {
/* prepare header */
if (url_open_dyn_buf(&ctx->pb) >= 0) {
if (stream->stream_type == STREAM_TYPE_REDIRECT) {
c->http_error = 301;
q = c->buffer;
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 301 Moved\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Location: %s\r\n", stream->feed_filename);
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: text/html\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<html><head><title>Moved</title></head><body>\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "You should be <a href=\"%s\">redirected</a>.\r\n", stream->feed_filename);
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "</body></html>\r\n");
-
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 301 Moved\r\n"
+ "Location: %s\r\n"
+ "Content-type: text/html\r\n"
+ "\r\n"
+ "<html><head><title>Moved</title></head><body>\r\n"
+ "You should be <a href=\"%s\">redirected</a>.\r\n"
+ "</body></html>\r\n", stream->feed_filename, stream->feed_filename);
/* prepare output buffer */
c->buffer_ptr = c->buffer;
c->buffer_end = q;
/* If already streaming this feed, do not let start another feeder. */
if (stream->feed_opened) {
snprintf(msg, sizeof(msg), "This feed is already being received.");
+ http_log("feed %s already being received\n", stream->feed_filename);
goto send_error;
}
if (c->post == 0 && max_bandwidth < current_bandwidth) {
c->http_error = 200;
q = c->buffer;
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 Server too busy\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: text/html\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<html><head><title>Too busy</title></head><body>\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<p>The server is too busy to serve your request at this time.</p>\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<p>The bandwidth being served (including your stream) is %lldkbit/sec, and this exceeds the limit of %lldkbit/sec.</p>\r\n",
- current_bandwidth, max_bandwidth);
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "</body></html>\r\n");
-
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 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 bandwidth being served (including your stream) is %" PRIu64 "kbit/sec, "
+ "and this exceeds the limit of %" PRIu64 "kbit/sec.</p>\r\n"
+ "</body></html>\r\n", current_bandwidth, max_bandwidth);
/* prepare output buffer */
c->buffer_ptr = c->buffer;
c->buffer_end = q;
q = c->buffer;
switch(redir_type) {
case REDIR_ASX:
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 ASX Follows\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: video/x-ms-asf\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<ASX Version=\"3\">\r\n");
- //q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<!-- Autogenerated by ffserver -->\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<ENTRY><REF HREF=\"http://%s/%s%s\"/></ENTRY>\r\n",
- hostbuf, filename, info);
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "</ASX>\r\n");
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 ASX Follows\r\n"
+ "Content-type: video/x-ms-asf\r\n"
+ "\r\n"
+ "<ASX Version=\"3\">\r\n"
+ //"<!-- Autogenerated by ffserver -->\r\n"
+ "<ENTRY><REF HREF=\"http://%s/%s%s\"/></ENTRY>\r\n"
+ "</ASX>\r\n", hostbuf, filename, info);
break;
case REDIR_RAM:
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 RAM Follows\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: audio/x-pn-realaudio\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "# Autogenerated by ffserver\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "http://%s/%s%s\r\n",
- hostbuf, filename, info);
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 RAM Follows\r\n"
+ "Content-type: audio/x-pn-realaudio\r\n"
+ "\r\n"
+ "# Autogenerated by ffserver\r\n"
+ "http://%s/%s%s\r\n", hostbuf, filename, info);
break;
case REDIR_ASF:
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 ASF Redirect follows\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: video/x-ms-asf\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "[Reference]\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Ref1=http://%s/%s%s\r\n",
- hostbuf, filename, info);
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 ASF Redirect follows\r\n"
+ "Content-type: video/x-ms-asf\r\n"
+ "\r\n"
+ "[Reference]\r\n"
+ "Ref1=http://%s/%s%s\r\n", hostbuf, filename, info);
break;
case REDIR_RTSP:
{
p = strrchr(hostname, ':');
if (p)
*p = '\0';
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 RTSP Redirect follows\r\n");
- /* XXX: incorrect mime type ? */
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: application/x-rtsp\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "rtsp://%s:%d/%s\r\n",
- hostname, ntohs(my_rtsp_addr.sin_port),
- filename);
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 RTSP Redirect follows\r\n"
+ /* XXX: incorrect mime type ? */
+ "Content-type: application/x-rtsp\r\n"
+ "\r\n"
+ "rtsp://%s:%d/%s\r\n", hostname, ntohs(my_rtsp_addr.sin_port), filename);
}
break;
case REDIR_SDP:
int sdp_data_size, len;
struct sockaddr_in my_addr;
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 OK\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: application/sdp\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 200 OK\r\n"
+ "Content-type: application/sdp\r\n"
+ "\r\n");
len = sizeof(my_addr);
getsockname(c->fd, (struct sockaddr *)&my_addr, &len);
if (c->post) {
/* if post, it means a feed is being sent */
if (!stream->is_feed) {
- /* However it might be a status report from WMP! Lets log the data
- * as it might come in handy one day
- */
+ /* However it might be a status report from WMP! Let us log the
+ * data as it might come in handy one day. */
char *logline = 0;
int client_id = 0;
send_error:
c->http_error = 404;
q = c->buffer;
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 404 Not Found\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-type: %s\r\n", "text/html");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<HTML>\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n");
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "<BODY>%s</BODY>\n", msg);
- q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "</HTML>\n");
-
+ q += snprintf(q, c->buffer_size,
+ "HTTP/1.0 404 Not Found\r\n"
+ "Content-type: text/html\r\n"
+ "\r\n"
+ "<HTML>\n"
+ "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n"
+ "<BODY>%s</BODY>\n"
+ "</HTML>\n", msg);
/* prepare output buffer */
c->buffer_ptr = c->buffer;
c->buffer_end = q;
url_fprintf(pb, "Pragma: no-cache\r\n");
url_fprintf(pb, "\r\n");
- url_fprintf(pb, "<HEAD><TITLE>%s Status</TITLE>\n", program_name);
+ url_fprintf(pb, "<HTML><HEAD><TITLE>%s Status</TITLE>\n", program_name);
if (c->stream->feed_filename[0])
url_fprintf(pb, "<link rel=\"shortcut icon\" href=\"%s\">\n", c->stream->feed_filename);
url_fprintf(pb, "</HEAD>\n<BODY>");
stream->conns_served);
fmt_bytecount(pb, stream->bytes_served);
switch(stream->stream_type) {
- case STREAM_TYPE_LIVE:
- {
+ case STREAM_TYPE_LIVE: {
int audio_bit_rate = 0;
int video_bit_rate = 0;
const char *audio_codec_name = "";
url_fprintf(pb, "Number of connections: %d / %d<BR>\n",
nb_connections, nb_max_connections);
- url_fprintf(pb, "Bandwidth in use: %lldk / %lldk<BR>\n",
+ url_fprintf(pb, "Bandwidth in use: %" PRIu64 "k / %" PRIu64 "k<BR>\n",
current_bandwidth, max_bandwidth);
url_fprintf(pb, "<TABLE>\n");
strcpy(input_filename, c->stream->feed->feed_filename);
buf_size = FFM_PACKET_SIZE;
/* compute position (absolute time) */
- if (find_info_tag(buf, sizeof(buf), "date", info))
- {
+ if (find_info_tag(buf, sizeof(buf), "date", info)) {
stream_pos = parse_date(buf, 0);
if (stream_pos == INT64_MIN)
return -1;
- }
- else if (find_info_tag(buf, sizeof(buf), "buffer", info)) {
+ } else if (find_info_tag(buf, sizeof(buf), "buffer", info)) {
int prebuffer = strtol(buf, 0, 10);
stream_pos = av_gettime() - prebuffer * (int64_t)1000000;
} else
strcpy(input_filename, c->stream->feed_filename);
buf_size = 0;
/* compute position (relative time) */
- if (find_info_tag(buf, sizeof(buf), "date", info))
- {
+ if (find_info_tag(buf, sizeof(buf), "date", info)) {
stream_pos = parse_date(buf, 1);
if (stream_pos == INT64_MIN)
return -1;
- }
- else
+ } else
stream_pos = 0;
}
if (input_filename[0] == '\0')
AVStream *ist, *ost;
send_it:
ist = c->fmt_in->streams[source_index];
- ost = ctx->streams[pkt.stream_index];
/* specific handling for RTP: we use several
output stream (one for each RTP
connection). XXX: need more abstract handling */
} else {
ctx = &c->fmt_ctx;
/* Fudge here */
- codec = ost->codec;
+ codec = ctx->streams[pkt.stream_index]->codec;
}
if (c->is_packetized) {
/* XXX: potential leak */
return -1;
}
- c->fmt_ctx.pb->is_streamed = 1;
+ ost = ctx->streams[pkt.stream_index];
+
+ ctx->pb->is_streamed = 1;
if (pkt.dts != AV_NOPTS_VALUE)
pkt.dts = av_rescale_q(pkt.dts, ist->time_base, ost->time_base);
if (pkt.pts != AV_NOPTS_VALUE)
goto fail;
}
- for (i = 0; i < s->nb_streams; i++)
- memcpy(feed->streams[i]->codec,
- s->streams[i]->codec, sizeof(AVCodecContext));
+ for (i = 0; i < s->nb_streams; i++) {
+ AVStream *fst = feed->streams[i];
+ AVStream *st = s->streams[i];
+ memcpy(fst->codec, st->codec, sizeof(AVCodecContext));
+ if (fst->codec->extradata_size) {
+ fst->codec->extradata = av_malloc(fst->codec->extradata_size);
+ if (!fst->codec->extradata)
+ goto fail;
+ memcpy(fst->codec->extradata, st->codec->extradata,
+ fst->codec->extradata_size);
+ }
+ }
av_close_input_stream(s);
av_free(pb);
if (sf->index != ss->index ||
sf->id != ss->id) {
- printf("Index & Id do not match for stream %d (%s)\n",
+ http_log("Index & Id do not match for stream %d (%s)\n",
i, feed->feed_filename);
matches = 0;
} else {
#define CHECK_CODEC(x) (ccf->x != ccs->x)
if (CHECK_CODEC(codec) || CHECK_CODEC(codec_type)) {
- printf("Codecs do not match for stream %d\n", i);
+ http_log("Codecs do not match for stream %d\n", i);
matches = 0;
} else if (CHECK_CODEC(bit_rate) || CHECK_CODEC(flags)) {
- printf("Codec bitrates do not match for stream %d\n", i);
+ http_log("Codec bitrates do not match for stream %d\n", i);
matches = 0;
} else if (ccf->codec_type == CODEC_TYPE_VIDEO) {
if (CHECK_CODEC(time_base.den) ||
CHECK_CODEC(time_base.num) ||
CHECK_CODEC(width) ||
CHECK_CODEC(height)) {
- printf("Codec width, height and framerate do not match for stream %d\n", i);
+ http_log("Codec width, height and framerate do not match for stream %d\n", i);
matches = 0;
}
} else if (ccf->codec_type == CODEC_TYPE_AUDIO) {
if (CHECK_CODEC(sample_rate) ||
CHECK_CODEC(channels) ||
CHECK_CODEC(frame_size)) {
- printf("Codec sample_rate, channels, frame_size do not match for stream %d\n", i);
+ http_log("Codec sample_rate, channels, frame_size do not match for stream %d\n", i);
matches = 0;
}
} else {
- printf("Unknown codec type\n");
+ http_log("Unknown codec type\n");
matches = 0;
}
}
break;
}
} else
- printf("Deleting feed file '%s' as stream counts differ (%d != %d)\n",
+ http_log("Deleting feed file '%s' as stream counts differ (%d != %d)\n",
feed->feed_filename, s->nb_streams, feed->nb_streams);
av_close_input_file(s);
} else
- printf("Deleting feed file '%s' as it appears to be corrupt\n",
+ http_log("Deleting feed file '%s' as it appears to be corrupt\n",
feed->feed_filename);
if (!matches) {
if (feed->readonly) {
- printf("Unable to delete feed file '%s' as it is marked readonly\n",
+ http_log("Unable to delete feed file '%s' as it is marked readonly\n",
feed->feed_filename);
exit(1);
}
AVFormatContext s1, *s = &s1;
if (feed->readonly) {
- printf("Unable to create feed file '%s' as it is marked readonly\n",
+ http_log("Unable to create feed file '%s' as it is marked readonly\n",
feed->feed_filename);
exit(1);
}
}
#endif
-static int opt_default(const char *opt, const char *arg,
+static int ffserver_opt_default(const char *opt, const char *arg,
AVCodecContext *avctx, int type)
{
const AVOption *o = NULL;
const AVOption *o2 = av_find_opt(avctx, opt, NULL, type, type);
if(o2)
- o = av_set_string(avctx, opt, arg);
+ o = av_set_string2(avctx, opt, arg, 1);
if(!o)
return -1;
return 0;
filename, line_num, arg);
errors++;
}
+ } else if (!strcasecmp(cmd, "MaxHTTPConnections")) {
+ get_arg(arg, sizeof(arg), &p);
+ val = atoi(arg);
+ if (val < 1 || val > 65536) {
+ fprintf(stderr, "%s:%d: Invalid MaxHTTPConnections: %s\n",
+ filename, line_num, arg);
+ errors++;
+ }
+ nb_max_http_connections = val;
} else if (!strcasecmp(cmd, "MaxClients")) {
get_arg(arg, sizeof(arg), &p);
val = atoi(arg);
- if (val < 1 || val > HTTP_MAX_CONNECTIONS) {
+ if (val < 1 || val > nb_max_http_connections) {
fprintf(stderr, "%s:%d: Invalid MaxClients: %s\n",
filename, line_num, arg);
errors++;
(my_http_addr.sin_addr.s_addr == INADDR_ANY) ? "127.0.0.1" :
inet_ntoa(my_http_addr.sin_addr),
ntohs(my_http_addr.sin_port), feed->filename);
-
- if (ffserver_debug)
- {
- int j;
- fprintf(stdout, "Launch commandline: ");
- for (j = 0; j <= i; j++)
- fprintf(stdout, "%s ", feed->child_argv[j]);
- fprintf(stdout, "\n");
- }
}
} else if (!strcasecmp(cmd, "ReadOnlyFile")) {
if (feed) {
} else if (!strcasecmp(cmd, "Format")) {
get_arg(arg, sizeof(arg), &p);
if (stream) {
- if (!strcmp(arg, "status")) {
- stream->stream_type = STREAM_TYPE_STATUS;
- stream->fmt = NULL;
- } else {
- stream->stream_type = STREAM_TYPE_LIVE;
- /* jpeg cannot be used here, so use single frame jpeg */
- if (!strcmp(arg, "jpeg"))
- strcpy(arg, "mjpeg");
- stream->fmt = guess_stream_format(arg, NULL, NULL);
- if (!stream->fmt) {
- fprintf(stderr, "%s:%d: Unknown Format: %s\n",
- filename, line_num, arg);
- errors++;
+ if (!strcmp(arg, "status")) {
+ stream->stream_type = STREAM_TYPE_STATUS;
+ stream->fmt = NULL;
+ } else {
+ stream->stream_type = STREAM_TYPE_LIVE;
+ /* jpeg cannot be used here, so use single frame jpeg */
+ if (!strcmp(arg, "jpeg"))
+ strcpy(arg, "mjpeg");
+ stream->fmt = guess_stream_format(arg, NULL, NULL);
+ if (!stream->fmt) {
+ fprintf(stderr, "%s:%d: Unknown Format: %s\n",
+ filename, line_num, arg);
+ errors++;
+ }
+ }
+ if (stream->fmt) {
+ audio_id = stream->fmt->audio_codec;
+ video_id = stream->fmt->video_codec;
}
- }
- if (stream->fmt) {
- audio_id = stream->fmt->audio_codec;
- video_id = stream->fmt->video_codec;
- }
}
} else if (!strcasecmp(cmd, "InputFormat")) {
get_arg(arg, sizeof(arg), &p);
avctx = &audio_enc;
type = AV_OPT_FLAG_AUDIO_PARAM;
}
- if (opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING_PARAM)) {
+ if (ffserver_opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING_PARAM)) {
fprintf(stderr, "AVOption error: %s %s\n", arg, arg2);
errors++;
}
exit(1);
}
+ /* open log file if needed */
+ if (logfilename[0] != '\0') {
+ if (!strcmp(logfilename, "-"))
+ logfile = stdout;
+ else
+ logfile = fopen(logfilename, "a");
+ av_log_set_callback(http_av_log);
+ }
+
build_file_streams();
build_feed_streams();
} else {
/* child */
setsid();
- chdir("/");
close(0);
open("/dev/null", O_RDWR);
if (strcmp(logfilename, "-") != 0) {
/* signal init */
signal(SIGPIPE, SIG_IGN);
- /* open log file if needed */
- if (logfilename[0] != '\0') {
- if (!strcmp(logfilename, "-"))
- logfile = stderr;
- else
- logfile = fopen(logfilename, "a");
- av_log_set_callback(http_av_log);
- }
+ if (ffserver_daemon)
+ chdir("/");
if (http_server() < 0) {
http_log("Could not start server\n");