Add Haivision Open SRT protocol

Message ID 1522078669-17122-1-git-send-email-sven.dueking@nablet.com
State New
Headers show
Series
  • Add Haivision Open SRT protocol
Related show

Commit Message

Sven Dueking March 26, 2018, 3:37 p.m.
protocol requires libsrt (https://github.com/Haivision/srt) to be
installed

Signed-off-by: Sven Dueking <sven.dueking@nablet.com>
Signed-off-by: Luca Barbato <lu_zero@gentoo.org>
---
 configure               |   5 +
 doc/protocols.texi      | 140 ++++++++++++
 libavformat/Makefile    |   1 +
 libavformat/opensrt.c   | 549 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/protocols.c |   1 +
 5 files changed, 696 insertions(+)
 create mode 100644 libavformat/opensrt.c

Comments

Diego Biurrun March 26, 2018, 3:14 p.m. | #1
On Mon, Mar 26, 2018 at 11:37:49AM -0400, Sven Dueking wrote:
> --- a/configure
> +++ b/configure
> @@ -4710,6 +4714,7 @@ enabled omx               && require_header OMX_Core.h
> +enabled opensrt           && require_pkg_config libsrt "srt >= 1.2.0" srt/srt.h srt_socket

Why do you call this thing opensrt when it calls itself libsrt?

Diego
nablet developer March 26, 2018, 3:16 p.m. | #2
On 26-Mar-18 22:14, Diego Biurrun wrote:
> On Mon, Mar 26, 2018 at 11:37:49AM -0400, Sven Dueking wrote:
>> --- a/configure
>> +++ b/configure
>> @@ -4710,6 +4714,7 @@ enabled omx               && require_header OMX_Core.h
>> +enabled opensrt           && require_pkg_config libsrt "srt >= 1.2.0" srt/srt.h srt_socket
> Why do you call this thing opensrt when it calls itself libsrt?
>
>
names srt and libsrt were found confusing and misleading, because of 
similar acronyms used for other
multimedia related things which are pretty well-known and wide-spread 
already (e.g. SRT subtitles or
SRTP protocol). as result, maintainers/reviewers asked to change name to 
avoid confusion, and Haivision
recommended to use "opensrt" naming in order to distinguish from other 
similar acronyms.
Diego Biurrun March 26, 2018, 3:42 p.m. | #3
On Mon, Mar 26, 2018 at 10:16:06PM +0700, nablet developer wrote:
> On 26-Mar-18 22:14, Diego Biurrun wrote:
> > On Mon, Mar 26, 2018 at 11:37:49AM -0400, Sven Dueking wrote:
> > > --- a/configure
> > > +++ b/configure
> > > @@ -4710,6 +4714,7 @@ enabled omx               && require_header OMX_Core.h
> > > +enabled opensrt           && require_pkg_config libsrt "srt >= 1.2.0" srt/srt.h srt_socket
> > Why do you call this thing opensrt when it calls itself libsrt?
> > 
> names srt and libsrt were found confusing and misleading, because of
> similar acronyms used for other multimedia related things which are
> pretty well-known and wide-spread already (e.g. SRT subtitles or SRTP
> protocol). as result, maintainers/reviewers asked to change name to
> avoid confusion, and Haivision recommended to use "opensrt" naming in
> order to distinguish from other similar acronyms.

The problem is that Haivision does not use "opensrt" as name, they use
"SRT/srt" and libsrt. Using a different name inside libav does not
really help in reducing that potential for confusion. If Haivision
recommends "opensrt", why don't they change the name of their library?
Better yet, why don't they change the name of the protocol?

Notice that what I gathered from the discussion was people complaining
about the bad naming choice, not suggesting "opensrt" as the alternative.

I have asked this before: what is your relationship to Haivision?

Diego
Luca Barbato March 26, 2018, 4:37 p.m. | #4
On 26/03/2018 17:37, Sven Dueking wrote:
> protocol requires libsrt (https://github.com/Haivision/srt) to be
> installed
> 
> Signed-off-by: Sven Dueking <sven.dueking@nablet.com>
> Signed-off-by: Luca Barbato <lu_zero@gentoo.org>
> ---

 From a quick search across engines:

- "srt protocol" leads to the right thing.

- gstreamer references srt/libsrt in their components and in their 
documentation.

If there aren't other non-cosmetic issues I'd merge it with the libsrt 
namespace and be done with that.

I might ask you help so we can have some additional information about 
how to properly use the protocol in the wiki.

lu
Luca Barbato March 26, 2018, 5:05 p.m. | #5
On 26/03/2018 17:16, nablet developer wrote:
> names srt and libsrt were found confusing and misleading, because of
>  similar acronyms used for other multimedia related things which are
> pretty well-known and wide-spread already (e.g. SRT subtitles or SRTP
> protocol). as result, maintainers/reviewers asked to change name to 
> avoid confusion, and Haivision recommended to use "opensrt" naming in
> order to distinguish from other similar acronyms.

One is SubRip text, quite a different beast.

SRTP was already a bad name since RTPs and RTSP existed before and are 
even closely related.

Right now I'm less concerned about spending time introducing yet another 
potentially unsearchable variant since gstreamer is using srt/libsrt 
since a while and just feeding any search engine worth its name with 
"srt protocol" produces non-misleading links.

lu
Diego Biurrun March 29, 2018, 6:29 a.m. | #6
On Mon, Mar 26, 2018 at 11:37:49AM -0400, Sven Dueking wrote:
> protocol requires libsrt (https://github.com/Haivision/srt) to be
> installed

Luca and I pushed a cleaned-up version of this yesterday.

Diego

Patch

diff --git a/configure b/configure
index 95e6006..adb0341 100755
--- a/configure
+++ b/configure
@@ -229,6 +229,7 @@  External library support:
   --enable-libxcb-xfixes     X11 mouse rendering [auto]
   --enable-libxvid           MPEG-4 ASP video encoding
   --enable-openssl           crypto
+  --enable-opensrt           enable Haivision Open SRT protocol [no]
   --enable-zlib              compression [autodetect]
 
   The following libraries provide various hardware acceleration features:
@@ -1380,6 +1381,7 @@  EXTERNAL_LIBRARY_LIST="
     libxcb
     libxcb_shm
     libxcb_xfixes
+    opensrt
 "
 
 SYSTEM_LIBRARY_LIST="
@@ -2522,6 +2524,8 @@  librtmpt_protocol_deps="librtmp"
 librtmpte_protocol_deps="librtmp"
 mmsh_protocol_select="http_protocol"
 mmst_protocol_select="network"
+opensrt_protocol_select="network"
+opensrt_protocol_deps="opensrt"
 rtmp_protocol_conflict="librtmp_protocol"
 rtmp_protocol_select="tcp_protocol"
 rtmp_protocol_suggest="zlib"
@@ -4710,6 +4714,7 @@  enabled omx               && require_header OMX_Core.h
 enabled omx_rpi           && { check_header OMX_Core.h ||
                                { ! enabled cross_compile && add_cflags -isystem/opt/vc/include/IL && check_header OMX_Core.h ; } ||
                                die "ERROR: OpenMAX IL headers not found"; } && enable omx
+enabled opensrt           && require_pkg_config libsrt "srt >= 1.2.0" srt/srt.h srt_socket
 enabled openssl           && { { check_pkg_config openssl openssl openssl/ssl.h OPENSSL_init_ssl ||
                                  check_pkg_config openssl openssl openssl/ssl.h SSL_library_init; } ||
                                check_lib openssl openssl/ssl.h SSL_library_init -lssl -lcrypto ||
diff --git a/doc/protocols.texi b/doc/protocols.texi
index c136c74..40c755d 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -655,6 +655,146 @@  To play back the first stream announced on one the default IPv6 SAP multicast ad
 avplay sap://[ff0e::2:7ffe]
 @end example
 
+@section srt
+
+Haivision Secure Reliable Transport Protocol via libsrt.
+
+The supported syntax for a SRT URL is:
+@example
+srt://@var{hostname}:@var{port}[?@var{options}]
+@end example
+
+@var{options} contains a list of &-separated options of the form
+@var{key}=@var{val}.
+
+or
+
+@example
+@var{options} srt://@var{hostname}:@var{port}
+@end example
+
+@var{options} contains a list of '-@var{key} @var{val}'
+options.
+
+This protocol accepts the following options.
+
+@table @option
+@item connect_timeout
+Connection timeout; SRT cannot connect for RTT > 1500 msec
+(2 handshake exchanges) with the default connect timeout of
+3 seconds. This option applies to the caller and rendezvous
+connection modes. The connect timeout is 10 times the value
+set for the rendezvous mode (which can be used as a
+workaround for this connection problem with earlier versions).
+
+@item ffs=@var{bytes}
+Flight Flag Size (Window Size), in bytes. FFS is actually an
+internal parameter and you should set it to not less than
+@option{recv_buffer_size} and @option{mss}. The default value
+is relatively large, therefore unless you set a very large receiver buffer,
+you do not need to change this option. Default value is 25600.
+
+@item inputbw=@var{bytes/seconds}
+Sender nominal input rate, in bytes per seconds. Used along with
+@option{oheadbw}, when @option{maxbw} is set to relative (0), to
+calculate maximum sending rate when recovery packets are sent
+along with the main media stream:
+@option{inputbw} * (100 + @option{oheadbw}) / 100
+if @option{inputbw} is not set while @option{maxbw} is set to
+relative (0), the actual actual input rate is evaluated inside
+the library. Default value is 0.
+
+@item iptos=@var{tos}
+IP Type of Service. Applies to sender only. Default value is 0xB8.
+
+@item ipttl=@var{ttl}
+IP Time To Live. Applies to sender only. Default value is 64.
+
+@item listen_timeout
+Set socket listen timeout.
+
+@item maxbw=@var{bytes/seconds}
+Maximum sending bandwidth, in bytes per seconds.
+-1 infinite (CSRTCC limit is 30mbps)
+0 relative to input rate (see @option{inputbw})
+>0 absolute limit value
+Default value is 0 (relative)
+
+@item mode=@var{caller|listener|rendezvous}
+Connection mode.
+@option{caller} opens client connection.
+@option{listener} starts server to listen for incoming connections.
+@option{rendezvous} use Rendez-Vous connection mode.
+Default value is caller.
+
+@item mss=@var{bytes}
+Maximum Segment Size, in bytes. Used for buffer allocation
+and rate calculation using a packet counter assuming fully
+filled packets. The smallest MSS between the peers is
+used. This is 1500 by default in the overall internet.
+This is the maximum size of the UDP packet and can be
+only decreased, unless you have some unusual dedicated
+network settings. Default value is 1500.
+
+@item nakreport=@var{1|0}
+If set to 1, Receiver will send `UMSG_LOSSREPORT` messages
+periodically until a lost packet is retransmitted or
+intentionally dropped. Default value is 1.
+
+@item oheadbw=@var{percents}
+Recovery bandwidth overhead above input rate, in percents.
+See @option{inputbw}. Default value is 25%.
+
+@item passphrase=@var{string}
+HaiCrypt Encryption/Decryption Passphrase string, length
+from 10 to 79 characters. The passphrase is the shared
+secret between the sender and the receiver. It is used
+to generate the Key Encrypting Key using PBKDF2
+(Password-Based Key Derivation Function). It is used
+only if @option{pbkeylen} is non-zero. It is used on
+the receiver only if the received data is encrypted.
+The configured passphrase cannot be recovered (write-only).
+
+@item pbkeylen=@var{bytes}
+Sender encryption key length, in bytes.
+Only can be set to 0, 16, 24 and 32.
+Enable sender encryption if not 0.
+Not required on receiver (set to 0),
+key size obtained from sender in HaiCrypt handshake.
+Default value is 0.
+
+@item recv_buffer_size=@var{bytes}
+Set receive buffer size, expressed in bytes.
+
+@item send_buffer_size=@var{bytes}
+Set send buffer size, expressed in bytes.
+
+@item rw_timeout
+Set raise error timeout for read/write optations.
+
+This option is only relevant in read mode:
+if no data arrived in more than this time
+interval, raise error.
+
+@item tlpktdrop=@var{1|0}
+Too-late Packet Drop. When enabled on receiver, it skips
+missing packets that have not been delivered in time and
+delivers the following packets to the application when
+their time-to-play has come. It also sends a fake ACK to
+the sender. When enabled on sender and enabled on the
+receiving peer, the sender drops the older packets that
+have no chance of being delivered in time. It was
+automatically enabled in the sender if the receiver
+supports it.
+
+@item tsbpddelay
+Timestamp-based Packet Delivery Delay.
+Used to absorb burst of missed packet retransmission.
+
+@end table
+
+For more information see: @url{https://github.com/Haivision/srt}.
+
 @section tcp
 
 Transmission Control Protocol.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 2c1c0f6..8a3b748 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -398,6 +398,7 @@  OBJS-$(CONFIG_MD5_PROTOCOL)              += md5proto.o
 OBJS-$(CONFIG_MMSH_PROTOCOL)             += mmsh.o mms.o asf.o
 OBJS-$(CONFIG_MMST_PROTOCOL)             += mmst.o mms.o asf.o
 OBJS-$(CONFIG_PIPE_PROTOCOL)             += file.o
+OBJS-$(CONFIG_OPENSRT_PROTOCOL)          += opensrt.o
 OBJS-$(CONFIG_RTMP_PROTOCOL)             += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPE_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
 OBJS-$(CONFIG_RTMPS_PROTOCOL)            += rtmpproto.o rtmpdigest.o rtmppkt.o
diff --git a/libavformat/opensrt.c b/libavformat/opensrt.c
new file mode 100644
index 0000000..3f85c1c
--- /dev/null
+++ b/libavformat/opensrt.c
@@ -0,0 +1,549 @@ 
+/*
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Haivision Open SRT (Secure Reliable Transport) protocol
+ */
+
+#include "avformat.h"
+#include "libavutil/avassert.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/time.h"
+
+#include "internal.h"
+#include "network.h"
+#include "os_support.h"
+#include "url.h"
+#if HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#include <srt/srt.h>
+
+enum SRTMode {
+    SRT_MODE_CALLER = 0,
+    SRT_MODE_LISTENER = 1,
+    SRT_MODE_RENDEZVOUS = 2
+};
+
+typedef struct SRTContext {
+    const AVClass *class;
+    int fd;
+    int eid;
+    int64_t rw_timeout;
+    int64_t listen_timeout;
+    int recv_buffer_size;
+    int send_buffer_size;
+
+    int64_t maxbw;
+    int pbkeylen;
+    char *passphrase;
+    int mss;
+    int ffs;
+    int ipttl;
+    int iptos;
+    int64_t inputbw;
+    int oheadbw;
+    int64_t tsbpddelay;
+    int tlpktdrop;
+    int nakreport;
+    int64_t connect_timeout;
+    enum SRTMode mode;
+} SRTContext;
+
+#define D AV_OPT_FLAG_DECODING_PARAM
+#define E AV_OPT_FLAG_ENCODING_PARAM
+#define OFFSET(x) offsetof(SRTContext, x)
+static const AVOption opensrt_options[] = {
+    { "rw_timeout",     "Timeout of socket I/O operations",                                     OFFSET(rw_timeout),       AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "listen_timeout", "Connection awaiting timeout",                                          OFFSET(listen_timeout),   AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "send_buffer_size", "Socket send buffer size (in bytes)",                                 OFFSET(send_buffer_size), AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, INT_MAX,   .flags = D|E },
+    { "recv_buffer_size", "Socket receive buffer size (in bytes)",                              OFFSET(recv_buffer_size), AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, INT_MAX,   .flags = D|E },
+    { "maxbw",          "Maximum bandwidth (bytes per second) that the connection can use",     OFFSET(maxbw),            AV_OPT_TYPE_INT64,    { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "pbkeylen",       "Crypto key len in bytes {16,24,32} Default: 16 (128-bit)",             OFFSET(pbkeylen),         AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 32,        .flags = D|E },
+    { "passphrase",     "Crypto PBKDF2 Passphrase size[0,10..64] 0:disable crypto",             OFFSET(passphrase),       AV_OPT_TYPE_STRING,   { .str = NULL },              .flags = D|E },
+    { "mss",            "The Maximum Segment Size",                                             OFFSET(mss),              AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 1500,      .flags = D|E },
+    { "ffs",            "Flight flag size (window size) (in bytes)",                            OFFSET(ffs),              AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, INT_MAX,   .flags = D|E },
+    { "ipttl",          "IP Time To Live",                                                      OFFSET(ipttl),            AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 255,       .flags = D|E },
+    { "iptos",          "IP Type of Service",                                                   OFFSET(iptos),            AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 255,       .flags = D|E },
+    { "inputbw",        "Estimated input stream rate",                                          OFFSET(inputbw),          AV_OPT_TYPE_INT64,    { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "oheadbw",        "MaxBW ceiling based on % over input stream rate",                      OFFSET(oheadbw),          AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 100,       .flags = D|E },
+    { "tsbpddelay",     "TsbPd receiver delay to absorb burst of missed packet retransmission", OFFSET(tsbpddelay),       AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "tlpktdrop",      "Enable receiver pkt drop",                                             OFFSET(tlpktdrop),        AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 1,         .flags = D|E },
+    { "nakreport",      "Enable receiver to send periodic NAK reports",                         OFFSET(nakreport),        AV_OPT_TYPE_INT,      { .i64 = -1 }, -1, 1,         .flags = D|E },
+    { "connect_timeout", "Connect timeout. Caller default: 3000, rendezvous (x 10)",            OFFSET(connect_timeout),  AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, .flags = D|E },
+    { "mode",           "Connection mode (caller, listener, rendezvous)",                       OFFSET(mode),             AV_OPT_TYPE_INT,      { .i64 = SRT_MODE_CALLER }, SRT_MODE_CALLER, SRT_MODE_RENDEZVOUS, .flags = D|E, "mode" },
+    { "caller",         NULL, 0, AV_OPT_TYPE_CONST,  { .i64 = SRT_MODE_CALLER },     INT_MIN, INT_MAX, .flags = D|E, "mode" },
+    { "listener",       NULL, 0, AV_OPT_TYPE_CONST,  { .i64 = SRT_MODE_LISTENER },   INT_MIN, INT_MAX, .flags = D|E, "mode" },
+    { "rendezvous",     NULL, 0, AV_OPT_TYPE_CONST,  { .i64 = SRT_MODE_RENDEZVOUS }, INT_MIN, INT_MAX, .flags = D|E, "mode" },
+    { NULL }
+};
+
+static int opensrt_neterrno(URLContext *h)
+{
+    int err = srt_getlasterror(NULL);
+    av_log(h, AV_LOG_ERROR, "%s\n", srt_getlasterror_str());
+    if (err == SRT_EASYNCRCV)
+        return AVERROR(EAGAIN);
+    return AVERROR_UNKNOWN;
+}
+
+static int opensrt_socket_nonblock(int socket, int enable)
+{
+    int ret = srt_setsockopt(socket, 0, SRTO_SNDSYN, &enable, sizeof(enable));
+    if (ret < 0)
+        return ret;
+    return srt_setsockopt(socket, 0, SRTO_RCVSYN, &enable, sizeof(enable));
+}
+
+static int opensrt_network_wait_fd(URLContext *h, int eid, int fd, int write)
+{
+    int ret, len = 1;
+    int modes = write ? SRT_EPOLL_OUT : SRT_EPOLL_IN;
+    SRTSOCKET ready[1];
+
+    if (srt_epoll_add_usock(eid, fd, &modes) < 0)
+        return opensrt_neterrno(h);
+    if (write) {
+        ret = srt_epoll_wait(eid, 0, 0, ready, &len, POLLING_TIME, 0, 0, 0, 0);
+    } else {
+        ret = srt_epoll_wait(eid, ready, &len, 0, 0, POLLING_TIME, 0, 0, 0, 0);
+    }
+    if (ret < 0) {
+        if (srt_getlasterror(NULL) == SRT_ETIMEOUT)
+            ret = AVERROR(EAGAIN);
+        else
+            ret = opensrt_neterrno(h);
+    } else {
+        ret = 0;
+    }
+    if (srt_epoll_remove_usock(eid, fd) < 0)
+        return opensrt_neterrno(h);
+    return ret;
+}
+
+/* TODO de-duplicate code from ff_network_wait_fd_timeout() */
+
+static int opensrt_network_wait_fd_timeout(URLContext *h, int eid, int fd, int write, int64_t timeout, AVIOInterruptCB *int_cb)
+{
+    int ret;
+    int64_t wait_start = 0;
+
+    while (1) {
+        if (ff_check_interrupt(int_cb))
+            return AVERROR_EXIT;
+        ret = opensrt_network_wait_fd(h, eid, fd, write);
+        if (ret != AVERROR(EAGAIN))
+            return ret;
+        if (timeout > 0) {
+            if (!wait_start)
+                wait_start = av_gettime_relative();
+            else if (av_gettime_relative() - wait_start > timeout)
+                return AVERROR(ETIMEDOUT);
+        }
+    }
+}
+
+static int opensrt_listen(int eid, int fd, const struct sockaddr *addr, socklen_t addrlen, URLContext *h, int timeout)
+{
+    int ret;
+    int reuse = 1;
+    if (srt_setsockopt(fd, SOL_SOCKET, SRTO_REUSEADDR, &reuse, sizeof(reuse))) {
+        av_log(h, AV_LOG_WARNING, "setsockopt(SRTO_REUSEADDR) failed\n");
+    }
+    ret = srt_bind(fd, addr, addrlen);
+    if (ret)
+        return opensrt_neterrno(h);
+
+    ret = srt_listen(fd, 1);
+    if (ret)
+        return opensrt_neterrno(h);
+
+    while ((ret = opensrt_network_wait_fd_timeout(h, eid, fd, 1, timeout, &h->interrupt_callback))) {
+        switch (ret) {
+        case AVERROR(ETIMEDOUT):
+            continue;
+        default:
+            return ret;
+        }
+    }
+
+    ret = srt_accept(fd, NULL, NULL);
+    if (ret < 0)
+        return opensrt_neterrno(h);
+    if (opensrt_socket_nonblock(ret, 1) < 0)
+        av_log(h, AV_LOG_DEBUG, "opensrt_socket_nonblock failed\n");
+
+    return ret;
+}
+
+static int opensrt_listen_connect(int eid, int fd, const struct sockaddr *addr, socklen_t addrlen, int timeout, URLContext *h, int will_try_next)
+{
+    int ret;
+
+    if (opensrt_socket_nonblock(fd, 1) < 0)
+        av_log(h, AV_LOG_DEBUG, "ff_socket_nonblock failed\n");
+
+    while ((ret = srt_connect(fd, addr, addrlen))) {
+        ret = opensrt_neterrno(h);
+        switch (ret) {
+        case AVERROR(EINTR):
+            if (ff_check_interrupt(&h->interrupt_callback))
+                return AVERROR_EXIT;
+            continue;
+        case AVERROR(EINPROGRESS):
+        case AVERROR(EAGAIN):
+            ret = opensrt_network_wait_fd_timeout(h, eid, fd, 1, timeout, &h->interrupt_callback);
+            if (ret < 0)
+                return ret;
+            ret = srt_getlasterror(NULL);
+            srt_clearlasterror();
+            if (ret != 0) {
+                char buf[128];
+                ret = AVERROR(ret);
+                av_strerror(ret, buf, sizeof(buf));
+                if (will_try_next)
+                    av_log(h, AV_LOG_WARNING,
+                           "Connection to %s failed (%s), trying next address\n",
+                           h->filename, buf);
+                else
+                    av_log(h, AV_LOG_ERROR, "Connection to %s failed: %s\n",
+                           h->filename, buf);
+            }
+        default:
+            return ret;
+        }
+    }
+    return ret;
+}
+
+static int opensrt_setsockopt(URLContext *h, int fd, SRT_SOCKOPT optname, const char * optnamestr, const void * optval, int optlen)
+{
+    if (srt_setsockopt(fd, 0, optname, optval, optlen) < 0) {
+        av_log(h, AV_LOG_ERROR, "failed to set option %s on socket: %s\n", optnamestr, srt_getlasterror_str());
+        return AVERROR(EIO);
+    }
+    return 0;
+}
+
+/* - The "POST" options can be altered any time on a connected socket.
+     They MAY have also some meaning when set prior to connecting; such
+     option is SRTO_RCVSYN, which makes connect/accept call asynchronous.
+     Because of that this option is treated special way in this app. */
+static int opensrt_set_options_post(URLContext *h, int fd)
+{
+    SRTContext *s = h->priv_data;
+
+    if ((s->inputbw >= 0 && opensrt_setsockopt(h, fd, SRTO_INPUTBW, "SRTO_INPUTBW", &s->inputbw, sizeof(s->inputbw)) < 0) ||
+        (s->oheadbw >= 0 && opensrt_setsockopt(h, fd, SRTO_OHEADBW, "SRTO_OHEADBW", &s->oheadbw, sizeof(s->oheadbw)) < 0)) {
+        return AVERROR(EIO);
+    }
+    return 0;
+}
+
+/* - The "PRE" options must be set prior to connecting and can't be altered
+     on a connected socket, however if set on a listening socket, they are
+     derived by accept-ed socket. */
+static int opensrt_set_options_pre(URLContext *h, int fd)
+{
+    SRTContext *s = h->priv_data;
+    int yes = 1;
+    int tsbpddelay = s->tsbpddelay / 1000;
+    int connect_timeout = s->connect_timeout;
+
+    if ((s->mode == SRT_MODE_RENDEZVOUS && opensrt_setsockopt(h, fd, SRTO_RENDEZVOUS, "SRTO_RENDEZVOUS", &yes, sizeof(yes)) < 0) ||
+        (s->maxbw >= 0 && opensrt_setsockopt(h, fd, SRTO_MAXBW, "SRTO_MAXBW", &s->maxbw, sizeof(s->maxbw)) < 0) ||
+        (s->pbkeylen >= 0 && opensrt_setsockopt(h, fd, SRTO_PBKEYLEN, "SRTO_PBKEYLEN", &s->pbkeylen, sizeof(s->pbkeylen)) < 0) ||
+        (s->passphrase && opensrt_setsockopt(h, fd, SRTO_PASSPHRASE, "SRTO_PASSPHRASE", &s->passphrase, sizeof(s->passphrase)) < 0) ||
+        (s->mss >= 0 && opensrt_setsockopt(h, fd, SRTO_MSS, "SRTO_MMS", &s->mss, sizeof(s->mss)) < 0) ||
+        (s->ffs >= 0 && opensrt_setsockopt(h, fd, SRTO_FC, "SRTO_FC", &s->ffs, sizeof(s->ffs)) < 0) ||
+        (s->ipttl >= 0 && opensrt_setsockopt(h, fd, SRTO_IPTTL, "SRTO_UPTTL", &s->ipttl, sizeof(s->ipttl)) < 0) ||
+        (s->iptos >= 0 && opensrt_setsockopt(h, fd, SRTO_IPTOS, "SRTO_IPTOS", &s->iptos, sizeof(s->iptos)) < 0) ||
+        (tsbpddelay >= 0 && opensrt_setsockopt(h, fd, SRTO_TSBPDDELAY, "SRTO_TSBPDELAY", &tsbpddelay, sizeof(tsbpddelay)) < 0) ||
+        (s->tlpktdrop >= 0 && opensrt_setsockopt(h, fd, SRTO_TLPKTDROP, "SRTO_TLPKDROP", &s->tlpktdrop, sizeof(s->tlpktdrop)) < 0) ||
+        (s->nakreport >= 0 && opensrt_setsockopt(h, fd, SRTO_NAKREPORT, "SRTO_NAKREPORT", &s->nakreport, sizeof(s->nakreport)) < 0) ||
+        (connect_timeout >= 0 && opensrt_setsockopt(h, fd, SRTO_CONNTIMEO, "SRTO_CONNTIMEO", &connect_timeout, sizeof(connect_timeout)) <0 )) {
+        return AVERROR(EIO);
+    }
+    return 0;
+}
+
+
+static int opensrt_setup(URLContext *h, const char *uri, int flags)
+{
+    struct addrinfo hints = { 0 }, *ai, *cur_ai;
+    int port, fd = -1;
+    SRTContext *s = h->priv_data;
+    const char *p;
+    char buf[256];
+    int ret;
+    char hostname[1024],proto[1024],path[1024];
+    char portstr[10];
+    int open_timeout = 5000000;
+    int eid;
+
+    eid = srt_epoll_create();
+    if (eid < 0)
+        return opensrt_neterrno(h);
+    s->eid = eid;
+
+    av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
+        &port, path, sizeof(path), uri);
+    if (strcmp(proto, "srt"))
+        return AVERROR(EINVAL);
+    if (port <= 0 || port >= 65536) {
+        av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
+        return AVERROR(EINVAL);
+    }
+    p = strchr(uri, '?');
+    if (p) {
+        if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
+            s->rw_timeout = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
+            s->listen_timeout = strtol(buf, NULL, 10);
+        }
+    }
+    if (s->rw_timeout >= 0) {
+        open_timeout = h->rw_timeout = s->rw_timeout;
+    }
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_DGRAM;
+    snprintf(portstr, sizeof(portstr), "%d", port);
+    if (s->mode == SRT_MODE_LISTENER)
+        hints.ai_flags |= AI_PASSIVE;
+    ret = getaddrinfo(hostname[0] ? hostname : NULL, portstr, &hints, &ai);
+    if (ret) {
+        av_log(h, AV_LOG_ERROR,
+               "Failed to resolve hostname %s: %s\n",
+               hostname, gai_strerror(ret));
+        return AVERROR(EIO);
+    }
+
+    cur_ai = ai;
+
+ restart:
+
+    fd = srt_socket(cur_ai->ai_family, cur_ai->ai_socktype, 0);
+    if (fd < 0) {
+        ret = opensrt_neterrno(h);
+        goto fail;
+    }
+
+    if ((ret = opensrt_set_options_pre(h, fd)) < 0) {
+        goto fail;
+    }
+
+    /* Set the socket's send or receive buffer sizes, if specified.
+       If unspecified or setting fails, system default is used. */
+    if (s->recv_buffer_size > 0) {
+        srt_setsockopt(fd, SOL_SOCKET, SRTO_UDP_RCVBUF, &s->recv_buffer_size, sizeof (s->recv_buffer_size));
+    }
+    if (s->send_buffer_size > 0) {
+        srt_setsockopt(fd, SOL_SOCKET, SRTO_UDP_SNDBUF, &s->send_buffer_size, sizeof (s->send_buffer_size));
+    }
+    if (s->mode == SRT_MODE_LISTENER) {
+        // multi-client
+        if ((ret = opensrt_listen(s->eid, fd, cur_ai->ai_addr, cur_ai->ai_addrlen, h, open_timeout / 1000)) < 0)
+            goto fail1;
+        fd = ret;
+    } else {
+        if (s->mode == SRT_MODE_RENDEZVOUS) {
+            ret = srt_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen);
+            if (ret)
+                goto fail1;
+        }
+
+        if ((ret = opensrt_listen_connect(s->eid, fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
+                                          open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) {
+            if (ret == AVERROR_EXIT)
+                goto fail1;
+            else
+                goto fail;
+        }
+    }
+    if ((ret = opensrt_set_options_post(h, fd)) < 0) {
+        goto fail;
+    }
+
+    h->is_streamed = 1;
+    s->fd = fd;
+
+    freeaddrinfo(ai);
+    return 0;
+
+ fail:
+    if (cur_ai->ai_next) {
+        /* Retry with the next sockaddr */
+        cur_ai = cur_ai->ai_next;
+        if (fd >= 0)
+            srt_close(fd);
+        ret = 0;
+        goto restart;
+    }
+ fail1:
+    if (fd >= 0)
+        srt_close(fd);
+    freeaddrinfo(ai);
+    return ret;
+}
+
+static int opensrt_open(URLContext *h, const char *uri, int flags)
+{
+    SRTContext *s = h->priv_data;
+    const char * p;
+    char buf[256];
+
+    if (srt_startup() < 0) {
+        return AVERROR_UNKNOWN;
+    }
+
+    /* SRT options (srt/srt.h) */
+    p = strchr(uri, '?');
+    if (p) {
+        if (av_find_info_tag(buf, sizeof(buf), "maxbw", p)) {
+            s->maxbw = strtoll(buf, NULL, 0);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "pbkeylen", p)) {
+            s->pbkeylen = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "passphrase", p)) {
+            s->passphrase = av_strndup(buf, strlen(buf));
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "mss", p)) {
+            s->mss = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "ffs", p)) {
+            s->ffs = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "ipttl", p)) {
+            s->ipttl = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "iptos", p)) {
+            s->iptos = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "inputbw", p)) {
+            s->inputbw = strtoll(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "oheadbw", p)) {
+            s->oheadbw = strtoll(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "tsbpddelay", p)) {
+            s->tsbpddelay = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "tlpktdrop", p)) {
+            s->tlpktdrop = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "nakreport", p)) {
+            s->nakreport = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "connect_timeout", p)) {
+            s->connect_timeout = strtol(buf, NULL, 10);
+        }
+        if (av_find_info_tag(buf, sizeof(buf), "mode", p)) {
+            if (!strcmp(buf, "caller")) {
+                s->mode = SRT_MODE_CALLER;
+            } else if (!strcmp(buf, "listener")) {
+                s->mode = SRT_MODE_LISTENER;
+            } else if (!strcmp(buf, "rendezvous")) {
+                s->mode = SRT_MODE_RENDEZVOUS;
+            } else {
+                return AVERROR(EIO);
+            }
+        }
+    }
+    return opensrt_setup(h, uri, flags);
+}
+
+static int opensrt_read(URLContext *h, uint8_t *buf, int size)
+{
+    SRTContext *s = h->priv_data;
+    int ret;
+
+    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
+        ret = opensrt_network_wait_fd_timeout(h, s->eid, s->fd, 0, h->rw_timeout, &h->interrupt_callback);
+        if (ret)
+            return ret;
+    }
+
+    ret = srt_recvmsg(s->fd, buf, size);
+    if (ret < 0) {
+        ret = opensrt_neterrno(h);
+    }
+
+    return ret;
+}
+
+static int opensrt_write(URLContext *h, const uint8_t *buf, int size)
+{
+    SRTContext *s = h->priv_data;
+    int ret;
+
+    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
+        ret = opensrt_network_wait_fd_timeout(h, s->eid, s->fd, 1, h->rw_timeout, &h->interrupt_callback);
+        if (ret)
+            return ret;
+    }
+
+    ret = srt_sendmsg(s->fd, buf, size, -1, 0);
+    if (ret < 0) {
+        ret = opensrt_neterrno(h);
+    }
+
+    return ret;
+}
+
+static int opensrt_close(URLContext *h)
+{
+    SRTContext *s = h->priv_data;
+
+    srt_close(s->fd);
+
+    srt_epoll_release(s->eid);
+
+    srt_cleanup();
+
+    return 0;
+}
+
+static int opensrt_get_file_handle(URLContext *h)
+{
+    SRTContext *s = h->priv_data;
+    return s->fd;
+}
+
+static const AVClass opensrt_class = {
+    .class_name = "opensrt",
+    .item_name  = av_default_item_name,
+    .option     = opensrt_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const URLProtocol ff_opensrt_protocol = {
+    .name                = "srt",
+    .url_open            = opensrt_open,
+    .url_read            = opensrt_read,
+    .url_write           = opensrt_write,
+    .url_close           = opensrt_close,
+    .url_get_file_handle = opensrt_get_file_handle,
+    .priv_data_size      = sizeof(SRTContext),
+    .flags               = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class     = &opensrt_class,
+};
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index 8ea5c0e..c2ae72d 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -38,6 +38,7 @@  extern const URLProtocol ff_mmsh_protocol;
 extern const URLProtocol ff_mmst_protocol;
 extern const URLProtocol ff_md5_protocol;
 extern const URLProtocol ff_pipe_protocol;
+extern const URLProtocol ff_opensrt_protocol;
 extern const URLProtocol ff_rtmp_protocol;
 extern const URLProtocol ff_rtmpe_protocol;
 extern const URLProtocol ff_rtmps_protocol;