|
|
@@ -95,7 +95,7 @@ typedef struct HTTPContext { |
|
|
int last_packet_sent; /* true if last data packet was sent */ |
|
|
int last_packet_sent; /* true if last data packet was sent */ |
|
|
int suppress_log; |
|
|
int suppress_log; |
|
|
int bandwidth; |
|
|
int bandwidth; |
|
|
time_t start_time; |
|
|
|
|
|
|
|
|
long start_time; /* In milliseconds - this wraps fairly often */ |
|
|
int wmp_client_id; |
|
|
int wmp_client_id; |
|
|
char protocol[16]; |
|
|
char protocol[16]; |
|
|
char method[16]; |
|
|
char method[16]; |
|
|
@@ -121,12 +121,16 @@ typedef struct FFStream { |
|
|
AVOutputFormat *fmt; |
|
|
AVOutputFormat *fmt; |
|
|
int nb_streams; |
|
|
int nb_streams; |
|
|
int prebuffer; /* Number of millseconds early to start */ |
|
|
int prebuffer; /* Number of millseconds early to start */ |
|
|
time_t max_time; |
|
|
|
|
|
|
|
|
long max_time; /* Number of milliseconds to run */ |
|
|
int send_on_key; |
|
|
int send_on_key; |
|
|
AVStream *streams[MAX_STREAMS]; |
|
|
AVStream *streams[MAX_STREAMS]; |
|
|
int feed_streams[MAX_STREAMS]; /* index of streams in the feed */ |
|
|
int feed_streams[MAX_STREAMS]; /* index of streams in the feed */ |
|
|
char feed_filename[1024]; /* file name of the feed storage, or |
|
|
char feed_filename[1024]; /* file name of the feed storage, or |
|
|
input file name for a stream */ |
|
|
input file name for a stream */ |
|
|
|
|
|
char author[512]; |
|
|
|
|
|
char title[512]; |
|
|
|
|
|
char copyright[512]; |
|
|
|
|
|
char comment[512]; |
|
|
pid_t pid; /* Of ffmpeg process */ |
|
|
pid_t pid; /* Of ffmpeg process */ |
|
|
char **child_argv; |
|
|
char **child_argv; |
|
|
struct FFStream *next; |
|
|
struct FFStream *next; |
|
|
@@ -154,7 +158,7 @@ FFStream *first_stream; /* contains all streams, including feeds */ |
|
|
|
|
|
|
|
|
static int handle_http(HTTPContext *c, long cur_time); |
|
|
static int handle_http(HTTPContext *c, long cur_time); |
|
|
static int http_parse_request(HTTPContext *c); |
|
|
static int http_parse_request(HTTPContext *c); |
|
|
static int http_send_data(HTTPContext *c); |
|
|
|
|
|
|
|
|
static int http_send_data(HTTPContext *c, long cur_time); |
|
|
static void compute_stats(HTTPContext *c); |
|
|
static void compute_stats(HTTPContext *c); |
|
|
static int open_input_stream(HTTPContext *c, const char *info); |
|
|
static int open_input_stream(HTTPContext *c, const char *info); |
|
|
static int http_start_receive_data(HTTPContext *c); |
|
|
static int http_start_receive_data(HTTPContext *c); |
|
|
@@ -162,6 +166,9 @@ static int http_receive_data(HTTPContext *c); |
|
|
|
|
|
|
|
|
static const char *my_program_name; |
|
|
static const char *my_program_name; |
|
|
|
|
|
|
|
|
|
|
|
static int ffserver_debug; |
|
|
|
|
|
static int no_launch; |
|
|
|
|
|
|
|
|
int nb_max_connections; |
|
|
int nb_max_connections; |
|
|
int nb_connections; |
|
|
int nb_connections; |
|
|
|
|
|
|
|
|
@@ -213,6 +220,9 @@ static void log_connection(HTTPContext *c) |
|
|
|
|
|
|
|
|
static void start_children(FFStream *feed) |
|
|
static void start_children(FFStream *feed) |
|
|
{ |
|
|
{ |
|
|
|
|
|
if (no_launch) |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
for (; feed; feed = feed->next) { |
|
|
for (; feed; feed = feed->next) { |
|
|
if (feed->child_argv) { |
|
|
if (feed->child_argv) { |
|
|
feed->pid = fork(); |
|
|
feed->pid = fork(); |
|
|
@@ -227,15 +237,17 @@ static void start_children(FFStream *feed) |
|
|
char *slash; |
|
|
char *slash; |
|
|
int i; |
|
|
int i; |
|
|
|
|
|
|
|
|
for (i = 0; i < 10; i++) { |
|
|
|
|
|
close(i); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (!ffserver_debug) { |
|
|
|
|
|
for (i = 0; i < 10; i++) { |
|
|
|
|
|
close(i); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
i = open("/dev/null", O_RDWR); |
|
|
|
|
|
if (i) |
|
|
|
|
|
dup2(i, 0); |
|
|
|
|
|
dup2(i, 1); |
|
|
|
|
|
dup2(i, 2); |
|
|
|
|
|
|
|
|
i = open("/dev/null", O_RDWR); |
|
|
|
|
|
if (i) |
|
|
|
|
|
dup2(i, 0); |
|
|
|
|
|
dup2(i, 1); |
|
|
|
|
|
dup2(i, 2); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
pstrcpy(pathname, sizeof(pathname), my_program_name); |
|
|
pstrcpy(pathname, sizeof(pathname), my_program_name); |
|
|
|
|
|
|
|
|
@@ -404,6 +416,7 @@ static int http_server(struct sockaddr_in my_addr) |
|
|
c->buffer_ptr = c->buffer; |
|
|
c->buffer_ptr = c->buffer; |
|
|
c->buffer_end = c->buffer + c->buffer_size; |
|
|
c->buffer_end = c->buffer + c->buffer_size; |
|
|
c->timeout = cur_time + REQUEST_TIMEOUT; |
|
|
c->timeout = cur_time + REQUEST_TIMEOUT; |
|
|
|
|
|
c->start_time = cur_time; |
|
|
nb_connections++; |
|
|
nb_connections++; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@@ -494,7 +507,7 @@ static int handle_http(HTTPContext *c, long cur_time) |
|
|
|
|
|
|
|
|
if (!(c->poll_entry->revents & POLLOUT)) |
|
|
if (!(c->poll_entry->revents & POLLOUT)) |
|
|
return 0; |
|
|
return 0; |
|
|
if (http_send_data(c) < 0) |
|
|
|
|
|
|
|
|
if (http_send_data(c, cur_time) < 0) |
|
|
return -1; |
|
|
return -1; |
|
|
break; |
|
|
break; |
|
|
case HTTPSTATE_RECEIVE_DATA: |
|
|
case HTTPSTATE_RECEIVE_DATA: |
|
|
@@ -1060,6 +1073,17 @@ static int http_parse_request(HTTPContext *c) |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int fmt_bytecount(char *q, INT64 count) |
|
|
|
|
|
{ |
|
|
|
|
|
static const char *suffix = " kMGTP"; |
|
|
|
|
|
const char *s; |
|
|
|
|
|
|
|
|
|
|
|
for (s = suffix; count >= 100000 && s[1]; count /= 1000, s++) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return sprintf(q, "%lld%c", count, *s); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
static void compute_stats(HTTPContext *c) |
|
|
static void compute_stats(HTTPContext *c) |
|
|
{ |
|
|
{ |
|
|
HTTPContext *c1; |
|
|
HTTPContext *c1; |
|
|
@@ -1093,7 +1117,7 @@ static void compute_stats(HTTPContext *c) |
|
|
/* format status */ |
|
|
/* format status */ |
|
|
q += sprintf(q, "<H2>Available Streams</H2>\n"); |
|
|
q += sprintf(q, "<H2>Available Streams</H2>\n"); |
|
|
q += sprintf(q, "<TABLE cellspacing=0 cellpadding=4>\n"); |
|
|
q += sprintf(q, "<TABLE cellspacing=0 cellpadding=4>\n"); |
|
|
q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>kbytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n"); |
|
|
|
|
|
|
|
|
q += sprintf(q, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>bytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n"); |
|
|
stream = first_stream; |
|
|
stream = first_stream; |
|
|
while (stream != NULL) { |
|
|
while (stream != NULL) { |
|
|
char sfilename[1024]; |
|
|
char sfilename[1024]; |
|
|
@@ -1112,8 +1136,9 @@ static void compute_stats(HTTPContext *c) |
|
|
|
|
|
|
|
|
q += sprintf(q, "<TR><TD><A HREF=\"/%s\">%s</A> ", |
|
|
q += sprintf(q, "<TR><TD><A HREF=\"/%s\">%s</A> ", |
|
|
sfilename, stream->filename); |
|
|
sfilename, stream->filename); |
|
|
q += sprintf(q, "<td align=right> %d <td align=right> %lld", |
|
|
|
|
|
stream->conns_served, stream->bytes_served / 1000); |
|
|
|
|
|
|
|
|
q += sprintf(q, "<td align=right> %d <td align=right> ", |
|
|
|
|
|
stream->conns_served); |
|
|
|
|
|
q += fmt_bytecount(q, stream->bytes_served); |
|
|
switch(stream->stream_type) { |
|
|
switch(stream->stream_type) { |
|
|
case STREAM_TYPE_LIVE: |
|
|
case STREAM_TYPE_LIVE: |
|
|
{ |
|
|
{ |
|
|
@@ -1286,13 +1311,14 @@ static void compute_stats(HTTPContext *c) |
|
|
|
|
|
|
|
|
i++; |
|
|
i++; |
|
|
p = inet_ntoa(c1->from_addr.sin_addr); |
|
|
p = inet_ntoa(c1->from_addr.sin_addr); |
|
|
q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <td align=right> %d <TD align=right> %Ld\n", |
|
|
|
|
|
|
|
|
q += sprintf(q, "<TR><TD><B>%d</B><TD>%s%s <TD> %s <TD> %s <td align=right> %d <TD align=right> ", |
|
|
i, c1->stream->filename, |
|
|
i, c1->stream->filename, |
|
|
c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", |
|
|
c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", |
|
|
p, |
|
|
p, |
|
|
http_state[c1->state], |
|
|
http_state[c1->state], |
|
|
bitrate / 1000, |
|
|
|
|
|
c1->data_count); |
|
|
|
|
|
|
|
|
bitrate / 1000); |
|
|
|
|
|
q += fmt_bytecount(q, c1->data_count); |
|
|
|
|
|
*q++ = '\n'; |
|
|
c1 = c1->next; |
|
|
c1 = c1->next; |
|
|
} |
|
|
} |
|
|
q += sprintf(q, "</TABLE>\n"); |
|
|
q += sprintf(q, "</TABLE>\n"); |
|
|
@@ -1388,13 +1414,18 @@ static int open_input_stream(HTTPContext *c, const char *info) |
|
|
return 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static int http_prepare_data(HTTPContext *c) |
|
|
|
|
|
|
|
|
static int http_prepare_data(HTTPContext *c, long cur_time) |
|
|
{ |
|
|
{ |
|
|
int i; |
|
|
int i; |
|
|
|
|
|
|
|
|
switch(c->state) { |
|
|
switch(c->state) { |
|
|
case HTTPSTATE_SEND_DATA_HEADER: |
|
|
case HTTPSTATE_SEND_DATA_HEADER: |
|
|
memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); |
|
|
memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); |
|
|
|
|
|
pstrcpy(c->fmt_ctx.author, sizeof(c->fmt_ctx.author), c->stream->author); |
|
|
|
|
|
pstrcpy(c->fmt_ctx.comment, sizeof(c->fmt_ctx.comment), c->stream->comment); |
|
|
|
|
|
pstrcpy(c->fmt_ctx.copyright, sizeof(c->fmt_ctx.copyright), c->stream->copyright); |
|
|
|
|
|
pstrcpy(c->fmt_ctx.title, sizeof(c->fmt_ctx.title), c->stream->title); |
|
|
|
|
|
|
|
|
if (c->stream->feed) { |
|
|
if (c->stream->feed) { |
|
|
/* open output stream by using specified codecs */ |
|
|
/* open output stream by using specified codecs */ |
|
|
c->fmt_ctx.oformat = c->stream->fmt; |
|
|
c->fmt_ctx.oformat = c->stream->fmt; |
|
|
@@ -1504,7 +1535,7 @@ static int http_prepare_data(HTTPContext *c) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (c->stream->max_time && |
|
|
if (c->stream->max_time && |
|
|
c->stream->max_time + c->start_time > time(0)) { |
|
|
|
|
|
|
|
|
c->stream->max_time + c->start_time - cur_time < 0) { |
|
|
/* We have timed out */ |
|
|
/* We have timed out */ |
|
|
c->state = HTTPSTATE_SEND_DATA_TRAILER; |
|
|
c->state = HTTPSTATE_SEND_DATA_TRAILER; |
|
|
} else if (av_read_packet(c->fmt_in, &pkt) < 0) { |
|
|
} else if (av_read_packet(c->fmt_in, &pkt) < 0) { |
|
|
@@ -1591,12 +1622,12 @@ static int http_prepare_data(HTTPContext *c) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* should convert the format at the same time */ |
|
|
/* should convert the format at the same time */ |
|
|
static int http_send_data(HTTPContext *c) |
|
|
|
|
|
|
|
|
static int http_send_data(HTTPContext *c, long cur_time) |
|
|
{ |
|
|
{ |
|
|
int len, ret; |
|
|
int len, ret; |
|
|
|
|
|
|
|
|
while (c->buffer_ptr >= c->buffer_end) { |
|
|
while (c->buffer_ptr >= c->buffer_end) { |
|
|
ret = http_prepare_data(c); |
|
|
|
|
|
|
|
|
ret = http_prepare_data(c, cur_time); |
|
|
if (ret < 0) |
|
|
if (ret < 0) |
|
|
return -1; |
|
|
return -1; |
|
|
else if (ret == 0) { |
|
|
else if (ret == 0) { |
|
|
@@ -2223,6 +2254,22 @@ int parse_ffconfig(const char *filename) |
|
|
filename, line_num); |
|
|
filename, line_num); |
|
|
errors++; |
|
|
errors++; |
|
|
} |
|
|
} |
|
|
|
|
|
} else if (!strcasecmp(cmd, "Author")) { |
|
|
|
|
|
if (stream) { |
|
|
|
|
|
get_arg(stream->author, sizeof(stream->author), &p); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (!strcasecmp(cmd, "Comment")) { |
|
|
|
|
|
if (stream) { |
|
|
|
|
|
get_arg(stream->comment, sizeof(stream->comment), &p); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (!strcasecmp(cmd, "Copyright")) { |
|
|
|
|
|
if (stream) { |
|
|
|
|
|
get_arg(stream->copyright, sizeof(stream->copyright), &p); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (!strcasecmp(cmd, "Title")) { |
|
|
|
|
|
if (stream) { |
|
|
|
|
|
get_arg(stream->title, sizeof(stream->title), &p); |
|
|
|
|
|
} |
|
|
} else if (!strcasecmp(cmd, "Preroll")) { |
|
|
} else if (!strcasecmp(cmd, "Preroll")) { |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
if (stream) { |
|
|
if (stream) { |
|
|
@@ -2251,7 +2298,7 @@ int parse_ffconfig(const char *filename) |
|
|
} else if (!strcasecmp(cmd, "MaxTime")) { |
|
|
} else if (!strcasecmp(cmd, "MaxTime")) { |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
if (stream) { |
|
|
if (stream) { |
|
|
stream->max_time = atoi(arg); |
|
|
|
|
|
|
|
|
stream->max_time = atoi(arg) * 1000; |
|
|
} |
|
|
} |
|
|
} else if (!strcasecmp(cmd, "AudioBitRate")) { |
|
|
} else if (!strcasecmp(cmd, "AudioBitRate")) { |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
get_arg(arg, sizeof(arg), &p); |
|
|
@@ -2470,7 +2517,7 @@ int main(int argc, char **argv) |
|
|
my_program_name = argv[0]; |
|
|
my_program_name = argv[0]; |
|
|
|
|
|
|
|
|
for(;;) { |
|
|
for(;;) { |
|
|
c = getopt_long_only(argc, argv, "Lh?f:", NULL, NULL); |
|
|
|
|
|
|
|
|
c = getopt_long_only(argc, argv, "ndLh?f:", NULL, NULL); |
|
|
if (c == -1) |
|
|
if (c == -1) |
|
|
break; |
|
|
break; |
|
|
switch(c) { |
|
|
switch(c) { |
|
|
@@ -2481,6 +2528,12 @@ int main(int argc, char **argv) |
|
|
case 'h': |
|
|
case 'h': |
|
|
help(); |
|
|
help(); |
|
|
exit(1); |
|
|
exit(1); |
|
|
|
|
|
case 'n': |
|
|
|
|
|
no_launch = 1; |
|
|
|
|
|
break; |
|
|
|
|
|
case 'd': |
|
|
|
|
|
ffserver_debug = 1; |
|
|
|
|
|
break; |
|
|
case 'f': |
|
|
case 'f': |
|
|
config_filename = optarg; |
|
|
config_filename = optarg; |
|
|
break; |
|
|
break; |
|
|
|