X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffserver.c;h=fdb504f84ad3b50ff85f4d66a0c6bfb7e2bf10a2;hb=f543f636a5d87c93cb73845668f09eb1874324b5;hp=40df9faeb840c4a4a30a51b7c5952483ae6b1c92;hpb=a9e534d56128971af55b44d38381461a56aa23a3;p=ffmpeg diff --git a/ffserver.c b/ffserver.c index 40df9faeb84..fdb504f84ad 100644 --- a/ffserver.c +++ b/ffserver.c @@ -35,7 +35,8 @@ #include "libavformat/rtpdec.h" #include "libavformat/rtsp.h" #include "libavutil/avstring.h" -#include "libavutil/random.h" +#include "libavutil/lfg.h" +#include "libavutil/random_seed.h" #include "libavutil/intreadwrite.h" #include "libavcodec/opt.h" #include @@ -236,6 +237,7 @@ typedef struct FFStream { int feed_opened; /* true if someone is writing to the feed */ int is_feed; /* true if it is a feed */ int readonly; /* True if writing is prohibited to the file */ + int truncate; /* True if feeder connection truncate the feed file */ int conns_served; int64_t bytes_served; int64_t feed_max_size; /* maximum storage size, zero means unlimited */ @@ -309,7 +311,7 @@ static uint64_t current_bandwidth; static int64_t cur_time; // Making this global saves on passing it around everywhere -static AVRandomState random_state; +static AVLFG random_state; static FILE *logfile = NULL; @@ -354,7 +356,7 @@ 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) + if (level > av_log_get_level()) return; if (print_prefix && avc) http_log("[%s @ %p]", avc->item_name(ptr), ptr); @@ -503,7 +505,7 @@ static void start_multicast(void) if (stream->is_multicast) { /* open the RTP connection */ snprintf(session_id, sizeof(session_id), "%08x%08x", - av_random(&random_state), av_random(&random_state)); + av_lfg_get(&random_state), av_lfg_get(&random_state)); /* choose a port if none given */ if (stream->multicast_port == 0) { @@ -699,6 +701,22 @@ static void start_wait_request(HTTPContext *c, int is_rtsp) } } +static void http_send_too_busy_reply(int fd) +{ + char buffer[300]; + int len = snprintf(buffer, sizeof(buffer), + "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 number of current connections is %d, and this exceeds the limit of %d.

\r\n" + "\r\n", + nb_connections, nb_max_connections); + send(fd, buffer, len, 0); +} + + static void new_connection(int server_fd, int is_rtsp) { struct sockaddr_in from_addr; @@ -714,10 +732,10 @@ static void new_connection(int server_fd, int is_rtsp) } ff_socket_nonblock(fd, 1); - /* XXX: should output a warning page when coming - close to the connection limit */ - if (nb_connections >= nb_max_connections) + if (nb_connections >= nb_max_connections) { + http_send_too_busy_reply(fd); goto fail; + } /* add a new connection */ c = av_mallocz(sizeof(HTTPContext)); @@ -1174,6 +1192,38 @@ static void get_word(char *buf, int buf_size, const char **pp) *pp = p; } +static void get_arg(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + int quote; + + p = *pp; + while (isspace(*p)) p++; + q = buf; + quote = 0; + if (*p == '\"' || *p == '\'') + quote = *p++; + for(;;) { + if (quote) { + if (*p == quote) + break; + } else { + if (isspace(*p)) + break; + } + if (*p == '\0') + break; + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (quote && *p == quote) + p++; + *pp = p; +} + static int validate_acl(FFStream *stream, HTTPContext *c) { enum IPAddressAction last_action = IP_DENY; @@ -1263,7 +1313,7 @@ static int http_parse_request(HTTPContext *c) av_strlcpy(c->protocol, protocol, sizeof(c->protocol)); if (ffserver_debug) - http_log("New connection: %s %s\n", cmd, url); + http_log("%s - - New connection: %s %s\n", inet_ntoa(c->from_addr.sin_addr), cmd, url); /* find the filename and the optional info string in the request */ p = strchr(url, '?'); @@ -1320,6 +1370,7 @@ static int http_parse_request(HTTPContext *c) } if (stream == NULL) { snprintf(msg, sizeof(msg), "File '%s' not found", url); + http_log("File '%s' not found\n", url); goto send_error; } @@ -1355,16 +1406,16 @@ static int http_parse_request(HTTPContext *c) } } + if (c->post == 0 && stream->stream_type == STREAM_TYPE_LIVE) + current_bandwidth += stream->bandwidth; + /* 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); + http_log("Feed '%s' already being received\n", stream->feed_filename); goto send_error; } - if (c->post == 0 && stream->stream_type == STREAM_TYPE_LIVE) - current_bandwidth += stream->bandwidth; - if (c->post == 0 && max_bandwidth < current_bandwidth) { c->http_error = 200; q = c->buffer; @@ -1599,7 +1650,7 @@ static int http_parse_request(HTTPContext *c) if (!strcmp(c->stream->fmt->name,"asf_stream")) { /* Need to allocate a client id */ - c->wmp_client_id = av_random(&random_state) & 0x7fffffff; + c->wmp_client_id = av_lfg_get(&random_state); q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "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); } @@ -1619,10 +1670,10 @@ static int http_parse_request(HTTPContext *c) "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); + "\n" + "404 Not Found\n" + "%s\n" + "\n", msg); /* prepare output buffer */ c->buffer_ptr = c->buffer; c->buffer_end = q; @@ -1667,15 +1718,15 @@ static void compute_status(HTTPContext *c) url_fprintf(pb, "Pragma: no-cache\r\n"); url_fprintf(pb, "\r\n"); - url_fprintf(pb, "%s Status\n", program_name); + 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, "

%s Status

\n", program_name); + url_fprintf(pb, "\n"); + url_fprintf(pb, "

%s Status

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

Available Streams

\n"); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
PathServed
Conns

bytes
FormatBit rate
kbits/s
Video
kbits/s

Codec
Audio
kbits/s

Codec
Feed\n"); + url_fprintf(pb, "

Available Streams

\n"); + url_fprintf(pb, "\n"); + url_fprintf(pb, "
PathServed
Conns

bytes
FormatBit rate
kbits/s
Video
kbits/s

Codec
Audio
kbits/s

Codec
Feed\n"); stream = first_stream; while (stream != NULL) { char sfilename[1024]; @@ -1703,7 +1754,7 @@ static void compute_status(HTTPContext *c) } } - url_fprintf(pb, "
%s ", + url_fprintf(pb, "
%s ", sfilename, stream->filename); url_fprintf(pb, " %d ", stream->conns_served); @@ -1744,26 +1795,26 @@ static void compute_status(HTTPContext *c) abort(); } } - url_fprintf(pb, " %s %d %d %s %s %d %s %s", + url_fprintf(pb, " %s %d %d %s %s %d %s %s", stream->fmt->name, stream->bandwidth, video_bit_rate / 1000, video_codec_name, video_codec_name_extra, audio_bit_rate / 1000, audio_codec_name, audio_codec_name_extra); if (stream->feed) - url_fprintf(pb, "%s", stream->feed->filename); + url_fprintf(pb, "%s", stream->feed->filename); else - url_fprintf(pb, "%s", stream->feed_filename); + url_fprintf(pb, "%s", stream->feed_filename); url_fprintf(pb, "\n"); } break; default: - url_fprintf(pb, " - - - - \n"); + url_fprintf(pb, " - - - - \n"); break; } } stream = stream->next; } - url_fprintf(pb, "
\n"); + url_fprintf(pb, "
\n"); stream = first_stream; while (stream != NULL) { @@ -1831,47 +1882,17 @@ static void compute_status(HTTPContext *c) stream = stream->next; } -#if 0 - { - float avg; - AVCodecContext *enc; - char buf[1024]; - - /* feed status */ - stream = first_feed; - while (stream != NULL) { - url_fprintf(pb, "

Feed '%s'

\n", stream->filename); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
ParametersFrame countSizeAvg bitrate (kbits/s)\n"); - for(i=0;inb_streams;i++) { - AVStream *st = stream->streams[i]; - FeedData *fdata = st->priv_data; - enc = st->codec; - - avcodec_string(buf, sizeof(buf), enc); - avg = fdata->avg_frame_size * (float)enc->rate * 8.0; - if (enc->codec->type == CODEC_TYPE_AUDIO && enc->frame_size > 0) - avg /= enc->frame_size; - url_fprintf(pb, "
%s %d %"PRId64" %0.1f\n", - buf, enc->frame_number, fdata->data_count, avg / 1000.0); - } - url_fprintf(pb, "
\n"); - stream = stream->next_feed; - } - } -#endif - /* connection status */ - url_fprintf(pb, "

Connection Status

\n"); + url_fprintf(pb, "

Connection Status

\n"); - url_fprintf(pb, "Number of connections: %d / %d
\n", + url_fprintf(pb, "Number of connections: %d / %d
\n", nb_connections, nb_max_connections); - url_fprintf(pb, "Bandwidth in use: %"PRIu64"k / %"PRIu64"k
\n", + url_fprintf(pb, "Bandwidth in use: %"PRIu64"k / %"PRIu64"k
\n", current_bandwidth, max_bandwidth); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
#FileIPProtoStateTarget bits/secActual bits/secBytes transferred\n"); + url_fprintf(pb, "\n"); + url_fprintf(pb, "
#FileIPProtoStateTarget bits/secActual bits/secBytes transferred\n"); c1 = first_http_ctx; i = 0; while (c1 != NULL) { @@ -1890,7 +1911,7 @@ static void compute_status(HTTPContext *c) i++; p = inet_ntoa(c1->from_addr.sin_addr); - url_fprintf(pb, "
%d%s%s%s%s%s", + url_fprintf(pb, "
%d%s%s%s%s%s", i, c1->stream ? c1->stream->filename : "", c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", @@ -1905,13 +1926,13 @@ static void compute_status(HTTPContext *c) url_fprintf(pb, "\n"); c1 = c1->next; } - url_fprintf(pb, "
\n"); + url_fprintf(pb, "
\n"); /* date */ ti = time(NULL); p = ctime(&ti); - url_fprintf(pb, "
Generated at %s", p); - url_fprintf(pb, "\n\n"); + url_fprintf(pb, "
Generated at %s", p); + url_fprintf(pb, "\n\n"); len = url_close_dyn_buf(pb, &c->pb_buffer); c->buffer_ptr = c->pb_buffer; @@ -1970,12 +1991,6 @@ static int open_input_stream(HTTPContext *c, const char *info) if (input_filename[0] == '\0') return -1; -#if 0 - { time_t when = stream_pos / 1000000; - http_log("Stream pos = %"PRId64", time=%s", stream_pos, ctime(&when)); - } -#endif - /* open stream */ if ((ret = av_open_input_file(&s, input_filename, c->stream->ifmt, buf_size, c->stream->ap_in)) < 0) { @@ -1984,7 +1999,11 @@ static int open_input_stream(HTTPContext *c, const char *info) } s->flags |= AVFMT_FLAG_GENPTS; c->fmt_in = s; - av_find_stream_info(c->fmt_in); + if (strcmp(s->iformat->name, "ffm") && av_find_stream_info(c->fmt_in) < 0) { + http_log("Could not find stream info '%s'\n", input_filename); + av_close_input_file(s); + return -1; + } /* open each parser */ for(i=0;inb_streams;i++) @@ -2043,14 +2062,10 @@ static int http_prepare_data(HTTPContext *c) switch(c->state) { case HTTPSTATE_SEND_DATA_HEADER: memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); - av_strlcpy(c->fmt_ctx.author, c->stream->author, - sizeof(c->fmt_ctx.author)); - av_strlcpy(c->fmt_ctx.comment, c->stream->comment, - sizeof(c->fmt_ctx.comment)); - av_strlcpy(c->fmt_ctx.copyright, c->stream->copyright, - sizeof(c->fmt_ctx.copyright)); - av_strlcpy(c->fmt_ctx.title, c->stream->title, - sizeof(c->fmt_ctx.title)); + av_metadata_set(&c->fmt_ctx.metadata, "author" ,c->stream->author); + av_metadata_set(&c->fmt_ctx.metadata, "comment" ,c->stream->comment); + av_metadata_set(&c->fmt_ctx.metadata, "copyright",c->stream->copyright); + av_metadata_set(&c->fmt_ctx.metadata, "title" ,c->stream->title); for(i=0;istream->nb_streams;i++) { AVStream *st; @@ -2183,14 +2198,6 @@ static int http_prepare_data(HTTPContext *c) 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); -#endif /* find RTP context */ c->packet_stream_index = pkt.stream_index; ctx = c->rtp_ctx[c->packet_stream_index]; @@ -2413,10 +2420,19 @@ static int http_start_receive_data(HTTPContext *c) } c->feed_fd = fd; - if ((c->stream->feed_write_index = ffm_read_write_index(fd)) < 0) { - http_log("Error reading write index from feed file: %s\n", strerror(errno)); - return -1; + if (c->stream->truncate) { + /* truncate feed file */ + ffm_write_write_index(c->feed_fd, FFM_PACKET_SIZE); + ftruncate(c->feed_fd, FFM_PACKET_SIZE); + http_log("Truncating feed file '%s'\n", c->stream->feed_filename); + } else { + if ((c->stream->feed_write_index = ffm_read_write_index(fd)) < 0) { + http_log("Error reading write index from feed file: %s\n", strerror(errno)); + return -1; + } } + + c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE); c->stream->feed_size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); @@ -2517,6 +2533,8 @@ static int http_receive_data(HTTPContext *c) if (s->nb_streams != feed->nb_streams) { av_close_input_stream(s); av_free(pb); + http_log("Feed '%s' stream number does not match registered feed\n", + c->stream->feed_filename); goto fail; } @@ -2721,11 +2739,8 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, if (avc == NULL) { return -1; } - if (stream->title[0] != 0) { - av_strlcpy(avc->title, stream->title, sizeof(avc->title)); - } else { - av_strlcpy(avc->title, "No Title", sizeof(avc->title)); - } + av_metadata_set(&avc->metadata, "title", + stream->title[0] ? stream->title : "No Title"); avc->nb_streams = stream->nb_streams; if (stream->is_multicast) { snprintf(avc->filename, 1024, "rtp://%s:%d?multicast=1?ttl=%d", @@ -2874,7 +2889,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, /* generate session id if needed */ if (h->session_id[0] == '\0') snprintf(h->session_id, sizeof(h->session_id), "%08x%08x", - av_random(&random_state), av_random(&random_state)); + av_lfg_get(&random_state), av_lfg_get(&random_state)); /* find rtp session, and create it if none found */ rtp_c = find_rtp_session(h->session_id); @@ -3013,14 +3028,6 @@ static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPMessageHeader *h) return; } -#if 0 - /* XXX: seek in stream */ - if (h->range_start != AV_NOPTS_VALUE) { - printf("range_start=%0.3f\n", (double)h->range_start / AV_TIME_BASE); - av_seek_frame(rtp_c->fmt_in, -1, h->range_start); - } -#endif - rtp_c->state = HTTPSTATE_SEND_DATA; /* now everything is OK, so we can send the connection parameters */ @@ -3387,9 +3394,10 @@ static void build_file_streams(void) stream->ap_in->mpeg2ts_compute_pcr = 1; } + http_log("Opening file '%s'\n", stream->feed_filename); if ((ret = av_open_input_file(&infile, stream->feed_filename, stream->ifmt, 0, stream->ap_in)) < 0) { - http_log("could not open %s: %d\n", stream->feed_filename, ret); + http_log("Could not open '%s': %d\n", stream->feed_filename, ret); /* remove stream (no need to spend more time on it) */ fail: remove_stream(stream); @@ -3558,7 +3566,7 @@ static void build_feed_streams(void) exit(1); } - feed->feed_write_index = ffm_read_write_index(fd); + feed->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE); feed->feed_size = lseek(fd, 0, SEEK_END); /* ensure that we do not wrap before the end of file */ if (feed->feed_max_size && feed->feed_max_size < feed->feed_size) @@ -3592,38 +3600,6 @@ static void compute_bandwidth(void) } } -static void get_arg(char *buf, int buf_size, const char **pp) -{ - const char *p; - char *q; - int quote; - - p = *pp; - while (isspace(*p)) p++; - q = buf; - quote = 0; - if (*p == '\"' || *p == '\'') - quote = *p++; - for(;;) { - if (quote) { - if (*p == quote) - break; - } else { - if (isspace(*p)) - break; - } - if (*p == '\0') - break; - if ((q - buf) < buf_size - 1) - *q++ = *p; - p++; - } - *q = '\0'; - if (quote && *p == quote) - p++; - *pp = p; -} - /* add a codec and set the default parameters */ static void add_codec(FFStream *stream, AVCodecContext *av) { @@ -3764,7 +3740,7 @@ static int parse_ffconfig(const char *filename) const char *p; int val, errors, line_num; FFStream **last_stream, *stream, *redirect; - FFStream **last_feed, *feed; + FFStream **last_feed, *feed, *s; AVCodecContext audio_enc, video_enc; enum CodecID audio_id, video_id; @@ -3872,17 +3848,19 @@ static int parse_ffconfig(const char *filename) filename, line_num); } else { feed = av_mallocz(sizeof(FFStream)); - /* add in stream list */ - *last_stream = feed; - last_stream = &feed->next; - /* add in feed list */ - *last_feed = feed; - last_feed = &feed->next_feed; - get_arg(feed->filename, sizeof(feed->filename), &p); q = strrchr(feed->filename, '>'); if (*q) *q = '\0'; + + for (s = first_feed; s; s = s->next) { + if (!strcmp(feed->filename, s->filename)) { + fprintf(stderr, "%s:%d: Feed '%s' already registered\n", + filename, line_num, s->filename); + errors++; + } + } + feed->fmt = guess_format("ffm", NULL, NULL); /* defaut feed file */ snprintf(feed->feed_filename, sizeof(feed->feed_filename), @@ -3890,6 +3868,13 @@ static int parse_ffconfig(const char *filename) feed->feed_max_size = 5 * 1024 * 1024; feed->is_feed = 1; feed->feed = feed; /* self feeding :-) */ + + /* add in stream list */ + *last_stream = feed; + last_stream = &feed->next; + /* add in feed list */ + *last_feed = feed; + last_feed = &feed->next_feed; } } else if (!strcasecmp(cmd, "Launch")) { if (feed) { @@ -3925,6 +3910,11 @@ static int parse_ffconfig(const char *filename) get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p); } else if (stream) get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p); + } else if (!strcasecmp(cmd, "Truncate")) { + if (feed) { + get_arg(arg, sizeof(arg), &p); + feed->truncate = strtod(arg, NULL); + } } else if (!strcasecmp(cmd, "FileMaxSize")) { if (feed) { char *p1; @@ -3945,6 +3935,11 @@ static int parse_ffconfig(const char *filename) break; } feed->feed_max_size = (int64_t)fsize; + if (feed->feed_max_size < FFM_PACKET_SIZE*4) { + fprintf(stderr, "%s:%d: Feed max file size is too small, " + "must be at least %d\n", filename, line_num, FFM_PACKET_SIZE*4); + errors++; + } } } else if (!strcasecmp(cmd, "")) { if (!feed) { @@ -3961,15 +3956,22 @@ static int parse_ffconfig(const char *filename) fprintf(stderr, "%s:%d: Already in a tag\n", filename, line_num); } else { + FFStream *s; const AVClass *class; stream = av_mallocz(sizeof(FFStream)); - *last_stream = stream; - last_stream = &stream->next; - get_arg(stream->filename, sizeof(stream->filename), &p); q = strrchr(stream->filename, '>'); if (*q) *q = '\0'; + + for (s = first_stream; s; s = s->next) { + if (!strcmp(stream->filename, s->filename)) { + fprintf(stderr, "%s:%d: Stream '%s' already registered\n", + filename, line_num, s->filename); + errors++; + } + } + stream->fmt = guess_stream_format(NULL, stream->filename, NULL); /* fetch avclass so AVOption works * FIXME try to use avcodec_get_context_defaults2 @@ -3986,6 +3988,9 @@ static int parse_ffconfig(const char *filename) audio_id = stream->fmt->audio_codec; video_id = stream->fmt->video_codec; } + + *last_stream = stream; + last_stream = &stream->next; } } else if (!strcasecmp(cmd, "Feed")) { get_arg(arg, sizeof(arg), &p); @@ -4416,7 +4421,6 @@ static int parse_ffconfig(const char *filename) } else { fprintf(stderr, "%s:%d: Incorrect keyword: '%s'\n", filename, line_num, cmd); - errors++; } } @@ -4459,7 +4463,7 @@ static void opt_debug(void) logfilename[0] = '-'; } -static void opt_show_help(void) +static void show_help(void) { printf("usage: ffserver [options]\n" "Hyper fast multi format Audio/Video streaming server\n"); @@ -4468,10 +4472,7 @@ static void opt_show_help(void) } static const OptionDef options[] = { - { "h", OPT_EXIT, {(void*)opt_show_help}, "show help" }, - { "version", OPT_EXIT, {(void*)show_version}, "show version" }, - { "L", OPT_EXIT, {(void*)show_license}, "show license" }, - { "formats", OPT_EXIT, {(void*)show_formats}, "show available formats, codecs, protocols, ..." }, +#include "cmdutils_common_opts.h" { "n", OPT_BOOL, {(void *)&no_launch }, "enable no-launch mode" }, { "d", 0, {(void*)opt_debug}, "enable debug mode" }, { "f", HAS_ARG | OPT_STRING, {(void*)&config_filename }, "use configfile instead of /etc/ffserver.conf", "configfile" }, @@ -4496,7 +4497,7 @@ int main(int argc, char **argv) unsetenv("http_proxy"); /* Kill the http_proxy */ - av_random_init(&random_state, av_gettime() + (getpid() << 16)); + av_lfg_init(&random_state, ff_random_get_seed()); memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = handle_child_exit;