X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffserver.c;h=c7c7a9f05368d818e6138155647484514a56acbc;hb=ed2cea42900c24d9ca74e6cbe8d9cce4b6362d1d;hp=73e61e5a53aac6c29d643797b9f092119c1235b8;hpb=14bea432f16d7c66f9099e427819028b6b4c3bdc;p=ffmpeg diff --git a/ffserver.c b/ffserver.c index 73e61e5a53a..c7c7a9f0536 100644 --- a/ffserver.c +++ b/ffserver.c @@ -17,7 +17,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define HAVE_AV_CONFIG_H -#include "common.h" #include "avformat.h" #include @@ -27,6 +26,7 @@ #include #include #include +#undef time //needed because HAVE_AV_CONFIG_H is defined on top #include #include #include @@ -34,7 +34,6 @@ #include #include #include -#include #include #ifdef CONFIG_HAVE_DLFCN #include @@ -53,13 +52,11 @@ enum HTTPState { HTTPSTATE_SEND_DATA_TRAILER, HTTPSTATE_RECEIVE_DATA, HTTPSTATE_WAIT_FEED, /* wait for data from the feed */ - HTTPSTATE_WAIT, /* wait before sending next packets */ - HTTPSTATE_WAIT_SHORT, /* short wait for short term - bandwidth limitation */ HTTPSTATE_READY, RTSPSTATE_WAIT_REQUEST, RTSPSTATE_SEND_REPLY, + RTSPSTATE_SEND_PACKET, }; const char *http_state[] = { @@ -71,12 +68,11 @@ const char *http_state[] = { "SEND_DATA_TRAILER", "RECEIVE_DATA", "WAIT_FEED", - "WAIT", - "WAIT_SHORT", "READY", "RTSP_WAIT_REQUEST", "RTSP_SEND_REPLY", + "RTSP_SEND_PACKET", }; #define IOBUFFER_INIT_SIZE 8192 @@ -113,7 +109,13 @@ typedef struct HTTPContext { AVFormatContext *fmt_in; long start_time; /* In milliseconds - this wraps fairly often */ int64_t first_pts; /* initial pts value */ - int pts_stream_index; /* stream we choose as clock reference */ + int64_t cur_pts; /* current pts value from the stream in us */ + int64_t cur_frame_duration; /* duration of the current frame in us */ + int cur_frame_bytes; /* output frame size, needed to compute + the time at which we send each + packet */ + int pts_stream_index; /* stream we choose as clock reference */ + int64_t cur_clock; /* current clock reference value in us */ /* output format handling */ struct FFStream *stream; /* -1 is invalid stream */ @@ -137,16 +139,18 @@ typedef struct HTTPContext { uint8_t *pb_buffer; /* XXX: use that in all the code */ ByteIOContext *pb; int seq; /* RTSP sequence number */ - + /* RTP state specific */ enum RTSPProtocol rtp_protocol; char session_id[32]; /* session id */ AVFormatContext *rtp_ctx[MAX_STREAMS]; + + /* RTP/UDP specific */ URLContext *rtp_handles[MAX_STREAMS]; - /* RTP short term bandwidth limitation */ - int packet_byte_count; - int packet_start_time_us; /* used for short durations (a few - seconds max) */ + + /* RTP/TCP specific */ + struct HTTPContext *rtsp_c; + uint8_t *packet_buffer, *packet_buffer_ptr, *packet_buffer_end; } HTTPContext; static AVFrame dummy_frame; @@ -177,6 +181,8 @@ typedef struct FFStream { char filename[1024]; /* stream filename */ struct FFStream *feed; /* feed we are using (can be null if coming from file) */ + AVFormatParameters *ap_in; /* input parameters */ + AVInputFormat *ifmt; /* if non NULL, force input format */ AVOutputFormat *fmt; IPAddressACL *acl; int nb_streams; @@ -208,6 +214,7 @@ typedef struct FFStream { /* feed specific */ 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 conns_served; int64_t bytes_served; int64_t feed_max_size; /* maximum storage size */ @@ -240,11 +247,11 @@ static void compute_stats(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); -static int compute_send_delay(HTTPContext *c); /* RTSP handling */ static int rtsp_parse_request(HTTPContext *c); static void rtsp_cmd_describe(HTTPContext *c, const char *url); +static void rtsp_cmd_options(HTTPContext *c, const char *url); static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPHeader *h); static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h); static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h); @@ -256,9 +263,11 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, /* RTP handling */ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, - FFStream *stream, const char *session_id); + FFStream *stream, const char *session_id, + enum RTSPProtocol rtp_protocol); static int rtp_new_av_stream(HTTPContext *c, - int stream_index, struct sockaddr_in *dest_addr); + int stream_index, struct sockaddr_in *dest_addr, + HTTPContext *rtsp_c); static const char *my_program_name; static const char *my_program_dir; @@ -286,7 +295,7 @@ static long gettime_ms(void) static FILE *logfile = NULL; -static void http_log(const char *fmt, ...) +static void __attribute__ ((format (printf, 1, 2))) http_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -474,7 +483,8 @@ static void start_multicast(void) dest_addr.sin_addr = stream->multicast_ip; dest_addr.sin_port = htons(stream->multicast_port); - rtp_c = rtp_new_connection(&dest_addr, stream, session_id); + rtp_c = rtp_new_connection(&dest_addr, stream, session_id, + RTSP_PROTOCOL_RTP_UDP_MULTICAST); if (!rtp_c) { continue; } @@ -484,14 +494,12 @@ static void start_multicast(void) continue; } - rtp_c->rtp_protocol = RTSP_PROTOCOL_RTP_UDP_MULTICAST; - /* open each RTP stream */ for(stream_index = 0; stream_index < stream->nb_streams; stream_index++) { dest_addr.sin_port = htons(stream->multicast_port + 2 * stream_index); - if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr) < 0) { + 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); exit(1); @@ -525,7 +533,6 @@ static int http_server(void) first_http_ctx = NULL; nb_connections = 0; - first_http_ctx = NULL; start_multicast(); @@ -548,6 +555,7 @@ static int http_server(void) switch(c->state) { case HTTPSTATE_SEND_HEADER: case RTSPSTATE_SEND_REPLY: + case RTSPSTATE_SEND_PACKET: c->poll_entry = poll_entry; poll_entry->fd = fd; poll_entry->events = POLLOUT; @@ -563,9 +571,12 @@ static int http_server(void) poll_entry->events = POLLOUT; poll_entry++; } else { - /* not strictly correct, but currently cannot add - more than one fd in poll entry */ - delay = 0; + /* when ffserver is doing the timing, we work by + looking at which packet need to be sent every + 10 ms */ + delay1 = 10; /* one tick wait XXX: 10 ms assumed */ + if (delay1 < delay) + delay = delay1; } break; case HTTPSTATE_WAIT_REQUEST: @@ -578,18 +589,6 @@ static int http_server(void) poll_entry->events = POLLIN;/* Maybe this will work */ poll_entry++; break; - case HTTPSTATE_WAIT: - c->poll_entry = NULL; - delay1 = compute_send_delay(c); - if (delay1 < delay) - delay = delay1; - break; - case HTTPSTATE_WAIT_SHORT: - c->poll_entry = NULL; - delay1 = 10; /* one tick wait XXX: 10 ms assumed */ - if (delay1 < delay) - delay = delay1; - break; default: c->poll_entry = NULL; break; @@ -601,7 +600,9 @@ static int http_server(void) second to handle timeouts */ do { ret = poll(poll_table, poll_entry - poll_table, delay); - } while (ret == -1); + if (ret < 0 && errno != EAGAIN && errno != EINTR) + return -1; + } while (ret <= 0); cur_time = gettime_ms(); @@ -671,8 +672,6 @@ static void new_connection(int server_fd, int is_rtsp) if (!c) goto fail; - c->next = first_http_ctx; - first_http_ctx = c; c->fd = fd; c->poll_entry = NULL; c->from_addr = from_addr; @@ -680,6 +679,9 @@ static void new_connection(int server_fd, int is_rtsp) c->buffer = av_malloc(c->buffer_size); if (!c->buffer) goto fail; + + c->next = first_http_ctx; + first_http_ctx = c; nb_connections++; start_wait_request(c, is_rtsp); @@ -713,6 +715,12 @@ static void close_connection(HTTPContext *c) } } + /* remove references, if any (XXX: do it faster) */ + for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) { + if (c1->rtsp_c == c) + c1->rtsp_c = NULL; + } + /* remove connection associated resources */ if (c->fd >= 0) close(c->fd); @@ -743,21 +751,26 @@ static void close_connection(HTTPContext *c) url_close(h); } } + + ctx = &c->fmt_ctx; if (!c->last_packet_sent) { - ctx = &c->fmt_ctx; if (ctx->oformat) { /* prepare header */ if (url_open_dyn_buf(&ctx->pb) >= 0) { av_write_trailer(ctx); - (void) url_close_dyn_buf(&ctx->pb, &c->pb_buffer); + url_close_dyn_buf(&ctx->pb, &c->pb_buffer); } } } + for(i=0; inb_streams; i++) + av_free(ctx->streams[i]) ; + if (c->stream) current_bandwidth -= c->stream->bandwidth; av_freep(&c->pb_buffer); + av_freep(&c->packet_buffer); av_free(c->buffer); av_free(c); nb_connections--; @@ -780,14 +793,15 @@ static int handle_connection(HTTPContext *c) if (!(c->poll_entry->revents & POLLIN)) return 0; /* read the data */ - len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); + read_loop: + len = read(c->fd, c->buffer_ptr, 1); if (len < 0) { if (errno != EAGAIN && errno != EINTR) return -1; } else if (len == 0) { return -1; } else { - /* search for end of request. XXX: not fully correct since garbage could come after the end */ + /* search for end of request. */ uint8_t *ptr; c->buffer_ptr += len; ptr = c->buffer_ptr; @@ -804,7 +818,7 @@ static int handle_connection(HTTPContext *c) } else if (ptr >= c->buffer_end) { /* request too long: cannot do anything */ return -1; - } + } else goto read_loop; } break; @@ -874,16 +888,6 @@ static int handle_connection(HTTPContext *c) /* nothing to do, we'll be waken up by incoming feed packets */ break; - case HTTPSTATE_WAIT: - /* if the delay expired, we can send new packets */ - if (compute_send_delay(c) <= 0) - c->state = HTTPSTATE_SEND_DATA; - break; - case HTTPSTATE_WAIT_SHORT: - /* just return back to send data */ - c->state = HTTPSTATE_SEND_DATA; - break; - case RTSPSTATE_SEND_REPLY: if (c->poll_entry->revents & (POLLERR | POLLHUP)) { av_freep(&c->pb_buffer); @@ -909,6 +913,31 @@ static int handle_connection(HTTPContext *c) } } break; + case RTSPSTATE_SEND_PACKET: + if (c->poll_entry->revents & (POLLERR | POLLHUP)) { + av_freep(&c->packet_buffer); + return -1; + } + /* no need to write if no events */ + if (!(c->poll_entry->revents & POLLOUT)) + return 0; + len = write(c->fd, c->packet_buffer_ptr, + c->packet_buffer_end - c->packet_buffer_ptr); + if (len < 0) { + if (errno != EAGAIN && errno != EINTR) { + /* error : close connection */ + av_freep(&c->packet_buffer); + return -1; + } + } else { + c->packet_buffer_ptr += len; + if (c->packet_buffer_ptr >= c->packet_buffer_end) { + /* all the buffer was sent : wait for a new request */ + av_freep(&c->packet_buffer); + c->state = RTSPSTATE_WAIT_REQUEST; + } + } + break; case HTTPSTATE_READY: /* nothing to do */ break; @@ -1227,7 +1256,7 @@ static int http_parse_request(HTTPContext *c) stream = stream->next; } if (stream == NULL) { - sprintf(msg, "File '%s' not found", url); + snprintf(msg, sizeof(msg), "File '%s' not found", url); goto send_error; } @@ -1238,13 +1267,13 @@ static int http_parse_request(HTTPContext *c) if (stream->stream_type == STREAM_TYPE_REDIRECT) { c->http_error = 301; q = c->buffer; - q += sprintf(q, "HTTP/1.0 301 Moved\r\n"); - q += sprintf(q, "Location: %s\r\n", stream->feed_filename); - q += sprintf(q, "Content-type: text/html\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "Moved\r\n"); - q += sprintf(q, "You should be redirected.\r\n", stream->feed_filename); - q += sprintf(q, "\r\n"); + 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"); /* prepare output buffer */ c->buffer_ptr = c->buffer; @@ -1270,14 +1299,14 @@ static int http_parse_request(HTTPContext *c) if (post == 0 && max_bandwidth < current_bandwidth) { c->http_error = 200; q = c->buffer; - q += sprintf(q, "HTTP/1.0 200 Server too busy\r\n"); - q += sprintf(q, "Content-type: text/html\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "Too busy\r\n"); - q += sprintf(q, "The server is too busy to serve your request at this time.

\r\n"); - q += sprintf(q, "The bandwidth being served (including your stream) is %dkbit/sec, and this exceeds the limit of %dkbit/sec\r\n", + 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 += sprintf(q, "\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n"); /* prepare output buffer */ c->buffer_ptr = c->buffer; @@ -1321,29 +1350,29 @@ static int http_parse_request(HTTPContext *c) q = c->buffer; switch(redir_type) { case REDIR_ASX: - q += sprintf(q, "HTTP/1.0 200 ASX Follows\r\n"); - q += sprintf(q, "Content-type: video/x-ms-asf\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "\r\n", + 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 += sprintf(q, "\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n"); break; case REDIR_RAM: - q += sprintf(q, "HTTP/1.0 200 RAM Follows\r\n"); - q += sprintf(q, "Content-type: audio/x-pn-realaudio\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "# Autogenerated by ffserver\r\n"); - q += sprintf(q, "http://%s/%s%s\r\n", + 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); break; case REDIR_ASF: - q += sprintf(q, "HTTP/1.0 200 ASF Redirect follows\r\n"); - q += sprintf(q, "Content-type: video/x-ms-asf\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "[Reference]\r\n"); - q += sprintf(q, "Ref1=http://%s/%s%s\r\n", + 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); break; case REDIR_RTSP: @@ -1354,11 +1383,11 @@ static int http_parse_request(HTTPContext *c) p = strrchr(hostname, ':'); if (p) *p = '\0'; - q += sprintf(q, "HTTP/1.0 200 RTSP Redirect follows\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 RTSP Redirect follows\r\n"); /* XXX: incorrect mime type ? */ - q += sprintf(q, "Content-type: application/x-rtsp\r\n"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "rtsp://%s:%d/%s\r\n", + 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); } @@ -1369,9 +1398,9 @@ static int http_parse_request(HTTPContext *c) int sdp_data_size, len; struct sockaddr_in my_addr; - q += sprintf(q, "HTTP/1.0 200 OK\r\n"); - q += sprintf(q, "Content-type: application/sdp\r\n"); - q += sprintf(q, "\r\n"); + 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"); len = sizeof(my_addr); getsockname(c->fd, (struct sockaddr *)&my_addr, &len); @@ -1402,7 +1431,7 @@ static int http_parse_request(HTTPContext *c) } } - sprintf(msg, "ASX/RAM file not handled"); + snprintf(msg, sizeof(msg), "ASX/RAM file not handled"); goto send_error; } @@ -1442,7 +1471,7 @@ static int http_parse_request(HTTPContext *c) if (eol) { if (eol[-1] == '\r') eol--; - http_log("%.*s\n", eol - logline, logline); + http_log("%.*s\n", (int) (eol - logline), logline); c->suppress_log = 1; } } @@ -1467,12 +1496,12 @@ static int http_parse_request(HTTPContext *c) } } - sprintf(msg, "POST command not handled"); + snprintf(msg, sizeof(msg), "POST command not handled"); c->stream = 0; goto send_error; } if (http_start_receive_data(c) < 0) { - sprintf(msg, "could not open feed"); + snprintf(msg, sizeof(msg), "could not open feed"); goto send_error; } c->http_error = 0; @@ -1491,17 +1520,17 @@ static int http_parse_request(HTTPContext *c) /* open input stream */ if (open_input_stream(c, info) < 0) { - sprintf(msg, "Input stream corresponding to '%s' not found", url); + snprintf(msg, sizeof(msg), "Input stream corresponding to '%s' not found", url); goto send_error; } /* prepare http header */ q = c->buffer; - q += sprintf(q, "HTTP/1.0 200 OK\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "HTTP/1.0 200 OK\r\n"); mime_type = c->stream->fmt->mime_type; if (!mime_type) mime_type = "application/x-octet_stream"; - q += sprintf(q, "Pragma: no-cache\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Pragma: no-cache\r\n"); /* for asf, we need extra headers */ if (!strcmp(c->stream->fmt->name,"asf_stream")) { @@ -1509,10 +1538,10 @@ static int http_parse_request(HTTPContext *c) c->wmp_client_id = random() & 0x7fffffff; - q += sprintf(q, "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); + 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); } - q += sprintf(q, "Content-Type: %s\r\n", mime_type); - q += sprintf(q, "\r\n"); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Content-Type: %s\r\n", mime_type); + q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "\r\n"); /* prepare output buffer */ c->http_error = 0; @@ -1523,13 +1552,13 @@ static int http_parse_request(HTTPContext *c) send_error: c->http_error = 404; q = c->buffer; - q += sprintf(q, "HTTP/1.0 404 Not Found\r\n"); - q += sprintf(q, "Content-type: %s\r\n", "text/html"); - q += sprintf(q, "\r\n"); - q += sprintf(q, "\n"); - q += sprintf(q, "404 Not Found\n"); - q += sprintf(q, "%s\n", msg); - q += sprintf(q, "\n"); + 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"); /* prepare output buffer */ c->buffer_ptr = c->buffer; @@ -1648,6 +1677,9 @@ static void compute_stats(HTTPContext *c) video_codec_name = codec->name; } break; + case CODEC_TYPE_DATA: + video_bit_rate += st->codec.bit_rate; + break; default: av_abort(); } @@ -1724,7 +1756,7 @@ static void compute_stats(HTTPContext *c) break; case CODEC_TYPE_VIDEO: type = "video"; - sprintf(parameters, "%dx%d, q=%d-%d, fps=%d", st->codec.width, st->codec.height, + snprintf(parameters, sizeof(parameters), "%dx%d, q=%d-%d, fps=%d", st->codec.width, st->codec.height, st->codec.qmin, st->codec.qmax, st->codec.frame_rate / st->codec.frame_rate_base); break; default: @@ -1887,7 +1919,8 @@ static int open_input_stream(HTTPContext *c, const char *info) #endif /* open stream */ - if (av_open_input_file(&s, input_filename, NULL, buf_size, NULL) < 0) { + if (av_open_input_file(&s, input_filename, c->stream->ifmt, + buf_size, c->stream->ap_in) < 0) { http_log("%s not found", input_filename); return -1; } @@ -1907,185 +1940,47 @@ static int open_input_stream(HTTPContext *c, const char *info) } } +#if 0 if (c->fmt_in->iformat->read_seek) { c->fmt_in->iformat->read_seek(c->fmt_in, stream_pos); } +#endif /* set the start time (needed for maxtime and RTP packet timing) */ c->start_time = cur_time; c->first_pts = AV_NOPTS_VALUE; return 0; } -/* currently desactivated because the new PTS handling is not - satisfactory yet */ -//#define AV_READ_FRAME -#ifdef AV_READ_FRAME - -/* XXX: generalize that in ffmpeg for picture/audio/data. Currently - the return packet MUST NOT be freed */ -int av_read_frame(AVFormatContext *s, AVPacket *pkt) +/* return the server clock (in us) */ +static int64_t get_server_clock(HTTPContext *c) { - AVStream *st; - int len, ret, old_nb_streams, i; - - /* see if remaining frames must be parsed */ - for(;;) { - if (s->cur_len > 0) { - st = s->streams[s->cur_pkt.stream_index]; - len = avcodec_parse_frame(&st->codec, &pkt->data, &pkt->size, - s->cur_ptr, s->cur_len); - if (len < 0) { - /* error: get next packet */ - s->cur_len = 0; - } else { - s->cur_ptr += len; - s->cur_len -= len; - if (pkt->size) { - /* init pts counter if not done */ - if (st->pts.den == 0) { - switch(st->codec.codec_type) { - case CODEC_TYPE_AUDIO: - st->pts_incr = (int64_t)s->pts_den; - av_frac_init(&st->pts, st->pts.val, 0, - (int64_t)s->pts_num * st->codec.sample_rate); - break; - case CODEC_TYPE_VIDEO: - st->pts_incr = (int64_t)s->pts_den * st->codec.frame_rate_base; - av_frac_init(&st->pts, st->pts.val, 0, - (int64_t)s->pts_num * st->codec.frame_rate); - break; - default: - av_abort(); - } - } - - /* a frame was read: return it */ - pkt->pts = st->pts.val; -#if 0 - printf("add pts=%Lx num=%Lx den=%Lx incr=%Lx\n", - st->pts.val, st->pts.num, st->pts.den, st->pts_incr); -#endif - switch(st->codec.codec_type) { - case CODEC_TYPE_AUDIO: - av_frac_add(&st->pts, st->pts_incr * st->codec.frame_size); - break; - case CODEC_TYPE_VIDEO: - av_frac_add(&st->pts, st->pts_incr); - break; - default: - av_abort(); - } - pkt->stream_index = s->cur_pkt.stream_index; - /* we use the codec indication because it is - more accurate than the demux flags */ - pkt->flags = 0; - if (st->codec.coded_frame->key_frame) - pkt->flags |= PKT_FLAG_KEY; - return 0; - } - } - } else { - /* free previous packet */ - av_free_packet(&s->cur_pkt); - - old_nb_streams = s->nb_streams; - ret = av_read_packet(s, &s->cur_pkt); - if (ret) - return ret; - /* open parsers for each new streams */ - for(i = old_nb_streams; i < s->nb_streams; i++) - open_parser(s, i); - st = s->streams[s->cur_pkt.stream_index]; - - /* update current pts (XXX: dts handling) from packet, or - use current pts if none given */ - if (s->cur_pkt.pts != AV_NOPTS_VALUE) { - av_frac_set(&st->pts, s->cur_pkt.pts); - } else { - s->cur_pkt.pts = st->pts.val; - } - if (!st->codec.codec) { - /* no codec opened: just return the raw packet */ - *pkt = s->cur_pkt; - - /* no codec opened: just update the pts by considering we - have one frame and free the packet */ - if (st->pts.den == 0) { - switch(st->codec.codec_type) { - case CODEC_TYPE_AUDIO: - st->pts_incr = (int64_t)s->pts_den * st->codec.frame_size; - av_frac_init(&st->pts, st->pts.val, 0, - (int64_t)s->pts_num * st->codec.sample_rate); - break; - case CODEC_TYPE_VIDEO: - st->pts_incr = (int64_t)s->pts_den * st->codec.frame_rate_base; - av_frac_init(&st->pts, st->pts.val, 0, - (int64_t)s->pts_num * st->codec.frame_rate); - break; - default: - av_abort(); - } - } - av_frac_add(&st->pts, st->pts_incr); - return 0; - } else { - s->cur_ptr = s->cur_pkt.data; - s->cur_len = s->cur_pkt.size; - } - } - } + /* compute current pts value from system time */ + return (int64_t)(cur_time - c->start_time) * 1000LL; } -static int compute_send_delay(HTTPContext *c) +/* return the estimated time at which the current packet must be sent + (in us) */ +static int64_t get_packet_send_clock(HTTPContext *c) { - int64_t cur_pts, delta_pts, next_pts; - int delay1; + int bytes_left, bytes_sent, frame_bytes; - /* compute current pts value from system time */ - cur_pts = ((int64_t)(cur_time - c->start_time) * c->fmt_in->pts_den) / - (c->fmt_in->pts_num * 1000LL); - /* compute the delta from the stream we choose as - main clock (we do that to avoid using explicit - buffers to do exact packet reordering for each - stream */ - /* XXX: really need to fix the number of streams */ - if (c->pts_stream_index >= c->fmt_in->nb_streams) - next_pts = cur_pts; - else - next_pts = c->fmt_in->streams[c->pts_stream_index]->pts.val; - delta_pts = next_pts - cur_pts; - if (delta_pts <= 0) { - delay1 = 0; + frame_bytes = c->cur_frame_bytes; + if (frame_bytes <= 0) { + return c->cur_pts; } else { - delay1 = (delta_pts * 1000 * c->fmt_in->pts_num) / c->fmt_in->pts_den; + bytes_left = c->buffer_end - c->buffer_ptr; + bytes_sent = frame_bytes - bytes_left; + return c->cur_pts + (c->cur_frame_duration * bytes_sent) / frame_bytes; } - return delay1; } -#else - -/* just fall backs */ -static int av_read_frame(AVFormatContext *s, AVPacket *pkt) -{ - return av_read_packet(s, pkt); -} - -static int compute_send_delay(HTTPContext *c) -{ - int datarate = 8 * get_longterm_datarate(&c->datarate, c->data_count); - if (datarate > c->stream->bandwidth * 2000) { - return 1000; - } - return 0; -} -#endif - static int http_prepare_data(HTTPContext *c) { int i, len, ret; AVFormatContext *ctx; + av_freep(&c->pb_buffer); switch(c->state) { case HTTPSTATE_SEND_DATA_HEADER: memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); @@ -2155,12 +2050,6 @@ static int http_prepare_data(HTTPContext *c) /* We have timed out */ c->state = HTTPSTATE_SEND_DATA_TRAILER; } else { - if (1 || c->is_packetized) { - if (compute_send_delay(c) > 0) { - c->state = HTTPSTATE_WAIT; - return 1; /* state changed */ - } - } redo: if (av_read_frame(c->fmt_in, &pkt) < 0) { if (c->stream->feed && c->stream->feed->feed_opened) { @@ -2183,9 +2072,10 @@ static int http_prepare_data(HTTPContext *c) } } else { /* update first pts if needed */ - if (c->first_pts == AV_NOPTS_VALUE) - c->first_pts = pkt.pts; - + if (c->first_pts == AV_NOPTS_VALUE) { + c->first_pts = pkt.dts; + c->start_time = cur_time; + } /* send it to the appropriate stream */ if (c->stream->feed) { /* if coming from a feed, select the right stream */ @@ -2229,8 +2119,28 @@ static int http_prepare_data(HTTPContext *c) 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 = pkt.dts; + if (st->start_time != AV_NOPTS_VALUE) + c->cur_pts -= st->start_time; + c->cur_frame_duration = pkt.duration; +#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]; + if(!ctx) { + av_free_packet(&pkt); + break; + } codec = &ctx->streams[0]->codec; /* only one stream per RTP connection */ pkt.stream_index = 0; @@ -2241,19 +2151,13 @@ static int http_prepare_data(HTTPContext *c) } codec->coded_frame->key_frame = ((pkt.flags & PKT_FLAG_KEY) != 0); - -#ifdef PJSG - if (codec->codec_type == CODEC_TYPE_AUDIO) { - codec->frame_size = (codec->sample_rate * pkt.duration + 500000) / 1000000; - /* printf("Calculated size %d, from sr %d, duration %d\n", codec->frame_size, codec->sample_rate, pkt.duration); */ - } -#endif - if (c->is_packetized) { - ret = url_open_dyn_packet_buf(&ctx->pb, - url_get_max_packet_size(c->rtp_handles[c->packet_stream_index])); - c->packet_byte_count = 0; - c->packet_start_time_us = av_gettime(); + 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); } @@ -2261,19 +2165,20 @@ static int http_prepare_data(HTTPContext *c) /* XXX: potential leak */ return -1; } - if (av_write_frame(ctx, pkt.stream_index, pkt.data, pkt.size)) { + 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) + goto redo; } -#ifndef AV_READ_FRAME av_free_packet(&pkt); -#endif } } } @@ -2304,76 +2209,128 @@ static int http_prepare_data(HTTPContext *c) #define SHORT_TERM_BANDWIDTH 8000000 /* should convert the format at the same time */ +/* send data starting at c->buffer_ptr to the output connection + (either UDP or TCP connection) */ static int http_send_data(HTTPContext *c) { - int len, ret, dt; - - while (c->buffer_ptr >= c->buffer_end) { - av_freep(&c->pb_buffer); - ret = http_prepare_data(c); - if (ret < 0) - return -1; - else if (ret == 0) { - continue; - } else { - /* state change requested */ - return 0; - } - } + int len, ret; - if (c->buffer_ptr < c->buffer_end) { - if (c->is_packetized) { - /* RTP/UDP data output */ - len = c->buffer_end - c->buffer_ptr; - if (len < 4) { - /* fail safe - should never happen */ - fail1: - c->buffer_ptr = c->buffer_end; - return 0; - } - len = (c->buffer_ptr[0] << 24) | - (c->buffer_ptr[1] << 16) | - (c->buffer_ptr[2] << 8) | - (c->buffer_ptr[3]); - if (len > (c->buffer_end - c->buffer_ptr)) - goto fail1; - - /* short term bandwidth limitation */ - dt = av_gettime() - c->packet_start_time_us; - if (dt < 1) - dt = 1; - - if ((c->packet_byte_count + len) * (int64_t)1000000 >= - (SHORT_TERM_BANDWIDTH / 8) * (int64_t)dt) { - /* bandwidth overflow : wait at most one tick and retry */ - c->state = HTTPSTATE_WAIT_SHORT; - return 0; + for(;;) { + if (c->buffer_ptr >= c->buffer_end) { + ret = http_prepare_data(c); + if (ret < 0) + return -1; + else if (ret != 0) { + /* state change requested */ + break; } - - c->buffer_ptr += 4; - url_write(c->rtp_handles[c->packet_stream_index], - c->buffer_ptr, len); - c->buffer_ptr += len; - c->packet_byte_count += len; } else { - /* TCP data output */ - len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); - if (len < 0) { - if (errno != EAGAIN && errno != EINTR) { - /* error : close connection */ - return -1; - } else { + if (c->is_packetized) { + /* RTP data output */ + len = c->buffer_end - c->buffer_ptr; + if (len < 4) { + /* fail safe - should never happen */ + fail1: + c->buffer_ptr = c->buffer_end; + return 0; + } + len = (c->buffer_ptr[0] << 24) | + (c->buffer_ptr[1] << 16) | + (c->buffer_ptr[2] << 8) | + (c->buffer_ptr[3]); + if (len > (c->buffer_end - c->buffer_ptr)) + goto fail1; + if ((get_packet_send_clock(c) - get_server_clock(c)) > 0) { + /* nothing to send yet: we can wait */ return 0; } + + c->data_count += len; + update_datarate(&c->datarate, c->data_count); + if (c->stream) + c->stream->bytes_served += len; + + if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) { + /* RTP packets are sent inside the RTSP TCP connection */ + ByteIOContext pb1, *pb = &pb1; + int interleaved_index, size; + uint8_t header[4]; + HTTPContext *rtsp_c; + + rtsp_c = c->rtsp_c; + /* if no RTSP connection left, error */ + if (!rtsp_c) + return -1; + /* if already sending something, then wait. */ + if (rtsp_c->state != RTSPSTATE_WAIT_REQUEST) { + break; + } + if (url_open_dyn_buf(pb) < 0) + goto fail1; + interleaved_index = c->packet_stream_index * 2; + /* RTCP packets are sent at odd indexes */ + if (c->buffer_ptr[1] == 200) + interleaved_index++; + /* write RTSP TCP header */ + header[0] = '$'; + header[1] = interleaved_index; + header[2] = len >> 8; + header[3] = len; + put_buffer(pb, header, 4); + /* write RTP packet data */ + c->buffer_ptr += 4; + put_buffer(pb, c->buffer_ptr, len); + size = url_close_dyn_buf(pb, &c->packet_buffer); + /* prepare asynchronous TCP sending */ + rtsp_c->packet_buffer_ptr = c->packet_buffer; + rtsp_c->packet_buffer_end = c->packet_buffer + size; + c->buffer_ptr += len; + + /* send everything we can NOW */ + len = write(rtsp_c->fd, rtsp_c->packet_buffer_ptr, + rtsp_c->packet_buffer_end - rtsp_c->packet_buffer_ptr); + if (len > 0) { + rtsp_c->packet_buffer_ptr += len; + } + if (rtsp_c->packet_buffer_ptr < rtsp_c->packet_buffer_end) { + /* if we could not send all the data, we will + send it later, so a new state is needed to + "lock" the RTSP TCP connection */ + rtsp_c->state = RTSPSTATE_SEND_PACKET; + break; + } else { + /* all data has been sent */ + av_freep(&c->packet_buffer); + } + } else { + /* send RTP packet directly in UDP */ + c->buffer_ptr += 4; + url_write(c->rtp_handles[c->packet_stream_index], + c->buffer_ptr, len); + c->buffer_ptr += len; + /* here we continue as we can send several packets per 10 ms slot */ + } } else { - c->buffer_ptr += len; + /* TCP data output */ + len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr); + if (len < 0) { + if (errno != EAGAIN && errno != EINTR) { + /* error : close connection */ + return -1; + } else { + return 0; + } + } else { + c->buffer_ptr += len; + } + c->data_count += len; + update_datarate(&c->datarate, c->data_count); + if (c->stream) + c->stream->bytes_served += len; + break; } } - c->data_count += len; - update_datarate(&c->datarate, c->data_count); - if (c->stream) - c->stream->bytes_served += len; - } + } /* for(;;) */ return 0; } @@ -2384,6 +2341,10 @@ static int http_start_receive_data(HTTPContext *c) if (c->stream->feed_opened) return -1; + /* Don't permit writing to this one */ + if (c->stream->readonly) + return -1; + /* open feed */ fd = open(c->stream->feed_filename, O_RDWR); if (fd < 0) @@ -2424,6 +2385,14 @@ static int http_receive_data(HTTPContext *c) } } + if (c->buffer_ptr - c->buffer >= 2 && c->data_count > FFM_PACKET_SIZE) { + if (c->buffer[0] != 'f' || + c->buffer[1] != 'm') { + http_log("Feed stream has become desynchronized -- disconnecting\n"); + goto fail; + } + } + if (c->buffer_ptr >= c->buffer_end) { FFStream *feed = c->stream; /* a packet has been received : write it in the store, except @@ -2609,6 +2578,8 @@ static int rtsp_parse_request(HTTPContext *c) if (!strcmp(cmd, "DESCRIBE")) { rtsp_cmd_describe(c, url); + } else if (!strcmp(cmd, "OPTIONS")) { + rtsp_cmd_options(c, url); } else if (!strcmp(cmd, "SETUP")) { rtsp_cmd_setup(c, url, header); } else if (!strcmp(cmd, "PLAY")) { @@ -2661,19 +2632,23 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, url_fprintf(pb, "c=IN IP4 %s\n", inet_ntoa(stream->multicast_ip)); } /* for each stream, we output the necessary info */ - private_payload_type = 96; + private_payload_type = RTP_PT_PRIVATE; for(i = 0; i < stream->nb_streams; i++) { st = stream->streams[i]; - switch(st->codec.codec_type) { - case CODEC_TYPE_AUDIO: - mediatype = "audio"; - break; - case CODEC_TYPE_VIDEO: + if (st->codec.codec_id == CODEC_ID_MPEG2TS) { mediatype = "video"; - break; - default: - mediatype = "application"; - break; + } else { + switch(st->codec.codec_type) { + case CODEC_TYPE_AUDIO: + mediatype = "audio"; + break; + case CODEC_TYPE_VIDEO: + mediatype = "video"; + break; + default: + mediatype = "application"; + break; + } } /* NOTE: the port indication is not correct in case of unicast. It is not an issue because RTSP gives it */ @@ -2687,7 +2662,7 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, } url_fprintf(pb, "m=%s %d RTP/AVP %d\n", mediatype, port, payload_type); - if (payload_type >= 96) { + if (payload_type >= RTP_PT_PRIVATE) { /* for private payload type, we need to give more info */ switch(st->codec.codec_id) { case CODEC_ID_MPEG4: @@ -2698,7 +2673,7 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, /* we must also add the mpeg4 header */ data = st->codec.extradata; if (data) { - url_fprintf(pb, "a=fmtp:%d config="); + url_fprintf(pb, "a=fmtp:%d config=", payload_type); for(j=0;jcodec.extradata_size;j++) { url_fprintf(pb, "%02x", data[j]); } @@ -2720,6 +2695,15 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, return -1; } +static void rtsp_cmd_options(HTTPContext *c, const char *url) +{ +// rtsp_reply_header(c, RTSP_STATUS_OK); + url_fprintf(c->pb, "RTSP/1.0 %d %s\r\n", RTSP_STATUS_OK, "OK"); + url_fprintf(c->pb, "CSeq: %d\r\n", c->seq); + url_fprintf(c->pb, "Public: %s\r\n", "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"); + url_fprintf(c->pb, "\r\n"); +} + static void rtsp_cmd_describe(HTTPContext *c, const char *url) { FFStream *stream; @@ -2730,7 +2714,7 @@ static void rtsp_cmd_describe(HTTPContext *c, const char *url) struct sockaddr_in my_addr; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; @@ -2751,7 +2735,6 @@ static void rtsp_cmd_describe(HTTPContext *c, const char *url) /* get the host IP */ len = sizeof(my_addr); getsockname(c->fd, (struct sockaddr *)&my_addr, &len); - content_length = prepare_sdp_description(stream, &content, my_addr.sin_addr); if (content_length < 0) { rtsp_reply_error(c, RTSP_STATUS_INTERNAL); @@ -2805,7 +2788,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPActionServerSetup setup; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; @@ -2846,7 +2829,18 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, /* find rtp session, and create it if none found */ rtp_c = find_rtp_session(h->session_id); if (!rtp_c) { - rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id); + /* always prefer UDP */ + th = find_transport(h, RTSP_PROTOCOL_RTP_UDP); + if (!th) { + th = find_transport(h, RTSP_PROTOCOL_RTP_TCP); + if (!th) { + rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); + return; + } + } + + rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id, + th->protocol); if (!rtp_c) { rtsp_reply_error(c, RTSP_STATUS_BANDWIDTH); return; @@ -2857,17 +2851,6 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, rtsp_reply_error(c, RTSP_STATUS_INTERNAL); return; } - - /* always prefer UDP */ - th = find_transport(h, RTSP_PROTOCOL_RTP_UDP); - if (!th) { - th = find_transport(h, RTSP_PROTOCOL_RTP_TCP); - if (!th) { - rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); - return; - } - } - rtp_c->rtp_protocol = th->protocol; } /* test if stream is OK (test needed because several SETUP needs @@ -2909,7 +2892,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, } /* setup stream */ - if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr) < 0) { + if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, c) < 0) { rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); return; } @@ -2952,19 +2935,28 @@ static HTTPContext *find_rtp_session_with_url(const char *url, HTTPContext *rtp_c; char path1[1024]; const char *path; + char buf[1024]; + int s; rtp_c = find_rtp_session(session_id); if (!rtp_c) return NULL; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; - if (strcmp(path, rtp_c->stream->filename) != 0) - return NULL; - return rtp_c; + if(!strcmp(path, rtp_c->stream->filename)) return rtp_c; + for(s=0; sstream->nb_streams; ++s) { + snprintf(buf, sizeof(buf), "%s/streamid=%d", + rtp_c->stream->filename, s); + if(!strncmp(path, buf, sizeof(buf))) { + // XXX: Should we reply with RTSP_STATUS_ONLY_AGGREGATE if nb_streams>1? + return rtp_c; + } + } + return NULL; } static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h) @@ -2984,6 +2976,14 @@ static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *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 */ @@ -3010,7 +3010,7 @@ static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h) } rtp_c->state = HTTPSTATE_READY; - + rtp_c->first_pts = AV_NOPTS_VALUE; /* now everything is OK, so we can send the connection parameters */ rtsp_reply_header(c, RTSP_STATUS_OK); /* session ID */ @@ -3049,10 +3049,12 @@ static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPHeader *h) /* RTP handling */ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, - FFStream *stream, const char *session_id) + FFStream *stream, const char *session_id, + enum RTSPProtocol rtp_protocol) { HTTPContext *c = NULL; - + const char *proto_str; + /* XXX: should output a warning page when coming close to the connection limit */ if (nb_connections >= nb_max_connections) @@ -3075,8 +3077,25 @@ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, pstrcpy(c->session_id, sizeof(c->session_id), session_id); c->state = HTTPSTATE_READY; c->is_packetized = 1; + c->rtp_protocol = rtp_protocol; + /* protocol is shown in statistics */ - pstrcpy(c->protocol, sizeof(c->protocol), "RTP"); + switch(c->rtp_protocol) { + case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + proto_str = "MCAST"; + break; + case RTSP_PROTOCOL_RTP_UDP: + proto_str = "UDP"; + break; + case RTSP_PROTOCOL_RTP_TCP: + proto_str = "TCP"; + break; + default: + proto_str = "???"; + break; + } + pstrcpy(c->protocol, sizeof(c->protocol), "RTP/"); + pstrcat(c->protocol, sizeof(c->protocol), proto_str); current_bandwidth += stream->bandwidth; @@ -3093,10 +3112,11 @@ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, } /* add a new RTP stream in an RTP connection (used in RTSP SETUP - command). if dest_addr is NULL, then TCP tunneling in RTSP is + command). If RTP/TCP protocol is used, TCP connection 'rtsp_c' is used. */ static int rtp_new_av_stream(HTTPContext *c, - int stream_index, struct sockaddr_in *dest_addr) + int stream_index, struct sockaddr_in *dest_addr, + HTTPContext *rtsp_c) { AVFormatContext *ctx; AVStream *st; @@ -3104,9 +3124,10 @@ static int rtp_new_av_stream(HTTPContext *c, URLContext *h; uint8_t *dummy_buf; char buf2[32]; + int max_packet_size; /* now we can open the relevant output stream */ - ctx = av_mallocz(sizeof(AVFormatContext)); + ctx = av_alloc_format_context(); if (!ctx) return -1; ctx->oformat = &rtp_mux; @@ -3126,9 +3147,13 @@ static int rtp_new_av_stream(HTTPContext *c, sizeof(AVStream)); } - if (dest_addr) { - /* build destination RTP address */ - ipaddr = inet_ntoa(dest_addr->sin_addr); + /* build destination RTP address */ + ipaddr = inet_ntoa(dest_addr->sin_addr); + + switch(c->rtp_protocol) { + case RTSP_PROTOCOL_RTP_UDP: + case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + /* RTP/UDP case */ /* XXX: also pass as parameter to function ? */ if (c->stream->is_multicast) { @@ -3147,18 +3172,24 @@ static int rtp_new_av_stream(HTTPContext *c, if (url_open(&h, ctx->filename, URL_WRONLY) < 0) goto fail; c->rtp_handles[stream_index] = h; - } else { + max_packet_size = url_get_max_packet_size(h); + break; + case RTSP_PROTOCOL_RTP_TCP: + /* RTP/TCP case */ + c->rtsp_c = rtsp_c; + max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; + break; + default: goto fail; } - http_log("%s:%d - - [%s] \"RTPSTART %s/streamid=%d\"\n", + http_log("%s:%d - - [%s] \"PLAY %s/streamid=%d %s\"\n", ipaddr, ntohs(dest_addr->sin_port), ctime1(buf2), - c->stream->filename, stream_index); + c->stream->filename, stream_index, c->protocol); /* normally, no packets should be output here, but the packet size may be checked */ - if (url_open_dyn_packet_buf(&ctx->pb, - url_get_max_packet_size(h)) < 0) { + if (url_open_dyn_packet_buf(&ctx->pb, max_packet_size) < 0) { /* XXX: close stream */ goto fail; } @@ -3190,6 +3221,7 @@ static AVStream *add_av_stream1(FFStream *stream, AVCodecContext *codec) fst->priv_data = av_mallocz(sizeof(FeedData)); memcpy(&fst->codec, codec, sizeof(AVCodecContext)); fst->codec.coded_frame = &dummy_frame; + fst->index = stream->nb_streams; stream->streams[stream->nb_streams++] = fst; return fst; } @@ -3262,20 +3294,21 @@ static void extract_mpeg4_header(AVFormatContext *infile) for(i=0;inb_streams;i++) { st = infile->streams[i]; if (st->codec.codec_id == CODEC_ID_MPEG4 && - st->codec.extradata == NULL) { + st->codec.extradata_size == 0) { mpeg4_count++; } } if (!mpeg4_count) return; - printf("MPEG4 without extra data: trying to find header\n"); + printf("MPEG4 without extra data: trying to find header in %s\n", infile->filename); while (mpeg4_count > 0) { if (av_read_packet(infile, &pkt) < 0) break; st = infile->streams[pkt.stream_index]; if (st->codec.codec_id == CODEC_ID_MPEG4 && - st->codec.extradata == NULL) { + st->codec.extradata_size == 0) { + av_freep(&st->codec.extradata); /* fill extradata with the header */ /* XXX: we make hard suppositions here ! */ p = pkt.data; @@ -3313,8 +3346,16 @@ static void build_file_streams(void) /* the stream comes from a file */ /* try to open the file */ /* open stream */ + stream->ap_in = av_mallocz(sizeof(AVFormatParameters)); + if (stream->fmt == &rtp_mux) { + /* specific case : if transport stream output to RTP, + we use a raw transport stream reader */ + stream->ap_in->mpeg2ts_raw = 1; + stream->ap_in->mpeg2ts_compute_pcr = 1; + } + if (av_open_input_file(&infile, stream->feed_filename, - NULL, 0, NULL) < 0) { + stream->ifmt, 0, stream->ap_in) < 0) { http_log("%s not found", stream->feed_filename); /* remove stream (no need to spend more time on it) */ fail: @@ -3390,7 +3431,8 @@ static void build_feed_streams(void) if (sf->index != ss->index || sf->id != ss->id) { - printf("Index & Id do not match for stream %d\n", i); + printf("Index & Id do not match for stream %d (%s)\n", + i, feed->feed_filename); matches = 0; } else { AVCodecContext *ccf, *ccs; @@ -3439,12 +3481,24 @@ static void build_feed_streams(void) printf("Deleting feed file '%s' as it appears to be corrupt\n", feed->feed_filename); } - if (!matches) + if (!matches) { + if (feed->readonly) { + printf("Unable to delete feed file '%s' as it is marked readonly\n", + feed->feed_filename); + exit(1); + } unlink(feed->feed_filename); + } } if (!url_exist(feed->feed_filename)) { AVFormatContext s1, *s = &s1; + if (feed->readonly) { + printf("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", @@ -3583,8 +3637,6 @@ static void add_codec(FFStream *stream, AVCodecContext *av) av->b_quant_factor = 1.25; if (!av->b_quant_offset) av->b_quant_offset = 1.25; - if (!av->rc_min_rate) - av->rc_min_rate = av->bit_rate / 2; if (!av->rc_max_rate) av->rc_max_rate = av->bit_rate * 2; @@ -3790,7 +3842,7 @@ static int parse_ffconfig(const char *filename) if (!argbuf[0]) break; - feed->child_argv[i] = av_malloc(strlen(argbuf + 1)); + feed->child_argv[i] = av_malloc(strlen(argbuf) + 1); strcpy(feed->child_argv[i], argbuf); } @@ -3799,6 +3851,13 @@ static int parse_ffconfig(const char *filename) snprintf(feed->child_argv[i], 256, "http://127.0.0.1:%d/%s", ntohs(my_http_addr.sin_port), feed->filename); } + } else if (!strcasecmp(cmd, "ReadOnlyFile")) { + if (feed) { + get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p); + feed->readonly = 1; + } else if (stream) { + get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p); + } } else if (!strcasecmp(cmd, "File")) { if (feed) { get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p); @@ -3908,6 +3967,12 @@ static int parse_ffconfig(const char *filename) audio_id = stream->fmt->audio_codec; video_id = stream->fmt->video_codec; } + } else if (!strcasecmp(cmd, "InputFormat")) { + stream->ifmt = av_find_input_format(arg); + if (!stream->ifmt) { + fprintf(stderr, "%s:%d: Unknown input format: %s\n", + filename, line_num, arg); + } } else if (!strcasecmp(cmd, "FaviconURL")) { if (stream && stream->stream_type == STREAM_TYPE_STATUS) { get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p); @@ -3997,6 +4062,11 @@ static int parse_ffconfig(const char *filename) errors++; } } + } else if (!strcasecmp(cmd, "VideoBufferSize")) { + if (stream) { + get_arg(arg, sizeof(arg), &p); + video_enc.rc_buffer_size = atoi(arg) * 1024; + } } else if (!strcasecmp(cmd, "VideoBitRateTolerance")) { if (stream) { get_arg(arg, sizeof(arg), &p); @@ -4035,7 +4105,12 @@ static int parse_ffconfig(const char *filename) } } else if (!strcasecmp(cmd, "VideoHighQuality")) { if (stream) { - video_enc.flags |= CODEC_FLAG_HQ; + video_enc.mb_decision = FF_MB_DECISION_BITS; + } + } else if (!strcasecmp(cmd, "Video4MotionVector")) { + if (stream) { + video_enc.mb_decision = FF_MB_DECISION_BITS; //FIXME remove + video_enc.flags |= CODEC_FLAG_4MV; } } else if (!strcasecmp(cmd, "VideoQDiff")) { get_arg(arg, sizeof(arg), &p); @@ -4287,23 +4362,27 @@ static void write_packet(FFCodec *ffenc, } #endif -static void help(void) +static void show_banner(void) +{ + printf("ffserver version " FFMPEG_VERSION ", Copyright (c) 2000-2003 Fabrice Bellard\n"); +} + +static void show_help(void) { - printf("ffserver version " FFMPEG_VERSION ", Copyright (c) 2000, 2001, 2002 Fabrice Bellard\n" - "usage: ffserver [-L] [-h] [-f configfile]\n" + show_banner(); + printf("usage: ffserver [-L] [-h] [-f configfile]\n" "Hyper fast multi format Audio/Video streaming server\n" "\n" - "-L : print the LICENCE\n" + "-L : print the LICENSE\n" "-h : this help\n" "-f configfile : use configfile instead of /etc/ffserver.conf\n" ); } -static void licence(void) +static void show_license(void) { + show_banner(); printf( - "ffserver version " FFMPEG_VERSION "\n" - "Copyright (c) 2000, 2001, 2002 Fabrice Bellard\n" "This library is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU Lesser General Public\n" "License as published by the Free Software Foundation; either\n" @@ -4366,11 +4445,11 @@ int main(int argc, char **argv) break; switch(c) { case 'L': - licence(); + show_license(); exit(1); case '?': case 'h': - help(); + show_help(); exit(1); case 'n': no_launch = 1;