diff --git a/src/include/aes67/utils/rtsp-srv.h b/src/include/aes67/utils/rtsp-srv.h
new file mode 100644
index 0000000000000000000000000000000000000000..e60ff553c3c4f0361b0d3ca0c3315f00d1dcb5a0
--- /dev/null
+++ b/src/include/aes67/utils/rtsp-srv.h
@@ -0,0 +1,156 @@
+/**
+ * AES67 Framework
+ * Copyright (C) 2021  Philip Tschiemer, https://github.com/tschiemer/aes67
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AES67_RTSP_UTILS_SRV_H
+#define AES67_RTSP_UTILS_SRV_H
+
+#include "aes67/arch.h"
+#include "aes67/net.h"
+#include "aes67/rtsp.h"
+
+#include <stdbool.h>
+#include <netinet/in.h>
+
+#ifndef AES67_RTSP_SRV_MAXURILEN
+#define AES67_RTSP_SRV_MAXURILEN 256
+#endif
+
+#ifndef AES67_RTSPSRV_LISTEN_BACKLOG
+#define AES67_RTSPSRV_LISTEN_BACKLOG 10
+#endif
+
+#ifndef AES67_RTSP_SRV_RXBUFSIZE
+#define AES67_RTSP_SRV_RXBUFSIZE 1500
+#endif
+
+#ifndef AES67_RTSP_SRV_TXBUFSIZE
+#define AES67_RTSP_SRV_TXBUFSIZE AES67_RTSP_SRV_RXBUFSIZE
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum aes67_rtsp_srv_state {
+    aes67_rtsp_srv_state_init = 0,
+    aes67_rtsp_srv_state_listening,
+    aes67_rtsp_srv_state_receiving,
+    aes67_rtsp_srv_state_sending
+};
+
+enum aes67_rtsp_srv_proto {
+    aes67_rtsp_srv_proto_undefined = 0,
+    aes67_rtsp_srv_proto_rtsp,
+    aes67_rtsp_srv_proto_http
+};
+
+enum aes67_rtsp_srv_method {
+    aes67_rtsp_srv_method_options   = 1,
+    aes67_rtsp_srv_method_describe  = 2,
+    aes67_rtsp_srv_method_get       = 4,
+    aes67_rtsp_srv_method_post      = 8,
+    aes67_rtsp_srv_method_delete    = 16,
+    aes67_rtsp_srv_method_put       = 32,
+};
+
+struct aes67_rtsp_srv_http_response {
+    u16_t status_code;
+    u16_t has_more;
+    u16_t datalen;
+    u8_t * data;
+};
+
+
+struct aes67_rtsp_srv_resource {
+    struct aes67_rtsp_srv_resource * next;
+
+    u8_t urilen;
+    char uri[AES67_RTSP_SRV_MAXURILEN];
+
+    void * sdpref;
+};
+
+struct aes67_rtsp_srv {
+
+    enum aes67_rtsp_srv_state state;
+
+    bool http_enabled;
+    void * user_data;
+
+    struct sockaddr_in listen_addr;
+    int listen_sockfd;
+
+    struct sockaddr_in client_addr;
+    int client_sockfd;
+
+    struct aes67_rtsp_srv_resource * first_res;
+
+
+    struct {
+
+        u8_t data[AES67_RTSP_SRV_RXBUFSIZE];
+        u16_t data_len;
+
+        // helper
+        u8_t * line;
+        u16_t llen;
+        int CR;
+
+        enum aes67_rtsp_srv_proto proto;
+        struct {
+            u16_t major;
+            u16_t minor;
+        } version;
+        enum aes67_rtsp_srv_method method;
+//    char * uri;
+//    u8_t urilen;
+        u16_t header_len;
+        u16_t content_length;
+
+        u16_t cseq;
+
+    } req; // request
+
+    struct {
+        u16_t status_code;
+        u16_t sent;
+        u16_t len;
+        u8_t data[AES67_RTSP_SRV_TXBUFSIZE]
+    } res; // response
+};
+
+void aes67_rtsp_srv_init(struct aes67_rtsp_srv * srv, bool http_enabled, void * user_data);
+void aes67_rtsp_srv_deinit(struct aes67_rtsp_srv * srv);
+
+int aes67_rtsp_srv_start(struct aes67_rtsp_srv * srv, const enum aes67_net_ipver ipver, const u8_t *ip, u16_t port);
+void aes67_rtsp_srv_stop(struct aes67_rtsp_srv * srv);
+
+void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv);
+
+void aes67_rtsp_srv_sdp_getter(struct aes67_rtsp_srv * srv, void * sdpref, u8_t * buf, u16_t * len, u16_t maxlen);
+void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, const char * uri, const u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, void * response_data);
+
+struct aes67_rtsp_srv_resource * aes67_rtsp_srv_sdp_add(struct aes67_rtsp_srv * srv, const char * uri, const u8_t urilen, const void * sdpref);
+void aes67_rtsp_srv_sdp_remove(struct aes67_rtsp_srv * srv, void * sdpref);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //AES67_RTSP_UTILS_SRV_H
diff --git a/src/utils/rav-publish/CMakeLists.txt b/src/utils/rav-publish/CMakeLists.txt
index 68cbb89ebaf69a70e5812e07f029f096178cf1d6..1457c0d706fa29b6d8fbe3b2a89868c35e48201a 100644
--- a/src/utils/rav-publish/CMakeLists.txt
+++ b/src/utils/rav-publish/CMakeLists.txt
@@ -13,6 +13,8 @@ add_executable(rav-publish
         aes67opts.h
         ${AES67_DIR}/src/include/aes67/utils/mdns.h
         ${AES67_MDNS_SOURCE_FILES}
+        ${AES67_DIR}/src/include/aes67/utils/rtsp-srv.h
+        ${AES67_DIR}/src/utils/rtsp-srv.c
         ${AES67_DIR}/src/third_party/dnmfarrell/URI-Encode-C/src/uri_encode.h
         ${AES67_DIR}/src/third_party/dnmfarrell/URI-Encode-C/src/uri_encode.c
         ${AES67_INCLUDES}
diff --git a/src/utils/rtsp-srv.c b/src/utils/rtsp-srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb678d3e4f9b52dc7ba3bb12518a1d8d59a48cee
--- /dev/null
+++ b/src/utils/rtsp-srv.c
@@ -0,0 +1,484 @@
+/**
+ * AES67 Framework
+ * Copyright (C) 2021  Philip Tschiemer, https://github.com/tschiemer/aes67
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aes67/utils/rtsp-srv.h"
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <search.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+
+static int sock_nonblock(int sockfd){
+    // set non-blocking
+    int flags = fcntl(sockfd, F_GETFL, 0);
+    if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1){
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
+}
+
+void aes67_rtsp_srv_init(struct aes67_rtsp_srv * srv, bool http_enabled, void * user_data)
+{
+    assert(srv);
+
+    srv->state = aes67_rtsp_srv_state_init;
+    srv->first_res = NULL;
+
+    srv->http_enabled = http_enabled;
+    srv->user_data = user_data;
+
+    srv->listen_sockfd = -1;
+    srv->client_sockfd = -1;
+}
+
+void aes67_rtsp_srv_deinit(struct aes67_rtsp_srv * srv)
+{
+    assert(srv);
+
+    aes67_rtsp_srv_stop(srv);
+
+    while(srv->first_res){
+        aes67_rtsp_srv_sdp_remove(srv, srv->first_res);
+    }
+
+    srv->state = aes67_rtsp_srv_state_init;
+}
+
+int aes67_rtsp_srv_start(struct aes67_rtsp_srv * srv, const enum aes67_net_ipver ipver, const u8_t *ip, u16_t port)
+{
+    assert(srv);
+    assert(ipver != aes67_net_ipver_4);
+//    assert(ip);
+    assert(port);
+
+    aes67_rtsp_srv_stop(srv);
+
+    srv->listen_sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
+
+    if (srv->listen_sockfd < 0){
+        perror ("socket()");
+        return EXIT_FAILURE;
+    }
+
+    memset(&srv->listen_addr, 0, sizeof(struct sockaddr_in));
+
+    srv->listen_addr.sin_family = AF_INET;
+    srv->listen_addr.sin_port = htons(port);
+    if (ip){
+        srv->listen_addr.sin_addr.s_addr = *(uint32_t*)ip;
+    } else {
+        srv->listen_addr.sin_addr.s_addr = INADDR_ANY;
+    }
+
+
+    if (bind (srv->listen_sockfd, (struct sockaddr *) &srv->listen_addr, sizeof(struct sockaddr_in)) < 0){
+        close(srv->listen_sockfd);
+        srv->listen_sockfd = -1;
+        perror ("bind()");
+        return EXIT_FAILURE;
+    }
+
+
+    if (listen(srv->listen_sockfd, AES67_RTSPSRV_LISTEN_BACKLOG) == -1){
+        close(srv->listen_sockfd);
+        srv->listen_sockfd = -1;
+        perror ("listen()");
+        return EXIT_FAILURE;
+    }
+
+
+    if (sock_nonblock(srv->listen_sockfd)){
+        fprintf(stderr, "Couldn't change non-/blocking\n");
+        return EXIT_FAILURE;
+    }
+
+    srv->state = aes67_rtsp_srv_state_listening;
+
+    return EXIT_SUCCESS;
+}
+
+void aes67_rtsp_srv_stop(struct aes67_rtsp_srv * srv)
+{
+    assert(srv);
+
+    if (srv->client_sockfd != -1){
+        close(srv->client_sockfd);
+        srv->client_sockfd = -1;
+    }
+
+    if (srv->listen_sockfd != -1){
+        close(srv->listen_sockfd);
+        srv->listen_sockfd = -1;
+    }
+}
+
+void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv)
+{
+    assert(srv);
+
+    if (srv->state == aes67_rtsp_srv_state_init){
+        return;
+    }
+    if (srv->state == aes67_rtsp_srv_state_listening) {
+
+        if ((srv->client_sockfd = accept(srv->listen_sockfd, (struct sockaddr *) &srv->client_sockfd,
+                                         sizeof(srv->client_sockfd))) != -1) {
+
+            srv->state = aes67_rtsp_srv_state_receiving;
+            srv->req.proto = aes67_rtsp_srv_proto_undefined;
+            srv->req.data_len = 0;
+            srv->req.header_len = 0;
+            srv->req.content_length = 0;
+            srv->req.CR = 0;
+            srv->res.status_code = 0;
+
+            u8_t ipstr[AES67_NET_ADDR_STR_MAX];
+            u8_t iplen = aes67_net_ip2str(ipstr, aes67_net_ipver_4, srv->client_addr.sin_addr.s_addr, ntohs(srv->client_addr.sin_port));
+            ipstr[iplen] = '\0';
+
+            fprintf(stderr, "RTSP SRV connection from %s\n", ipstr);
+        }
+    }
+    if (srv->state == aes67_rtsp_srv_state_receiving){
+
+        ssize_t r; // read result
+        u8_t c; // read buf
+        u16_t rl; // readlen
+        int CR = srv->req.CR;
+
+        // read first line
+
+        if (srv->req.proto == aes67_rtsp_srv_proto_undefined){
+            // read only a meaningful max
+            while(srv->req.data_len < 256 ){
+
+                r = read(srv->client_sockfd, &c, 1);
+
+                if (r == -1){ // timeout
+                    return;
+                } else if (r == 0) { // closed
+                    close(srv->client_sockfd);
+                    srv->client_sockfd = -1;
+                    srv->state = aes67_rtsp_srv_state_listening;
+                    return;
+                } else if (r == 1) {
+                    srv->req.data[srv->req.data_len++] = c;
+
+                    // if EOL
+                    if (c == '\n'){
+
+                        // basic sanity check
+                        if (srv->req.data_len < 32){
+                            close(srv->client_sockfd);
+                            srv->client_sockfd = -1;
+                            srv->state = aes67_rtsp_srv_state_listening;
+                            return;
+                        }
+                        // check if using carriage return
+                        if (srv->req.data[srv->req.data_len - 2] == '\r'){
+                            CR = srv->req.CR = 1;
+                        }
+
+                        u8_t * s = srv->req.data_len - CR - sizeof("HTTP/1.0");
+
+                        if (s[0] == 'H' &&
+                            s[1] == 'T' &&
+                            s[2] == 'T' &&
+                            s[3] == 'P' &&
+                            s[4] == '/' &&
+                            s[6] == '.'
+                        ){
+
+                            if (!srv->http_enabled){
+                                close(srv->client_sockfd);
+                                srv->client_sockfd = -1;
+                                srv->state = aes67_rtsp_srv_state_listening;
+                                return;
+                            }
+
+                            srv->req.proto = aes67_rtsp_srv_proto_http;
+                        }
+                        else if (s[0] == 'R' &&
+                                 s[1] == 'T' &&
+                                 s[2] == 'S' &&
+                                 s[3] == 'P' &&
+                                 s[4] == '/' &&
+                                 s[6] == '.'
+                                ){
+
+                            srv->req.proto = aes67_rtsp_srv_proto_rtsp;
+                        }
+                        // client error, just terminate connection without response
+                        else {
+                            close(srv->client_sockfd);
+                            srv->client_sockfd = -1;
+                            srv->state = aes67_rtsp_srv_state_listening;
+                            return;
+                        }
+
+                        srv->req.version.major = s[5] - '0';
+                        srv->req.version.minor = s[7] - '0';
+
+                        s = srv->req.data;
+
+                        if (srv->req.proto = aes67_rtsp_srv_proto_rtsp &&
+                            s[0] == 'D' &&
+                            s[1] == 'E' &&
+                            s[2] == 'S' &&
+                            s[3] == 'C' &&
+                            s[4] == 'R' &&
+                            s[5] == 'I' &&
+                            s[6] == 'B' &&
+                            s[7] == 'E'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_describe;
+                        }
+                        else if (s[0] == 'O' && // supported by both rtsp and http
+                                 s[1] == 'P' &&
+                                 s[2] == 'T' &&
+                                 s[3] == 'I' &&
+                                 s[4] == 'O' &&
+                                 s[5] == 'N' &&
+                                 s[6] == 'S'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_options;
+                        }
+                        else if (srv->req.proto = aes67_rtsp_srv_proto_http &&
+                                 s[0] == 'G' &&
+                                 s[1] == 'E' &&
+                                 s[2] == 'T'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_get;
+                        }
+                        else if (srv->req.proto = aes67_rtsp_srv_proto_http &&
+                                 s[0] == 'P' &&
+                                 s[1] == 'O' &&
+                                 s[2] == 'S' &&
+                                 s[3] == 'T'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_post;
+                        }
+                        else if (srv->req.proto = aes67_rtsp_srv_proto_http &&
+                                 s[0] == 'P' &&
+                                 s[1] == 'U' &&
+                                 s[2] == 'T'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_put;
+                        }
+                        else if (srv->req.proto = aes67_rtsp_srv_proto_http &&
+                                 s[0] == 'D' &&
+                                 s[1] == 'E' &&
+                                 s[2] == 'L' &&
+                                 s[3] == 'E' &&
+                                 s[4] == 'T' &&
+                                 s[5] == 'E'
+                                ) {
+                            srv->req.method = aes67_rtsp_srv_method_delete;
+                        }
+                        else {
+                            // method not supported/recognized, terminate without response
+                            close(srv->client_sockfd);
+                            srv->client_sockfd = -1;
+                            srv->state = aes67_rtsp_srv_state_listening;
+                            return;
+                        }
+
+
+                        // set line start
+                        srv->req.line = &srv->req.data[srv->req.data_len];
+                        srv->req.llen = 0;
+
+                        break;
+                    }
+                }
+            }
+            // fail if using too much memory
+            if (srv->req.data_len >= 256){
+                close(srv->client_sockfd);
+                srv->client_sockfd = -1;
+                srv->state = aes67_rtsp_srv_state_listening;
+                return;
+            }
+        } // read first line
+
+
+        // read complete header
+        if (srv->req.header_len == 0) {
+
+            while (srv->req.data_len < AES67_RTSP_SRV_RXBUFSIZE) {
+
+                r = read(srv->client_sockfd, &c, 1);
+
+                if (r == -1) { // timeout
+                    return;
+                } else if (r == 0) { // closed
+                    close(srv->client_sockfd);
+                    srv->client_sockfd = -1;
+                    srv->state = aes67_rtsp_srv_state_listening;
+                    printf("closed!\n");
+                    return;
+                } else if (r == 1) {
+                    srv->req.data[srv->req.data_len++] = c;
+                    srv->req.llen++;
+
+                    // if EOL
+                    if (c == '\n') {
+
+                        // end of header? ([CR]NL)
+                        if (srv->req.llen == 1 + CR && (!CR || srv->req.line[0] == '\r') && srv->req.line[1] == '\n') {
+                            srv->req.header_len = srv->req.data_len;
+                            // break to start reading body
+                            break;
+                        }
+
+
+                        // expect a header line: "<attr>: <value>\r\n"
+                        u8_t *delim = aes67_memchr(srv->req.line, ':', srv->req.line - &srv->req.data[srv->req.data_len]);
+                        if (delim != NULL) {
+
+//                            res->buf[res->buflen] = '\0';
+//                            printf("line %d [%s]\n", res->llen, res->line);
+//                            printf("%d\n", aes67_ischar_insensitive(res->line[8], 'l'));
+
+                            // verify cseq?
+                            if (delim - srv->req.line == sizeof("CSeq") - 1 &&
+                                srv->req.llen >= sizeof("CSeq: 1") + CR &&
+                                aes67_ischar_insensitive(srv->req.line[0], 'c') &&
+                                aes67_ischar_insensitive(srv->req.line[1], 's') &&
+                                srv->req.line[2] == 'e' &&
+                                srv->req.line[3] == 'q'
+                                    ) {
+                                srv->req.cseq = aes67_atoi(delim + 2, srv->req.llen - sizeof("cseq: ") - CR, 10, &rl);
+                            }
+
+                            // look for content-length field
+                            if (delim - srv->req.line == sizeof("Content-Length") - 1 &&
+                                srv->req.llen >= sizeof("Content-Length: 1") + CR &&
+                                aes67_ischar_insensitive(srv->req.line[0], 'c') &&
+                                srv->req.line[1] == 'o' &&
+                                srv->req.line[2] == 'n' &&
+                                srv->req.line[3] == 't' &&
+                                srv->req.line[4] == 'e' &&
+                                srv->req.line[5] == 'n' &&
+                                srv->req.line[6] == 't' &&
+                                srv->req.line[7] == '-' &&
+                                aes67_ischar_insensitive(srv->req.line[8], 'l') &&
+                                srv->req.line[9] == 'e' &&
+                                srv->req.line[10] == 'n' &&
+                                srv->req.line[11] == 'g' &&
+                                srv->req.line[12] == 't' &&
+                                srv->req.line[13] == 'h'
+                                    ) {
+                                srv->req.content_length = aes67_atoi(delim + 2, srv->req.llen - sizeof("content-length: \r"), 10,
+                                                                     &rl);
+                            }
+
+                        }
+
+                        // reset line start
+                        srv->req.line = &srv->req.data[srv->req.data_len];
+                        srv->req.llen = 0;
+                    }
+                }
+                // fail if using too much memory
+                if (srv->req.data_len >= AES67_RTSP_SRV_RXBUFSIZE) {
+                    close(srv->client_sockfd);
+                    srv->client_sockfd = -1;
+                    srv->state = aes67_rtsp_srv_state_listening;
+                    printf("overflow\n");
+                    return;
+                }
+            }
+        } // header
+
+        // read body
+    }
+}
+
+WEAK_FUN void aes67_rtsp_srv_sdp_getter(struct aes67_rtsp_srv * srv, void * sdpref, u8_t * buf, u16_t * len, u16_t maxlen)
+{
+    assert(false);
+}
+
+WEAK_FUN void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, const char * uri, const u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, void * response_data)
+{
+
+}
+
+
+struct aes67_rtsp_srv_resource * aes67_rtsp_srv_sdp_add(struct aes67_rtsp_srv * srv, const char * uri, const u8_t urilen, const void * sdpref)
+{
+    assert(srv);
+    assert(uri);
+    assert(sdpref);
+
+    struct aes67_rtsp_srv_resource * res = malloc(sizeof(struct aes67_rtsp_srv_resource));
+
+    aes67_memcpy(res->uri, uri, urilen);
+    res->uri[urilen] = '\0';
+    res->urilen = urilen;
+
+    res->sdpref = sdpref;
+
+    res->next = srv->first_res;
+    srv->first_res = res;
+
+    return res;
+}
+
+void aes67_rtsp_srv_sdp_remove(struct aes67_rtsp_srv * srv, void * sdpref)
+{
+    assert(srv);
+    assert(sdpref);
+
+    struct aes67_rtsp_srv_resource * res = srv->first_res;
+
+    // locate resource
+    while (res && res->sdpref != sdpref){
+        res = res->next;
+    }
+
+    // safety check
+    if (!res){
+        return;
+    }
+
+    // now free resource
+    if (srv->first_res == res){
+        srv->first_res = res->next;
+    } else {
+        struct aes67_rtsp_srv_resource * before = srv->first_res;
+        while(before != NULL){
+
+            if (before->next == res){
+                before->next = res->next;
+                break;
+            }
+
+            before = before->next;
+        }
+    }
+
+    free(res);
+}
\ No newline at end of file