X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffserver.c;h=89836c6f2aa1be0ed96be71d435f5325cacc7e89;hb=9fd88b29461b0245f0e73ec30a6b9f29a9e25e72;hp=58431868b934c6e0f037661ea69acb4d73db3676;hpb=75480e86baaa56449cae997db1b8a23e582a40e5;p=ffmpeg diff --git a/ffserver.c b/ffserver.c index 58431868b93..89836c6f2aa 100644 --- a/ffserver.c +++ b/ffserver.c @@ -19,11 +19,14 @@ * 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 +#include #include #include "libavutil/random.h" #include "libavutil/avstring.h" @@ -32,7 +35,7 @@ #include "libavformat/os_support.h" #include "libavformat/rtp.h" #include "libavformat/rtsp.h" - +#include "libavcodec/opt.h" #include #include #include @@ -50,20 +53,15 @@ #include #endif -#include "version.h" -#include "ffserver.h" #include "cmdutils.h" #undef exit const char program_name[] = "FFserver"; -static const int program_birth_year = 2000; +const int program_birth_year = 2000; static const OptionDef options[]; -/* maximum number of simultaneous HTTP connections */ -#define HTTP_MAX_CONNECTIONS 2000 - enum HTTPState { HTTPSTATE_WAIT_REQUEST, HTTPSTATE_SEND_HEADER, @@ -79,7 +77,7 @@ enum HTTPState { RTSPSTATE_SEND_PACKET, }; -const char *http_state[] = { +static const char *http_state[] = { "HTTP_WAIT_REQUEST", "HTTP_SEND_HEADER", @@ -217,7 +215,7 @@ typedef struct FFStream { time_t pid_start; /* Of ffmpeg process */ char **child_argv; struct FFStream *next; - int bandwidth; /* bandwidth, in kbits/s */ + unsigned bandwidth; /* bandwidth, in kbits/s */ /* RTSP options */ char *rtsp_option; /* multicast specific */ @@ -259,7 +257,7 @@ static void close_connection(HTTPContext *c); static int handle_connection(HTTPContext *c); static int http_parse_request(HTTPContext *c); static int http_send_data(HTTPContext *c); -static void compute_stats(HTTPContext *c); +static void compute_status(HTTPContext *c); 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); @@ -294,11 +292,13 @@ static int ffserver_daemon; static int no_launch; static int need_to_start_children; -static int nb_max_connections; -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 int max_bandwidth; -static int current_bandwidth; +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 @@ -306,18 +306,6 @@ static AVRandomState random_state; static FILE *logfile = NULL; -static void __attribute__ ((format (printf, 1, 2))) http_log(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - - if (logfile) { - vfprintf(logfile, fmt, ap); - fflush(logfile); - } - va_end(ap); -} - static char *ctime1(char *buf2) { time_t ti; @@ -332,16 +320,48 @@ static char *ctime1(char *buf2) return buf2; } -static void log_connection(HTTPContext *c) +static void http_vlog(const char *fmt, va_list vargs) { - char buf2[32]; + static int print_prefix = 1; + if (logfile) { + if (print_prefix) { + char buf[32]; + ctime1(buf); + fprintf(logfile, "%s ", buf); + } + print_prefix = strstr(fmt, "\n") != NULL; + vfprintf(logfile, fmt, vargs); + fflush(logfile); + } +} + +void __attribute__ ((format (printf, 1, 2))) 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_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); +} +static void log_connection(HTTPContext *c) +{ if (c->suppress_log) return; - http_log("%s - - [%s] \"%s %s %s\" %d %"PRId64"\n", - inet_ntoa(c->from_addr.sin_addr), - ctime1(buf2), c->method, c->url, + 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); } @@ -380,7 +400,7 @@ static void start_children(FFStream *feed) feed->pid = fork(); if (feed->pid < 0) { - fprintf(stderr, "Unable to create children\n"); + http_log("Unable to create children\n"); exit(1); } if (!feed->pid) { @@ -389,19 +409,6 @@ static void start_children(FFStream *feed) 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); - } - av_strlcpy(pathname, my_program_name, sizeof(pathname)); slash = strrchr(pathname, '/'); @@ -411,6 +418,25 @@ static void start_children(FFStream *feed) 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); + + if (!ffserver_debug) { + i = open("/dev/null", O_RDWR); + if (i != -1) { + dup2(i, 0); + dup2(i, 1); + dup2(i, 2); + close(i); + } + } + /* This is needed to make relative pathnames work */ chdir(my_program_dir); @@ -488,8 +514,8 @@ static void start_multicast(void) continue; if (open_input_stream(rtp_c, "") < 0) { - fprintf(stderr, "Could not open input stream for stream '%s'\n", - stream->filename); + http_log("Could not open input stream for stream '%s'\n", + stream->filename); continue; } @@ -499,8 +525,8 @@ static void start_multicast(void) dest_addr.sin_port = htons(stream->multicast_port + 2 * stream_index); if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, NULL) < 0) { - fprintf(stderr, "Could not open output stream '%s/streamid=%d'\n", - stream->filename, stream_index); + http_log("Could not open output stream '%s/streamid=%d'\n", + stream->filename, stream_index); exit(1); } } @@ -514,36 +540,51 @@ static void start_multicast(void) /* main loop of the http server */ static int http_server(void) { - int server_fd, ret, rtsp_server_fd, delay, delay1; - struct pollfd poll_table[HTTP_MAX_CONNECTIONS + 2], *poll_entry; + int server_fd = 0, rtsp_server_fd = 0; + int ret, delay, delay1; + struct pollfd *poll_table, *poll_entry; HTTPContext *c, *c_next; - server_fd = socket_open_listen(&my_http_addr); - if (server_fd < 0) + 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; + } - rtsp_server_fd = socket_open_listen(&my_rtsp_addr); - if (rtsp_server_fd < 0) + if (my_http_addr.sin_port) { + server_fd = socket_open_listen(&my_http_addr); + if (server_fd < 0) + return -1; + } + + if (my_rtsp_addr.sin_port) { + rtsp_server_fd = socket_open_listen(&my_rtsp_addr); + if (rtsp_server_fd < 0) + return -1; + } + + if (!rtsp_server_fd && !server_fd) { + http_log("HTTP and RTSP disabled.\n"); return -1; + } http_log("ffserver started.\n"); start_children(first_feed); - first_http_ctx = NULL; - nb_connections = 0; - start_multicast(); for(;;) { poll_entry = poll_table; - poll_entry->fd = server_fd; - poll_entry->events = POLLIN; - poll_entry++; - - poll_entry->fd = rtsp_server_fd; - poll_entry->events = POLLIN; - poll_entry++; + if (server_fd) { + poll_entry->fd = server_fd; + poll_entry->events = POLLIN; + poll_entry++; + } + if (rtsp_server_fd) { + poll_entry->fd = rtsp_server_fd; + poll_entry->events = POLLIN; + poll_entry++; + } /* wait for events on each HTTP handle */ c = first_http_ctx; @@ -622,13 +663,17 @@ static int http_server(void) } poll_entry = poll_table; - /* new HTTP connection request ? */ - if (poll_entry->revents & POLLIN) - new_connection(server_fd, 0); - poll_entry++; - /* new RTSP connection request ? */ - if (poll_entry->revents & POLLIN) - new_connection(rtsp_server_fd, 1); + if (server_fd) { + /* new HTTP connection request ? */ + if (poll_entry->revents & POLLIN) + new_connection(server_fd, 0); + poll_entry++; + } + if (rtsp_server_fd) { + /* new RTSP connection request ? */ + if (poll_entry->revents & POLLIN) + new_connection(rtsp_server_fd, 1); + } } } @@ -656,8 +701,10 @@ static void new_connection(int server_fd, int is_rtsp) len = sizeof(from_addr); fd = accept(server_fd, (struct sockaddr *)&from_addr, &len); - if (fd < 0) + if (fd < 0) { + http_log("error during accept %s\n", strerror(errno)); return; + } ff_socket_nonblock(fd, 1); /* XXX: should output a warning page when coming @@ -749,11 +796,12 @@ static void close_connection(HTTPContext *c) 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) { av_write_trailer(ctx); + av_freep(&c->pb_buffer); url_close_dyn_buf(ctx->pb, &c->pb_buffer); } } @@ -1275,14 +1323,14 @@ static int http_parse_request(HTTPContext *c) 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, "Moved\r\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "You should be redirected.\r\n", stream->feed_filename); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\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" + "Moved\r\n" + "You should be redirected.\r\n" + "\r\n", stream->feed_filename, stream->feed_filename); /* prepare output buffer */ c->buffer_ptr = c->buffer; c->buffer_end = q; @@ -1303,6 +1351,7 @@ static int http_parse_request(HTTPContext *c) /* 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; } @@ -1312,15 +1361,15 @@ static int http_parse_request(HTTPContext *c) 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, "Too busy\r\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "

The server is too busy to serve your request at this time.

\r\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "

The bandwidth being served (including your stream) is %dkbit/sec, and this exceeds the limit of %dkbit/sec.

\r\n", - current_bandwidth, max_bandwidth); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\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" + "Too busy\r\n" + "

The server is too busy to serve your request at this time.

\r\n" + "

The bandwidth being served (including your stream) is %" PRIu64 "kbit/sec, " + "and this exceeds the limit of %" PRIu64 "kbit/sec.

\r\n" + "\r\n", current_bandwidth, max_bandwidth); /* prepare output buffer */ c->buffer_ptr = c->buffer; c->buffer_end = q; @@ -1363,30 +1412,30 @@ static int http_parse_request(HTTPContext *c) 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, "\r\n"); - //q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n", - hostbuf, filename, info); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\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" + "\r\n" + //"\r\n" + "\r\n" + "\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: { @@ -1396,13 +1445,12 @@ static int http_parse_request(HTTPContext *c) 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: @@ -1411,9 +1459,10 @@ static int http_parse_request(HTTPContext *c) 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); @@ -1455,9 +1504,8 @@ static int http_parse_request(HTTPContext *c) 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; @@ -1524,7 +1572,7 @@ static int http_parse_request(HTTPContext *c) #endif if (c->stream->stream_type == STREAM_TYPE_STATUS) - goto send_stats; + goto send_status; /* open input stream */ if (open_input_stream(c, info) < 0) { @@ -1560,21 +1608,21 @@ static int http_parse_request(HTTPContext *c) 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, "\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "404 Not Found\n"); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "%s\n", msg); - q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\n"); - + q += snprintf(q, c->buffer_size, + "HTTP/1.0 404 Not Found\r\n" + "Content-type: text/html\r\n" + "\r\n" + "\n" + "404 Not Found\n" + "%s\n" + "\n", msg); /* prepare output buffer */ c->buffer_ptr = c->buffer; c->buffer_end = q; c->state = HTTPSTATE_SEND_HEADER; return 0; - send_stats: - compute_stats(c); + send_status: + compute_status(c); c->http_error = 200; /* horrible : we use this value to avoid going to the send data state */ c->state = HTTPSTATE_SEND_HEADER; @@ -1591,7 +1639,7 @@ static void fmt_bytecount(ByteIOContext *pb, int64_t count) url_fprintf(pb, "%"PRId64"%c", count, *s); } -static void compute_stats(HTTPContext *c) +static void compute_status(HTTPContext *c) { HTTPContext *c1; FFStream *stream; @@ -1612,11 +1660,11 @@ static void compute_stats(HTTPContext *c) url_fprintf(pb, "Pragma: no-cache\r\n"); url_fprintf(pb, "\r\n"); - url_fprintf(pb, "FFServer Status\n"); - if (c->stream->feed_filename) + url_fprintf(pb, "%s Status\n", program_name); + if (c->stream->feed_filename[0]) url_fprintf(pb, "\n", c->stream->feed_filename); url_fprintf(pb, "\n"); - url_fprintf(pb, "

FFServer Status

\n"); + url_fprintf(pb, "

%s Status

\n", program_name); /* format status */ url_fprintf(pb, "

Available Streams

\n"); url_fprintf(pb, "\n"); @@ -1654,8 +1702,7 @@ static void compute_stats(HTTPContext *c) 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 = ""; @@ -1813,7 +1860,7 @@ static void compute_stats(HTTPContext *c) url_fprintf(pb, "Number of connections: %d / %d
\n", nb_connections, nb_max_connections); - url_fprintf(pb, "Bandwidth in use: %dk / %dk
\n", + url_fprintf(pb, "Bandwidth in use: %" PRIu64 "k / %" PRIu64 "k
\n", current_bandwidth, max_bandwidth); url_fprintf(pb, "
\n"); @@ -1893,13 +1940,11 @@ static int open_input_stream(HTTPContext *c, const char *info) 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 @@ -1908,13 +1953,11 @@ static int open_input_stream(HTTPContext *c, const char *info) 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') @@ -1952,7 +1995,7 @@ static int open_input_stream(HTTPContext *c, const char *info) #if 1 if (c->fmt_in->iformat->read_seek) - c->fmt_in->iformat->read_seek(c->fmt_in, 0, stream_pos, 0); + av_seek_frame(c->fmt_in, -1, stream_pos, 0); #endif /* set the start time (needed for maxtime and RTP packet timing) */ c->start_time = cur_time; @@ -2002,14 +2045,10 @@ static int http_prepare_data(HTTPContext *c) av_strlcpy(c->fmt_ctx.title, c->stream->title, sizeof(c->fmt_ctx.title)); - /* open output stream by using specified codecs */ - c->fmt_ctx.oformat = c->stream->fmt; - c->fmt_ctx.nb_streams = c->stream->nb_streams; - for(i=0;ifmt_ctx.nb_streams;i++) { + for(i=0;istream->nb_streams;i++) { AVStream *st; AVStream *src; st = av_mallocz(sizeof(AVStream)); - st->codec= avcodec_alloc_context(); c->fmt_ctx.streams[i] = st; /* if file or feed, then just take streams from FFStream struct */ if (!c->stream->feed || @@ -2023,6 +2062,10 @@ static int http_prepare_data(HTTPContext *c) st->codec->frame_number = 0; /* XXX: should be done in AVStream, not in codec */ } + /* set output format parameters */ + c->fmt_ctx.oformat = c->stream->fmt; + c->fmt_ctx.nb_streams = c->stream->nb_streams; + c->got_key_frame = 0; /* prepare header and save header data in a stream */ @@ -2032,9 +2075,19 @@ static int http_prepare_data(HTTPContext *c) } c->fmt_ctx.pb->is_streamed = 1; + /* + * HACK to avoid mpeg ps muxer to spit many underflow errors + * Default value from FFmpeg + * Try to set it use configuration option + */ + c->fmt_ctx.preload = (int)(0.5*AV_TIME_BASE); + c->fmt_ctx.max_delay = (int)(0.7*AV_TIME_BASE); + av_set_parameters(&c->fmt_ctx, NULL); - if (av_write_header(&c->fmt_ctx) < 0) + if (av_write_header(&c->fmt_ctx) < 0) { + http_log("Error writing output header\n"); return -1; + } len = url_close_dyn_buf(c->fmt_ctx.pb, &c->pb_buffer); c->buffer_ptr = c->pb_buffer; @@ -2045,153 +2098,147 @@ static int http_prepare_data(HTTPContext *c) break; case HTTPSTATE_SEND_DATA: /* find a new packet */ - { + /* read a packet from the input stream */ + if (c->stream->feed) + ffm_set_write_index(c->fmt_in, + c->stream->feed->feed_write_index, + c->stream->feed->feed_size); + + if (c->stream->max_time && + c->stream->max_time + c->start_time - cur_time < 0) + /* We have timed out */ + c->state = HTTPSTATE_SEND_DATA_TRAILER; + else { AVPacket pkt; - - /* read a packet from the input stream */ - if (c->stream->feed) - ffm_set_write_index(c->fmt_in, - c->stream->feed->feed_write_index, - c->stream->feed->feed_size); - - if (c->stream->max_time && - c->stream->max_time + c->start_time - cur_time < 0) - /* We have timed out */ - c->state = HTTPSTATE_SEND_DATA_TRAILER; - else { - redo: - if (av_read_frame(c->fmt_in, &pkt) < 0) { - if (c->stream->feed && c->stream->feed->feed_opened) { - /* if coming from feed, it means we reached the end of the - ffm file, so must wait for more data */ - c->state = HTTPSTATE_WAIT_FEED; - return 1; /* state changed */ - } else { - if (c->stream->loop) { - av_close_input_file(c->fmt_in); - c->fmt_in = NULL; - if (open_input_stream(c, "") < 0) - goto no_loop; - goto redo; - } else { - no_loop: - /* must send trailer now because eof or error */ - c->state = HTTPSTATE_SEND_DATA_TRAILER; - } - } + redo: + if (av_read_frame(c->fmt_in, &pkt) < 0) { + if (c->stream->feed && c->stream->feed->feed_opened) { + /* if coming from feed, it means we reached the end of the + ffm file, so must wait for more data */ + c->state = HTTPSTATE_WAIT_FEED; + return 1; /* state changed */ } else { - /* update first pts if needed */ - if (c->first_pts == AV_NOPTS_VALUE) { - c->first_pts = av_rescale_q(pkt.dts, c->fmt_in->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q); - c->start_time = cur_time; + if (c->stream->loop) { + av_close_input_file(c->fmt_in); + c->fmt_in = NULL; + if (open_input_stream(c, "") < 0) + goto no_loop; + goto redo; + } else { + no_loop: + /* must send trailer now because eof or error */ + c->state = HTTPSTATE_SEND_DATA_TRAILER; } - /* 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;istream->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; - } - } + } + } else { + int source_index = pkt.stream_index; + /* update first pts if needed */ + if (c->first_pts == AV_NOPTS_VALUE) { + c->first_pts = av_rescale_q(pkt.dts, c->fmt_in->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q); + c->start_time = cur_time; + } + /* 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;istream->nb_streams;i++) { - if (c->feed_streams[i] == pkt.stream_index) { - pkt.stream_index = i; + if (c->switch_feed_streams[i] == pkt.stream_index) if (pkt.flags & PKT_FLAG_KEY) - c->got_key_frame |= 1 << i; - /* See if we have all the key frames, then - * we start to send. This logic is not quite - * right, but it works for the case of a - * single video stream with one or more - * audio streams (for which every frame is - * typically a key frame). - */ - if (!c->stream->send_on_key || - ((c->got_key_frame + 1) >> c->stream->nb_streams)) - goto send_it; - } + do_switch_stream(c, i); + if (c->switch_feed_streams[i] >= 0) + c->switch_pending = 1; } - } else { - AVCodecContext *codec; - - send_it: - /* specific handling for RTP: we use several - output stream (one for each RTP - connection). XXX: need more abstract handling */ - if (c->is_packetized) { - AVStream *st; - /* compute send time and duration */ - st = c->fmt_in->streams[pkt.stream_index]; - c->cur_pts = av_rescale_q(pkt.dts, st->time_base, AV_TIME_BASE_Q); - if (st->start_time != AV_NOPTS_VALUE) - c->cur_pts -= av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q); - c->cur_frame_duration = av_rescale_q(pkt.duration, st->time_base, AV_TIME_BASE_Q); + } + for(i=0;istream->nb_streams;i++) { + if (c->feed_streams[i] == pkt.stream_index) { + AVStream *st = c->fmt_in->streams[source_index]; + pkt.stream_index = i; + if (pkt.flags & PKT_FLAG_KEY && + (st->codec->codec_type == CODEC_TYPE_VIDEO || + c->stream->nb_streams == 1)) + c->got_key_frame = 1; + if (!c->stream->send_on_key || c->got_key_frame) + goto send_it; + } + } + } else { + AVCodecContext *codec; + AVStream *ist, *ost; + send_it: + ist = c->fmt_in->streams[source_index]; + /* specific handling for RTP: we use several + output stream (one for each RTP + connection). XXX: need more abstract handling */ + if (c->is_packetized) { + /* compute send time and duration */ + c->cur_pts = av_rescale_q(pkt.dts, ist->time_base, AV_TIME_BASE_Q); + if (ist->start_time != AV_NOPTS_VALUE) + c->cur_pts -= av_rescale_q(ist->start_time, ist->time_base, AV_TIME_BASE_Q); + c->cur_frame_duration = av_rescale_q(pkt.duration, ist->time_base, AV_TIME_BASE_Q); #if 0 - printf("index=%d pts=%0.3f duration=%0.6f\n", - pkt.stream_index, - (double)c->cur_pts / - AV_TIME_BASE, - (double)c->cur_frame_duration / - AV_TIME_BASE); + printf("index=%d pts=%0.3f duration=%0.6f\n", + pkt.stream_index, + (double)c->cur_pts / + AV_TIME_BASE, + (double)c->cur_frame_duration / + AV_TIME_BASE); #endif - /* find RTP context */ - c->packet_stream_index = pkt.stream_index; - ctx = c->rtp_ctx[c->packet_stream_index]; - if(!ctx) { - av_free_packet(&pkt); - break; - } - codec = ctx->streams[0]->codec; - /* only one stream per RTP connection */ - pkt.stream_index = 0; - } else { - ctx = &c->fmt_ctx; - /* Fudge here */ - codec = ctx->streams[pkt.stream_index]->codec; - } - - if (c->is_packetized) { - int max_packet_size; - if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) - max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; - else - max_packet_size = url_get_max_packet_size(c->rtp_handles[c->packet_stream_index]); - ret = url_open_dyn_packet_buf(&ctx->pb, max_packet_size); - } else { - ret = url_open_dyn_buf(&ctx->pb); - } - if (ret < 0) { - /* XXX: potential leak */ - return -1; - } - if (pkt.dts != AV_NOPTS_VALUE) - pkt.dts = av_rescale_q(pkt.dts, - c->fmt_in->streams[pkt.stream_index]->time_base, - ctx->streams[pkt.stream_index]->time_base); - if (pkt.pts != AV_NOPTS_VALUE) - pkt.pts = av_rescale_q(pkt.pts, - c->fmt_in->streams[pkt.stream_index]->time_base, - ctx->streams[pkt.stream_index]->time_base); - if (av_write_frame(ctx, &pkt)) - c->state = HTTPSTATE_SEND_DATA_TRAILER; - - len = url_close_dyn_buf(ctx->pb, &c->pb_buffer); - c->cur_frame_bytes = len; - c->buffer_ptr = c->pb_buffer; - c->buffer_end = c->pb_buffer + len; - - codec->frame_number++; - if (len == 0) { + /* find RTP context */ + c->packet_stream_index = pkt.stream_index; + ctx = c->rtp_ctx[c->packet_stream_index]; + if(!ctx) { av_free_packet(&pkt); - goto redo; + break; } + codec = ctx->streams[0]->codec; + /* only one stream per RTP connection */ + pkt.stream_index = 0; + } else { + ctx = &c->fmt_ctx; + /* Fudge here */ + codec = ctx->streams[pkt.stream_index]->codec; + } + + if (c->is_packetized) { + int max_packet_size; + if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) + max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; + else + max_packet_size = url_get_max_packet_size(c->rtp_handles[c->packet_stream_index]); + ret = url_open_dyn_packet_buf(&ctx->pb, max_packet_size); + } else { + ret = url_open_dyn_buf(&ctx->pb); + } + if (ret < 0) { + /* XXX: potential leak */ + return -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) + pkt.pts = av_rescale_q(pkt.pts, ist->time_base, ost->time_base); + pkt.duration = av_rescale_q(pkt.duration, ist->time_base, ost->time_base); + if (av_write_frame(ctx, &pkt) < 0) { + http_log("Error writing frame to output\n"); + c->state = HTTPSTATE_SEND_DATA_TRAILER; + } + + len = url_close_dyn_buf(ctx->pb, &c->pb_buffer); + c->cur_frame_bytes = len; + c->buffer_ptr = c->pb_buffer; + c->buffer_end = c->pb_buffer + len; + + codec->frame_number++; + if (len == 0) { + av_free_packet(&pkt); + goto redo; } - av_free_packet(&pkt); } + av_free_packet(&pkt); } } break; @@ -2206,6 +2253,7 @@ static int http_prepare_data(HTTPContext *c) /* XXX: potential leak */ return -1; } + c->fmt_ctx.pb->is_streamed = 1; av_write_trailer(ctx); len = url_close_dyn_buf(ctx->pb, &c->pb_buffer); c->buffer_ptr = c->pb_buffer; @@ -2352,8 +2400,10 @@ static int http_start_receive_data(HTTPContext *c) /* open feed */ fd = open(c->stream->feed_filename, O_RDWR); - if (fd < 0) + if (fd < 0) { + http_log("Error opening feeder file: %s\n", strerror(errno)); return -1; + } c->feed_fd = fd; c->stream->feed_write_index = ffm_read_write_index(fd); @@ -2407,7 +2457,10 @@ static int http_receive_data(HTTPContext *c) // printf("writing pos=0x%"PRIx64" size=0x%"PRIx64"\n", feed->feed_write_index, feed->feed_size); /* XXX: use llseek or url_seek */ lseek(c->feed_fd, feed->feed_write_index, SEEK_SET); - write(c->feed_fd, c->buffer, FFM_PACKET_SIZE); + if (write(c->feed_fd, c->buffer, FFM_PACKET_SIZE) < 0) { + http_log("Error writing to feed file: %s\n", strerror(errno)); + goto fail; + } feed->feed_write_index += FFM_PACKET_SIZE; /* update file size */ @@ -2429,41 +2482,46 @@ static int http_receive_data(HTTPContext *c) } } else { /* We have a header in our hands that contains useful data */ - AVFormatContext s; + AVFormatContext *s = NULL; + ByteIOContext *pb; AVInputFormat *fmt_in; int i; - memset(&s, 0, sizeof(s)); - - url_open_buf(&s.pb, c->buffer, c->buffer_end - c->buffer, URL_RDONLY); - s.pb->is_streamed = 1; - /* use feed output format name to find corresponding input format */ fmt_in = av_find_input_format(feed->fmt->name); if (!fmt_in) goto fail; - if (fmt_in->priv_data_size > 0) { - s.priv_data = av_mallocz(fmt_in->priv_data_size); - if (!s.priv_data) - goto fail; - } else - s.priv_data = NULL; + url_open_buf(&pb, c->buffer, c->buffer_end - c->buffer, URL_RDONLY); + pb->is_streamed = 1; - if (fmt_in->read_header(&s, 0) < 0) { - av_freep(&s.priv_data); + if (av_open_input_stream(&s, pb, c->stream->feed_filename, fmt_in, NULL) < 0) { + av_free(pb); goto fail; } /* Now we have the actual streams */ - if (s.nb_streams != feed->nb_streams) { - av_freep(&s.priv_data); + if (s->nb_streams != feed->nb_streams) { + av_close_input_stream(s); + av_free(pb); goto fail; } - for (i = 0; i < s.nb_streams; i++) - memcpy(feed->streams[i]->codec, - s.streams[i]->codec, sizeof(AVCodecContext)); - av_freep(&s.priv_data); + + 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); } c->buffer_ptr = c->buffer; } @@ -2472,6 +2530,12 @@ static int http_receive_data(HTTPContext *c) fail: c->stream->feed_opened = 0; close(c->feed_fd); + /* wake up any waiting connections to stop waiting for feed */ + for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) { + if (c1->state == HTTPSTATE_WAIT_FEED && + c1->stream->feed == c->stream->feed) + c1->state = HTTPSTATE_SEND_DATA_TRAILER; + } return -1; } @@ -3080,7 +3144,6 @@ static int rtp_new_av_stream(HTTPContext *c, char *ipaddr; URLContext *h = NULL; uint8_t *dummy_buf; - char buf2[32]; int max_packet_size; /* now we can open the relevant output stream */ @@ -3141,9 +3204,8 @@ static int rtp_new_av_stream(HTTPContext *c, goto fail; } - http_log("%s:%d - - [%s] \"PLAY %s/streamid=%d %s\"\n", + http_log("%s:%d - - \"PLAY %s/streamid=%d %s\"\n", ipaddr, ntohs(dest_addr->sin_port), - ctime1(buf2), c->stream->filename, stream_index, c->protocol); /* normally, no packets should be output here, but the packet size may be checked */ @@ -3322,7 +3384,7 @@ static void build_file_streams(void) /* find all the AVStreams inside and reference them in 'stream' */ if (av_find_stream_info(infile) < 0) { - http_log("Could not find codec parameters from '%s'", + http_log("Could not find codec parameters from '%s'\n", stream->feed_filename); av_close_input_file(infile); goto fail; @@ -3387,7 +3449,7 @@ static void build_feed_streams(void) 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 { @@ -3398,28 +3460,28 @@ static void build_feed_streams(void) #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; } } @@ -3427,17 +3489,17 @@ static void build_feed_streams(void) 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); } @@ -3448,15 +3510,15 @@ static void build_feed_streams(void) 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); } /* only write the header of the ffm file */ if (url_fopen(&s->pb, feed->feed_filename, URL_WRONLY) < 0) { - fprintf(stderr, "Could not open output feed file '%s'\n", - feed->feed_filename); + http_log("Could not open output feed file '%s'\n", + feed->feed_filename); exit(1); } s->oformat = feed->fmt; @@ -3468,7 +3530,7 @@ static void build_feed_streams(void) } av_set_parameters(s, NULL); if (av_write_header(s) < 0) { - fprintf(stderr, "Container doesn't supports the required parameters\n"); + http_log("Container doesn't supports the required parameters\n"); exit(1); } /* XXX: need better api */ @@ -3478,7 +3540,7 @@ static void build_feed_streams(void) /* get feed size and write index */ fd = open(feed->feed_filename, O_RDONLY); if (fd < 0) { - fprintf(stderr, "Could not open output feed file '%s'\n", + http_log("Could not open output feed file '%s'\n", feed->feed_filename); exit(1); } @@ -3496,7 +3558,8 @@ static void build_feed_streams(void) /* compute the bandwidth used by each stream */ static void compute_bandwidth(void) { - int bandwidth, i; + unsigned bandwidth; + int i; FFStream *stream; for(stream = first_stream; stream != NULL; stream = stream->next) { @@ -3669,6 +3732,18 @@ static void load_module(const char *filename) } #endif +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_string2(avctx, opt, arg, 1); + if(!o) + return -1; + return 0; +} + static int parse_ffconfig(const char *filename) { FILE *f; @@ -3745,10 +3820,19 @@ static int parse_ffconfig(const char *filename) 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++; @@ -3756,16 +3840,18 @@ static int parse_ffconfig(const char *filename) nb_max_connections = val; } } else if (!strcasecmp(cmd, "MaxBandwidth")) { + int64_t llval; get_arg(arg, sizeof(arg), &p); - val = atoi(arg); - if (val < 10 || val > 100000) { + llval = atoll(arg); + if (llval < 10 || llval > 10000000) { fprintf(stderr, "%s:%d: Invalid MaxBandwidth: %s\n", filename, line_num, arg); errors++; } else - max_bandwidth = val; + max_bandwidth = llval; } else if (!strcasecmp(cmd, "CustomLog")) { - get_arg(logfilename, sizeof(logfilename), &p); + if (!ffserver_debug) + get_arg(logfilename, sizeof(logfilename), &p); } else if (!strcasecmp(cmd, "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) { @@ -3873,6 +3950,7 @@ static int parse_ffconfig(const char *filename) fprintf(stderr, "%s:%d: Already in a tag\n", filename, line_num); } else { + const AVClass *class; stream = av_mallocz(sizeof(FFStream)); *last_stream = stream; last_stream = &stream->next; @@ -3882,8 +3960,15 @@ static int parse_ffconfig(const char *filename) if (*q) *q = '\0'; stream->fmt = guess_stream_format(NULL, stream->filename, NULL); + /* fetch avclass so AVOption works + * FIXME try to use avcodec_get_context_defaults2 + * without changing defaults too much */ + avcodec_get_context_defaults(&video_enc); + class = video_enc.av_class; memset(&audio_enc, 0, sizeof(AVCodecContext)); memset(&video_enc, 0, sizeof(AVCodecContext)); + audio_enc.av_class = class; + video_enc.av_class = class; audio_id = CODEC_ID_NONE; video_id = CODEC_ID_NONE; if (stream->fmt) { @@ -3910,24 +3995,26 @@ static int parse_ffconfig(const char *filename) } } else if (!strcasecmp(cmd, "Format")) { get_arg(arg, sizeof(arg), &p); - 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) { + 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); @@ -4054,8 +4141,14 @@ static int parse_ffconfig(const char *filename) } else if (!strcasecmp(cmd, "VideoFrameRate")) { get_arg(arg, sizeof(arg), &p); if (stream) { - video_enc.time_base.num= DEFAULT_FRAME_RATE_BASE; - video_enc.time_base.den = (int)(strtod(arg, NULL) * video_enc.time_base.num); + AVRational frame_rate; + if (av_parse_video_frame_rate(&frame_rate, arg) < 0) { + fprintf(stderr, "Incorrect frame rate\n"); + errors++; + } else { + video_enc.time_base.num = frame_rate.den; + video_enc.time_base.den = frame_rate.num; + } } } else if (!strcasecmp(cmd, "VideoGopSize")) { get_arg(arg, sizeof(arg), &p); @@ -4072,6 +4165,24 @@ static int parse_ffconfig(const char *filename) video_enc.mb_decision = FF_MB_DECISION_BITS; //FIXME remove video_enc.flags |= CODEC_FLAG_4MV; } + } else if (!strcasecmp(cmd, "AVOptionVideo") || + !strcasecmp(cmd, "AVOptionAudio")) { + char arg2[1024]; + AVCodecContext *avctx; + int type; + get_arg(arg, sizeof(arg), &p); + get_arg(arg2, sizeof(arg2), &p); + if (!strcasecmp(cmd, "AVOptionVideo")) { + avctx = &video_enc; + type = AV_OPT_FLAG_VIDEO_PARAM; + } else { + avctx = &audio_enc; + type = AV_OPT_FLAG_AUDIO_PARAM; + } + if (ffserver_opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING_PARAM)) { + fprintf(stderr, "AVOption error: %s %s\n", arg, arg2); + errors++; + } } else if (!strcasecmp(cmd, "VideoTag")) { get_arg(arg, sizeof(arg), &p); if ((strlen(arg) == 4) && stream) @@ -4231,20 +4342,21 @@ static int parse_ffconfig(const char *filename) fprintf(stderr, "%s:%d: No corresponding for \n", filename, line_num); errors++; - } - if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm") != 0) { - if (audio_id != CODEC_ID_NONE) { - audio_enc.codec_type = CODEC_TYPE_AUDIO; - audio_enc.codec_id = audio_id; - add_codec(stream, &audio_enc); - } - if (video_id != CODEC_ID_NONE) { - video_enc.codec_type = CODEC_TYPE_VIDEO; - video_enc.codec_id = video_id; - add_codec(stream, &video_enc); + } else { + if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm") != 0) { + if (audio_id != CODEC_ID_NONE) { + audio_enc.codec_type = CODEC_TYPE_AUDIO; + audio_enc.codec_id = audio_id; + add_codec(stream, &audio_enc); + } + if (video_id != CODEC_ID_NONE) { + video_enc.codec_type = CODEC_TYPE_VIDEO; + video_enc.codec_id = video_id; + add_codec(stream, &video_enc); + } } + stream = NULL; } - stream = NULL; } else if (!strcasecmp(cmd, " for \n", filename, line_num); errors++; + } else { + if (!redirect->feed_filename[0]) { + fprintf(stderr, "%s:%d: No URL found for \n", + filename, line_num); + errors++; + } + redirect = NULL; } - if (!redirect->feed_filename[0]) { - fprintf(stderr, "%s:%d: No URL found for \n", - filename, line_num); - errors++; - } - redirect = NULL; } else if (!strcasecmp(cmd, "LoadModule")) { get_arg(arg, sizeof(arg), &p); #ifdef HAVE_DLOPEN @@ -4330,6 +4443,7 @@ static void opt_debug() { ffserver_debug = 1; ffserver_daemon = 0; + logfilename[0] = '-'; } static void opt_show_help(void) @@ -4357,7 +4471,7 @@ int main(int argc, char **argv) av_register_all(); - show_banner(program_name, program_birth_year); + show_banner(); config_filename = "/etc/ffserver.conf"; @@ -4367,25 +4481,10 @@ int main(int argc, char **argv) parse_options(argc, argv, options, NULL); - putenv("http_proxy"); /* Kill the http_proxy */ + unsetenv("http_proxy"); /* Kill the http_proxy */ av_init_random(av_gettime() + (getpid() << 16), &random_state); - /* address on which the server will handle HTTP connections */ - my_http_addr.sin_family = AF_INET; - my_http_addr.sin_port = htons (8080); - my_http_addr.sin_addr.s_addr = htonl (INADDR_ANY); - - /* address on which the server will handle RTSP connections */ - my_rtsp_addr.sin_family = AF_INET; - my_rtsp_addr.sin_port = htons (5454); - my_rtsp_addr.sin_addr.s_addr = htonl (INADDR_ANY); - - nb_max_connections = 5; - max_bandwidth = 1000; - first_stream = NULL; - logfilename[0] = '\0'; - memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = handle_child_exit; sigact.sa_flags = SA_NOCLDSTOP | SA_RESTART; @@ -4396,6 +4495,15 @@ int main(int argc, char **argv) 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(); @@ -4416,7 +4524,6 @@ int main(int argc, char **argv) } else { /* child */ setsid(); - chdir("/"); close(0); open("/dev/null", O_RDWR); if (strcmp(logfilename, "-") != 0) { @@ -4431,16 +4538,11 @@ int main(int argc, char **argv) /* signal init */ signal(SIGPIPE, SIG_IGN); - /* open log file if needed */ - if (logfilename[0] != '\0') { - if (!strcmp(logfilename, "-")) - logfile = stdout; - else - logfile = fopen(logfilename, "a"); - } + if (ffserver_daemon) + chdir("/"); if (http_server() < 0) { - fprintf(stderr, "Could not start server\n"); + http_log("Could not start server\n"); exit(1); }