Signed-off-by: Stephan Holljes <klaxa1...@googlemail.com> --- Makefile | 8 +- ffserver.c | 2 +- httpd.h | 1 + lmhttpd.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 lmhttpd.c
diff --git a/Makefile b/Makefile index 18f3ac3..4714734 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ all: ffserver LAV_FLAGS = $(shell pkg-config --libs --cflags libavformat libavcodec libavutil) LUA_FLAGS = $(shell pkg-config --libs --cflags lua5.3) +MHD_FLAGS = $(shell pkg-config --libs --cflags libmicrohttpd) CFLAGS=-fsanitize=address -fsanitize=undefined # LAV_FLAGS = -L/usr/local/lib -lavcodec -lavformat -lavutil -ffserver: segment.o publisher.o fileserver.o lavfhttpd.o configreader.o ffserver.c - cc -g -Wall $(CFLAGS) $(LAV_FLAGS) $(LUA_FLAGS) -lpthread -o ffserver segment.o publisher.o fileserver.o lavfhttpd.o configreader.o ffserver.c +ffserver: segment.o publisher.o fileserver.o lavfhttpd.o lmhttpd.o configreader.o ffserver.c + cc -g -Wall $(CFLAGS) $(LAV_FLAGS) $(LUA_FLAGS) $(MHD_FLAGS) -lpthread -o ffserver segment.o publisher.o fileserver.o lavfhttpd.o lmhttpd.o configreader.o ffserver.c segment.o: segment.c segment.h cc -g -Wall $(CFLAGS) $(LAV_FLAGS) -lpthread -c segment.c @@ -19,6 +20,9 @@ fileserver.o: fileserver.c fileserver.h lavfhttpd.o: lavfhttpd.c httpd.h cc -g -Wall $(CFLAGS) $(LAV_FLAGS) -lpthread -c lavfhttpd.c +lmhttpd.o: lmhttpd.c httpd.h + cc -g -Wall $(CFLAGS) $(LAV_FLAGS) $(MHD_FLAGS) -lpthread -c lmhttpd.c + configreader.o: configreader.c configreader.h httpd.h cc -g -Wall $(CFLAGS) $(LAV_FLAGS) $(LUA_FLAGS) -c configreader.c clean: diff --git a/ffserver.c b/ffserver.c index 59c1b4d..de96c7b 100644 --- a/ffserver.c +++ b/ffserver.c @@ -797,7 +797,7 @@ void *run_server(void *arg) { ainfo.fs = fs; ainfo.ifmt_ctxs = ifmt_ctxs; ainfo.nb_pub = config->nb_streams; - ainfo.httpd = &lavfhttpd; + ainfo.httpd = &lmhttpd; ainfo.config = config; rinfos = av_mallocz_array(config->nb_streams, sizeof(struct ReadInfo)); diff --git a/httpd.h b/httpd.h index 1b2566e..e61211a 100644 --- a/httpd.h +++ b/httpd.h @@ -83,4 +83,5 @@ struct FFServerInfo { /** Current HTTPDInterface implementation using lavformat */ extern struct HTTPDInterface lavfhttpd; +extern struct HTTPDInterface lmhttpd; #endif diff --git a/lmhttpd.c b/lmhttpd.c new file mode 100644 index 0000000..b4fcdc6 --- /dev/null +++ b/lmhttpd.c @@ -0,0 +1,310 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LMHTTPD_H +#define LMHTTPD_H + +#define MAX_CLIENTS 16 +#define INITIAL_BUFSIZE 16 * 1024 * 1024 + +#include <sys/types.h> +#include <sys/select.h> +#include <sys/socket.h> + +#include <string.h> +#include <stdio.h> +#include <pthread.h> + +#include <microhttpd.h> + +#include <libavutil/log.h> +#include <libavutil/fifo.h> + +#include "httpd.h" + +struct MHDServer { + struct MHD_Daemon *daemon; + AVFifoBuffer *clients; +}; + +struct ConnectionInfo { + AVFifoBuffer *buffer; + pthread_mutex_t buffer_lock; + struct MHD_Connection *connection; + int close_connection; +}; + + +/** + * Helper callback that fills the libmicrohttpd-client buffer with data. + */ + +ssize_t helper_callback(void *cls, uint64_t pos, char *buf, size_t max) +{ + struct ConnectionInfo *cinfo = (struct ConnectionInfo*) cls; + pthread_mutex_lock(&cinfo->buffer_lock); + int buf_size = av_fifo_size(cinfo->buffer); + if (buf_size > 0) { + max = max > buf_size ? buf_size : max; + av_fifo_generic_read(cinfo->buffer, buf, max, NULL); + } else { + max = 0; + } + if (max == 0 && cinfo->close_connection) { + av_fifo_free(cinfo->buffer); + pthread_mutex_unlock(&cinfo->buffer_lock); + av_free(cinfo); + return MHD_CONTENT_READER_END_OF_STREAM; + } + pthread_mutex_unlock(&cinfo->buffer_lock); + return max; +} + +/** + * Free allocated callback params. Usually passed to MHD_create_response_from_callback, however it frees data too soon. + */ + +static void free_callback_param (void *cls) +{ + av_free(cls); +} + +/** + * Callback that handles incoming connections. + * + * Incoming connections are initialized and added to a queue of new clients. + */ + +static int answer_to_connection (void *cls, struct MHD_Connection *connection, + const char *url, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **con_cls) +{ + static int aptr; + struct MHD_Response *response; + struct HTTPClient *client; + int ret; + struct MHDServer *server = (struct MHDServer*) cls; + if (&aptr != *con_cls) + { + /* do never respond on first call (why? this is in every example... something about keeping track of con_cls?) */ + *con_cls = &aptr; + return MHD_YES; + } + *con_cls = NULL; + struct ConnectionInfo *cinfo = av_malloc(sizeof(struct ConnectionInfo)); + if (!cinfo) + return MHD_NO; + + pthread_mutex_init(&cinfo->buffer_lock, NULL); + if (MHD_set_connection_option(connection, MHD_CONNECTION_OPTION_TIMEOUT, (unsigned int) 10) != MHD_YES) + return MHD_NO; + + av_log(NULL, AV_LOG_ERROR, "Accepted new client %s %s %p\n", method, url, connection); + client = av_malloc(sizeof(struct HTTPClient)); + if (!client) + return MHD_NO; + // no locking needed, running on the same thread + + if (!strcmp("GET", method)) { + if (av_fifo_space(server->clients) > sizeof(struct HTTPClient*)) { + cinfo->buffer = av_fifo_alloc(INITIAL_BUFSIZE); + cinfo->connection = connection; + cinfo->close_connection = 0; + + if (!cinfo->buffer) + return MHD_NO; + *con_cls = cinfo; + client->resource = av_strdup(url); + client->method = av_strdup(method); + client->httpd_data = cinfo; + av_fifo_generic_write(server->clients, &client, sizeof(struct HTTPClient*), NULL); + } + response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, + 1024, + &helper_callback, + cinfo, + NULL); + + } else { + response = MHD_create_response_from_buffer (0, + (void *) "", + MHD_RESPMEM_MUST_COPY); + } + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + + return ret; +} + +/** + * Initialize the libmicrohttpd server with a config. + * + * Allocates and starts daemon. + */ + +int lmhttpd_init(void **server, struct HTTPDConfig config) { + struct MHDServer *server_p = av_malloc(sizeof(struct MHDServer)); + *server = NULL; + if (!server_p) { + av_log(NULL, AV_LOG_ERROR, "Could not allocate MHDServer struct\n"); + return -1; + } + server_p->clients = av_fifo_alloc_array(sizeof(struct HTTPClient), MAX_CLIENTS); + server_p->daemon = MHD_start_daemon (0, config.port, NULL, NULL, + &answer_to_connection, server_p, MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 10, MHD_OPTION_END); + if (!server_p->daemon || !server_p->clients) { + av_log(NULL, AV_LOG_ERROR, "Could not allocate MHD_Daemon\n"); + return -1; + } + *server = server_p; + return 0; +} + +/** + * Return a single new client that connected and run webserver write and read operations. + * + * This basically synchronizes the libmicrohttpd client API through a queue. + */ + +int lmhttpd_accept(void *server, struct HTTPClient **client, const char **valid_files) +{ + fd_set rs; + fd_set ws; + fd_set es; + struct timeval tv; + MHD_socket max; + //MHD_UNSIGNED_LONG_LONG mhd_timeout; + struct MHDServer *s = (struct MHDServer*) server; + + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + + if (MHD_get_fdset (s->daemon, &rs, &ws, &es, &max) != MHD_YES) + return HTTPD_OTHER_ERROR; + tv.tv_sec = 0; + tv.tv_usec = 500000; // 0.5 seconds + if (select (max + 1, &rs, &ws, &es, &tv) == -1) + return HTTPD_OTHER_ERROR; + + // run read and write operations + if (MHD_run_from_select(s->daemon, &rs, &ws, &es) != MHD_YES) + return HTTPD_OTHER_ERROR; + if(av_fifo_size(s->clients)) { + av_fifo_generic_read(s->clients, client, sizeof(struct HTTPClient*), NULL); + + return 0; + } + return HTTPD_LISTEN_TIMEOUT; +} + +/** + * Write data into a client buffer managed by libmicrohttpd. + */ + +int lmhttpd_write(void *server, struct HTTPClient *client, const unsigned char *buf, int size) +{ + struct ConnectionInfo* cinfo = (struct ConnectionInfo*) client->httpd_data; + int ret; + pthread_mutex_lock(&cinfo->buffer_lock); + if (!cinfo->close_connection && av_fifo_space(cinfo->buffer) >= size) { + ret = av_fifo_generic_write(cinfo->buffer, (void*) buf, size, NULL); + + } else { + ret = -1; + } + pthread_mutex_unlock(&cinfo->buffer_lock); + if (cinfo->close_connection) { + free_callback_param(cinfo); + ret = -1; + } + return ret; +} + +/** + * Unimplemented + */ + +int lmhttpd_read(void *server, struct HTTPClient *client, unsigned char *buf, int size) +{ + return 0; +} + +/** + * Close a connection by signaling to close it through the ConnectionInfo struct. + */ +void lmhttpd_close(void *server, struct HTTPClient *client) +{ + struct ConnectionInfo* cinfo = (struct ConnectionInfo*) client->httpd_data; + cinfo->close_connection = 1; + av_free(client->method); + av_free(client->resource); + av_free(client); +} + +/** + * Shutdown the libmicrohttpd daemon. + */ +void lmhttpd_shutdown(void *server) +{ + struct MHDServer *mhd_server = (struct MHDServer*) server; + fd_set rs; + fd_set ws; + fd_set es; + struct timeval tv; + MHD_socket max; + tv.tv_sec = 0; + tv.tv_usec = 500000; // 0.5 seconds + + MHD_quiesce_daemon(mhd_server->daemon); + + while (1) { + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + + if (MHD_get_fdset (mhd_server->daemon, &rs, &ws, &es, &max) != MHD_YES) + break; + + if (max == 0) + break; + + if (select (max + 1, &rs, &ws, &es, &tv) == -1) + break; + + if (MHD_run_from_select(mhd_server->daemon, &rs, &ws, &es) != MHD_YES) + break; + } + + MHD_stop_daemon(mhd_server->daemon); + av_fifo_free(mhd_server->clients); + av_free(mhd_server); +} + +struct HTTPDInterface lmhttpd = { + .init = lmhttpd_init, + .accept = lmhttpd_accept, + .write = lmhttpd_write, + .close = lmhttpd_close, + .shutdown = lmhttpd_shutdown, +}; + +#endif -- 2.1.4 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel