[FFmpeg-devel] [PATCH 5/7] lmhttpd.c/ffserver.c/httpd.h/Makefile: Add libmicrohttpd httpd interface implementation.
Stephan Holljes
klaxa1337 at googlemail.com
Thu Aug 2 02:00:22 EEST 2018
Signed-off-by: Stephan Holljes <klaxa1337 at 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
More information about the ffmpeg-devel
mailing list