static int rtp_new_av_stream(HTTPContext *c,
int stream_index, struct sockaddr_in *dest_addr,
HTTPContext *rtsp_c);
+/* utils */
+static size_t htmlencode (const char *src, char **dest);
+static inline void cp_html_entity (char *buffer, const char *entity);
+static inline int check_codec_match(AVCodecContext *ccf, AVCodecContext *ccs,
+ int stream);
static const char *my_program_name;
static FILE *logfile = NULL;
-static void htmlstrip(char *s) {
- while (s && *s) {
- s += strspn(s, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,. ");
- if (*s)
- *s++ = '?';
+static inline void cp_html_entity (char *buffer, const char *entity) {
+ if (!buffer || !entity)
+ return;
+ while (*entity)
+ *buffer++ = *entity++;
+}
+
+/**
+ * Substitutes known conflicting chars on a text string with
+ * their corresponding HTML entities.
+ *
+ * Returns the number of bytes in the 'encoded' representation
+ * not including the terminating NUL.
+ */
+static size_t htmlencode (const char *src, char **dest) {
+ const char *amp = "&";
+ const char *lt = "<";
+ const char *gt = ">";
+ const char *start;
+ char *tmp;
+ size_t final_size = 0;
+
+ if (!src)
+ return 0;
+
+ start = src;
+
+ /* Compute needed dest size */
+ while (*src != '\0') {
+ switch(*src) {
+ case 38: /* & */
+ final_size += 5;
+ break;
+ case 60: /* < */
+ case 62: /* > */
+ final_size += 4;
+ break;
+ default:
+ final_size++;
+ }
+ src++;
+ }
+
+ src = start;
+ *dest = av_mallocz(final_size + 1);
+ if (!*dest)
+ return 0;
+
+ /* Build dest */
+ tmp = *dest;
+ while (*src != '\0') {
+ switch(*src) {
+ case 38: /* & */
+ cp_html_entity (tmp, amp);
+ tmp += 5;
+ break;
+ case 60: /* < */
+ cp_html_entity (tmp, lt);
+ tmp += 4;
+ break;
+ case 62: /* > */
+ cp_html_entity (tmp, gt);
+ tmp += 4;
+ break;
+ default:
+ *tmp = *src;
+ tmp += 1;
+ }
+ src++;
}
+ *tmp = '\0';
+
+ return final_size;
}
static int64_t ffm_read_write_index(int fd)
feed->pid = fork();
if (feed->pid < 0) {
- http_log("Unable to create children\n");
- exit(1);
+ http_log("Unable to create children: %s\n", strerror(errno));
+ av_free (pathname);
+ exit(EXIT_FAILURE);
}
if (feed->pid)
"HTTP/1.0 503 Server too busy\r\n"
"Content-type: text/html\r\n"
"\r\n"
+ "<!DOCTYPE html>\n"
"<html><head><title>Too busy</title></head><body>\r\n"
"<p>The server is too busy to serve your request at "
"this time.</p>\r\n"
char url[1024], *q;
char protocol[32];
char msg[1024];
+ char *encoded_msg = NULL;
const char *mime_type;
FFServerStream *stream;
int i;
"Location: %s\r\n"
"Content-type: text/html\r\n"
"\r\n"
+ "<!DOCTYPE html>\n"
"<html><head><title>Moved</title></head><body>\r\n"
"You should be <a href=\"%s\">redirected</a>.\r\n"
"</body></html>\r\n",
"HTTP/1.0 503 Server too busy\r\n"
"Content-type: text/html\r\n"
"\r\n"
+ "<!DOCTYPE html>\n"
"<html><head><title>Too busy</title></head><body>\r\n"
"<p>The server is too busy to serve your request at "
"this time.</p>\r\n"
send_error:
c->http_error = 404;
q = c->buffer;
- htmlstrip(msg);
+ if (!htmlencode(msg, &encoded_msg)) {
+ http_log("Could not encode filename '%s' as HTML\n", msg);
+ }
snprintf(q, c->buffer_size,
"HTTP/1.0 404 Not Found\r\n"
"Content-type: text/html\r\n"
"\r\n"
+ "<!DOCTYPE html>\n"
"<html>\n"
- "<head><title>404 Not Found</title></head>\n"
+ "<head>\n"
+ "<meta charset=\"UTF-8\">\n"
+ "<title>404 Not Found</title>\n"
+ "</head>\n"
"<body>%s</body>\n"
- "</html>\n", msg);
+ "</html>\n", encoded_msg? encoded_msg : "File not found");
q += strlen(q);
/* prepare output buffer */
c->buffer_ptr = c->buffer;
c->buffer_end = q;
c->state = HTTPSTATE_SEND_HEADER;
+ av_freep(&encoded_msg);
return 0;
send_status:
compute_status(c);
avio_printf(pb, "Pragma: no-cache\r\n");
avio_printf(pb, "\r\n");
+ avio_printf(pb, "<!DOCTYPE html>\n");
avio_printf(pb, "<html><head><title>%s Status</title>\n", program_name);
if (c->stream->feed_filename[0])
avio_printf(pb, "<link rel=\"shortcut icon\" href=\"%s\">\n",
/********************************************************************/
/* ffserver initialization */
+/* FIXME: This code should use avformat_new_stream() */
static AVStream *add_av_stream1(FFServerStream *stream,
AVCodecContext *codec, int copy)
{
fst->codec = codec;
fst->priv_data = av_mallocz(sizeof(FeedData));
+ fst->internal = av_mallocz(sizeof(*fst->internal));
fst->index = stream->nb_streams;
avpriv_set_pts_info(fst, 33, 1, 90000);
fst->sample_aspect_ratio = codec->sample_aspect_ratio;
/* compute the needed AVStream for each file */
static void build_file_streams(void)
{
- FFServerStream *stream, *stream_next;
+ FFServerStream *stream;
+ AVFormatContext *infile;
int i, ret;
/* gather all streams */
- for(stream = config.first_stream; stream; stream = stream_next) {
- AVFormatContext *infile = NULL;
- stream_next = stream->next;
- if (stream->stream_type == STREAM_TYPE_LIVE &&
- !stream->feed) {
- /* the stream comes from a file */
- /* try to open the file */
- /* open stream */
- if (stream->fmt && !strcmp(stream->fmt->name, "rtp")) {
- /* specific case : if transport stream output to RTP,
- * we use a raw transport stream reader */
- av_dict_set(&stream->in_opts, "mpeg2ts_compute_pcr", "1", 0);
- }
+ for(stream = config.first_stream; stream; stream = stream->next) {
+ infile = NULL;
- if (!stream->feed_filename[0]) {
- http_log("Unspecified feed file for stream '%s'\n",
- stream->filename);
- goto fail;
- }
+ if (stream->stream_type != STREAM_TYPE_LIVE || stream->feed)
+ continue;
- http_log("Opening feed file '%s' for stream '%s'\n",
- stream->feed_filename, stream->filename);
- ret = avformat_open_input(&infile, stream->feed_filename,
- stream->ifmt, &stream->in_opts);
- if (ret < 0) {
- http_log("Could not open '%s': %s\n", stream->feed_filename,
- av_err2str(ret));
- /* remove stream (no need to spend more time on it) */
- fail:
- remove_stream(stream);
- } else {
- /* find all the AVStreams inside and reference them in
- * 'stream' */
- if (avformat_find_stream_info(infile, NULL) < 0) {
- http_log("Could not find codec parameters from '%s'\n",
- stream->feed_filename);
- avformat_close_input(&infile);
- goto fail;
- }
- extract_mpeg4_header(infile);
+ /* the stream comes from a file */
+ /* try to open the file */
+ /* open stream */
+
+
+ /* specific case: if transport stream output to RTP,
+ * we use a raw transport stream reader */
+ if (stream->fmt && !strcmp(stream->fmt->name, "rtp"))
+ av_dict_set(&stream->in_opts, "mpeg2ts_compute_pcr", "1", 0);
- for(i=0;i<infile->nb_streams;i++)
- add_av_stream1(stream, infile->streams[i]->codec, 1);
+ if (!stream->feed_filename[0]) {
+ http_log("Unspecified feed file for stream '%s'\n",
+ stream->filename);
+ goto fail;
+ }
+ http_log("Opening feed file '%s' for stream '%s'\n",
+ stream->feed_filename, stream->filename);
+
+ ret = avformat_open_input(&infile, stream->feed_filename,
+ stream->ifmt, &stream->in_opts);
+ if (ret < 0) {
+ http_log("Could not open '%s': %s\n", stream->feed_filename,
+ av_err2str(ret));
+ /* remove stream (no need to spend more time on it) */
+ fail:
+ remove_stream(stream);
+ } else {
+ /* find all the AVStreams inside and reference them in
+ * 'stream' */
+ if (avformat_find_stream_info(infile, NULL) < 0) {
+ http_log("Could not find codec parameters from '%s'\n",
+ stream->feed_filename);
avformat_close_input(&infile);
+ goto fail;
}
+ extract_mpeg4_header(infile);
+
+ for(i=0;i<infile->nb_streams;i++)
+ add_av_stream1(stream, infile->streams[i]->codec, 1);
+
+ avformat_close_input(&infile);
}
}
}
+static inline
+int check_codec_match(AVCodecContext *ccf, AVCodecContext *ccs, int stream)
+{
+ int matches = 1;
+
+#define CHECK_CODEC(x) (ccf->x != ccs->x)
+ if (CHECK_CODEC(codec_id) || CHECK_CODEC(codec_type)) {
+ http_log("Codecs do not match for stream %d\n", stream);
+ matches = 0;
+ } else if (CHECK_CODEC(bit_rate) || CHECK_CODEC(flags)) {
+ http_log("Codec bitrates do not match for stream %d\n", stream);
+ matches = 0;
+ } else if (ccf->codec_type == AVMEDIA_TYPE_VIDEO) {
+ if (CHECK_CODEC(time_base.den) ||
+ CHECK_CODEC(time_base.num) ||
+ CHECK_CODEC(width) ||
+ CHECK_CODEC(height)) {
+ http_log("Codec width, height or framerate do not match for stream %d\n", stream);
+ matches = 0;
+ }
+ } else if (ccf->codec_type == AVMEDIA_TYPE_AUDIO) {
+ if (CHECK_CODEC(sample_rate) ||
+ CHECK_CODEC(channels) ||
+ CHECK_CODEC(frame_size)) {
+ http_log("Codec sample_rate, channels, frame_size do not match for stream %d\n", stream);
+ matches = 0;
+ }
+ } else {
+ http_log("Unknown codec type for stream %d\n", stream);
+ matches = 0;
+ }
+
+ return matches;
+}
+
/* compute the needed AVStream for each feed */
-static void build_feed_streams(void)
+static int build_feed_streams(void)
{
FFServerStream *stream, *feed;
- int i;
+ int i, fd;
/* gather all streams */
for(stream = config.first_stream; stream; stream = stream->next) {
if (stream->is_feed) {
for(i=0;i<stream->nb_streams;i++)
stream->feed_streams[i] = i;
- } else {
- /* we handle a stream coming from a feed */
- for(i=0;i<stream->nb_streams;i++)
- stream->feed_streams[i] = add_av_stream(feed,
- stream->streams[i]);
+ continue;
}
+ /* we handle a stream coming from a feed */
+ for(i=0;i<stream->nb_streams;i++)
+ stream->feed_streams[i] = add_av_stream(feed, stream->streams[i]);
}
/* create feed files if needed */
for(feed = config.first_feed; feed; feed = feed->next_feed) {
- int fd;
if (avio_check(feed->feed_filename, AVIO_FLAG_READ) > 0) {
- /* See if it matches */
AVFormatContext *s = NULL;
int matches = 0;
- if (avformat_open_input(&s, feed->feed_filename, NULL, NULL) >= 0) {
- /* set buffer size */
- int ret = ffio_set_buf_size(s->pb, FFM_PACKET_SIZE);
- if (ret < 0) {
- http_log("Failed to set buffer size\n");
- exit(1);
- }
+ /* See if it matches */
- /* Now see if it matches */
- if (s->nb_streams == feed->nb_streams) {
- matches = 1;
- for(i=0;i<s->nb_streams;i++) {
- AVStream *sf, *ss;
- sf = feed->streams[i];
- ss = s->streams[i];
-
- if (sf->index != ss->index ||
- sf->id != ss->id) {
- http_log("Index & Id do not match for stream %d (%s)\n",
- i, feed->feed_filename);
- matches = 0;
- } else {
- AVCodecContext *ccf, *ccs;
-
- ccf = sf->codec;
- ccs = ss->codec;
-#define CHECK_CODEC(x) (ccf->x != ccs->x)
+ if (avformat_open_input(&s, feed->feed_filename, NULL, NULL) < 0) {
+ http_log("Deleting feed file '%s' as it appears "
+ "to be corrupt\n",
+ feed->feed_filename);
+ goto drop;
+ }
- if (CHECK_CODEC(codec_id) || CHECK_CODEC(codec_type)) {
- http_log("Codecs do not match for stream %d\n", i);
- matches = 0;
- } else if (CHECK_CODEC(bit_rate) || CHECK_CODEC(flags)) {
- http_log("Codec bitrates do not match for stream %d\n", i);
- matches = 0;
- } else if (ccf->codec_type == AVMEDIA_TYPE_VIDEO) {
- if (CHECK_CODEC(time_base.den) ||
- CHECK_CODEC(time_base.num) ||
- CHECK_CODEC(width) ||
- CHECK_CODEC(height)) {
- http_log("Codec width, height and framerate do not match for stream %d\n", i);
- matches = 0;
- }
- } else if (ccf->codec_type == AVMEDIA_TYPE_AUDIO) {
- if (CHECK_CODEC(sample_rate) ||
- CHECK_CODEC(channels) ||
- CHECK_CODEC(frame_size)) {
- http_log("Codec sample_rate, channels, frame_size do not match for stream %d\n", i);
- matches = 0;
- }
- } else {
- http_log("Unknown codec type\n");
- matches = 0;
- }
- }
- if (!matches)
- break;
- }
- } else
- http_log("Deleting feed file '%s' as stream counts differ (%d != %d)\n",
- feed->feed_filename, s->nb_streams, feed->nb_streams);
+ /* set buffer size */
+ if (ffio_set_buf_size(s->pb, FFM_PACKET_SIZE) < 0) {
+ http_log("Failed to set buffer size\n");
+ avformat_close_input(&s);
+ goto bail;
+ }
+ /* Now see if it matches */
+ if (s->nb_streams != feed->nb_streams) {
+ http_log("Deleting feed file '%s' as stream counts "
+ "differ (%d != %d)\n",
+ feed->feed_filename, s->nb_streams, feed->nb_streams);
+ goto drop;
+ }
+
+ matches = 1;
+ for(i=0;i<s->nb_streams;i++) {
+ AVStream *sf, *ss;
+
+ sf = feed->streams[i];
+ ss = s->streams[i];
+
+ if (sf->index != ss->index || sf->id != ss->id) {
+ http_log("Index & Id do not match for stream %d (%s)\n",
+ i, feed->feed_filename);
+ matches = 0;
+ break;
+ }
+
+ matches = check_codec_match (sf->codec, ss->codec, i);
+ if (!matches)
+ break;
+ }
+
+drop:
+ if (s)
avformat_close_input(&s);
- } else
- http_log("Deleting feed file '%s' as it appears to be corrupt\n",
- feed->feed_filename);
if (!matches) {
if (feed->readonly) {
- http_log("Unable to delete feed file '%s' as it is marked readonly\n",
- feed->feed_filename);
- exit(1);
+ http_log("Unable to delete read-only feed file '%s'\n",
+ feed->feed_filename);
+ goto bail;
}
unlink(feed->feed_filename);
}
}
+
if (avio_check(feed->feed_filename, AVIO_FLAG_WRITE) <= 0) {
AVFormatContext *s = avformat_alloc_context();
if (!s) {
http_log("Failed to allocate context\n");
- exit(1);
+ goto bail;
}
if (feed->readonly) {
- http_log("Unable to create feed file '%s' as it is marked readonly\n",
- feed->feed_filename);
- exit(1);
+ http_log("Unable to create feed file '%s' as it is "
+ "marked readonly\n",
+ feed->feed_filename);
+ avformat_free_context(s);
+ goto bail;
}
/* only write the header of the ffm file */
if (avio_open(&s->pb, feed->feed_filename, AVIO_FLAG_WRITE) < 0) {
http_log("Could not open output feed file '%s'\n",
feed->feed_filename);
- exit(1);
+ avformat_free_context(s);
+ goto bail;
}
s->oformat = feed->fmt;
s->nb_streams = feed->nb_streams;
s->streams = feed->streams;
if (avformat_write_header(s, NULL) < 0) {
http_log("Container doesn't support the required parameters\n");
- exit(1);
+ avio_closep(&s->pb);
+ avformat_free_context(s);
+ goto bail;
}
/* XXX: need better API */
av_freep(&s->priv_data);
s->nb_streams = 0;
avformat_free_context(s);
}
+
/* get feed size and write index */
fd = open(feed->feed_filename, O_RDONLY);
if (fd < 0) {
http_log("Could not open output feed file '%s'\n",
feed->feed_filename);
- exit(1);
+ goto bail;
}
- feed->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE);
+ 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)
close(fd);
}
+ return 0;
+
+bail:
+ return -1;
}
/* compute the bandwidth used by each stream */
static void handle_child_exit(int sig)
{
pid_t pid;
- int status, uptime;
+ int status;
+ time_t uptime;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
FFServerStream *feed;
uptime = time(0) - feed->pid_start;
feed->pid = 0;
fprintf(stderr,
- "%s: Pid %"PRId64" exited with status %d after %d seconds\n",
- feed->filename, (int64_t) pid, status, uptime);
+ "%s: Pid %"PRId64" exited with status %d after %"PRId64" "
+ "seconds\n",
+ feed->filename, (int64_t) pid, status, (int64_t)uptime);
if (uptime < 30)
/* Turn off any more restarts */
int main(int argc, char **argv)
{
struct sigaction sigact = { { 0 } };
- int ret = 0;
+ int cfg_parsed;
+ int ret = EXIT_FAILURE;
+
config.filename = av_strdup("/etc/ffserver.conf");
sigact.sa_flags = SA_NOCLDSTOP | SA_RESTART;
sigaction(SIGCHLD, &sigact, 0);
- if ((ret = ffserver_parse_ffconfig(config.filename, &config)) < 0) {
+ if ((cfg_parsed = ffserver_parse_ffconfig(config.filename, &config)) < 0) {
fprintf(stderr, "Error reading configuration file '%s': %s\n",
- config.filename, av_err2str(ret));
- av_freep(&config.filename);
- exit(1);
+ config.filename, av_err2str(cfg_parsed));
+ goto bail;
}
- av_freep(&config.filename);
/* open log file if needed */
if (config.logfilename[0] != '\0') {
build_file_streams();
- build_feed_streams();
+ if (build_feed_streams() < 0) {
+ http_log("Could not setup feed streams\n");
+ goto bail;
+ }
compute_bandwidth();
if (http_server() < 0) {
http_log("Could not start server\n");
- exit(1);
+ goto bail;
}
- return 0;
+ ret=EXIT_SUCCESS;
+
+bail:
+ av_freep (&config.filename);
+ avformat_network_deinit();
+ return ret;
}