obs-ffmpeg: Fix reconnection for SRT protocol

This is the first part of a fix for reconnection issues with SRT
protocol.
This part implements the logic on the side of the protocol.
The fixes are as follows:
- a cleanup of return values of the various functions used. The main
libsrt_open function returns OBS_OUTPUT_XXX enum members. But the other
functions return now only AVERROR(xxx).
- an explicit check of the socket state before returning
OBS_OUTPUT_SUCCESS. This fixes an issue due to relying on socket
non-blocking mode; in caller mode, the srt_connect function will return
a success even before a connection has succeeded since it relies on
notifications by srt_epoll_wait. This would prompt obs UI to display a
successful reconnection in spite of a failure.

Signed-off-by: pkv <pkv@obsproject.com>
This commit is contained in:
pkv
2025-05-13 20:43:18 +02:00
committed by Ryan Foster
parent 3ae9d6a106
commit 8d9bd44179
+160 -63
View File
@@ -33,6 +33,11 @@
enum SRTMode { SRT_MODE_CALLER = 0, SRT_MODE_LISTENER = 1, SRT_MODE_RENDEZVOUS = 2 };
struct srt_err {
int obs_output_error_number;
int srt_error_code;
};
typedef struct SRTContext {
SRTSOCKET fd;
int eid;
@@ -77,23 +82,56 @@ typedef struct SRTContext {
int linger;
int tsbpd;
double time; // time in s in order to post logs at definite intervals
struct srt_err last_error;
} SRTContext;
#define OBS_OUTPUT_TIMEDOUT -10
static int libsrt_neterrno(URLContext *h)
{
SRTContext *s = (SRTContext *)h->priv_data;
int os_errno;
int errj;
int err = srt_getlasterror(&os_errno);
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: %s", srt_getlasterror_str());
if (err == SRT_EASYNCRCV || err == SRT_EASYNCSND)
s->last_error.srt_error_code = err;
switch (err) {
case SRT_EASYNCRCV:
case SRT_EASYNCSND:
return AVERROR(EAGAIN);
if (err == SRT_ECONNREJ) {
int errj = srt_getrejectreason(s->fd);
if (errj == SRT_REJ_BADSECRET)
case SRT_ECONNREJ:
errj = srt_getrejectreason(s->fd);
if (errj == SRT_REJ_BADSECRET) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Wrong password");
else
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
} else {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Connection rejected, %s",
srt_rejectreason_str(errj));
s->last_error.obs_output_error_number = OBS_OUTPUT_CONNECT_FAILED;
}
break;
case SRT_ECONNSOCK:
s->last_error.obs_output_error_number = OBS_OUTPUT_CONNECT_FAILED;
break;
case SRT_ESECFAIL:
s->last_error.obs_output_error_number = OBS_OUTPUT_INVALID_STREAM;
break;
case SRT_ECONNLOST:
case SRT_ENOCONN:
case SRT_ECONNFAIL:
s->last_error.obs_output_error_number = OBS_OUTPUT_DISCONNECTED;
break;
case SRT_ENOSERVER:
case SRT_ETIMEOUT:
s->last_error.obs_output_error_number = OBS_OUTPUT_TIMEDOUT;
errj = srt_getrejectreason(s->fd);
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Connection timed out, %s",
srt_rejectreason_str(errj));
break;
default:
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
break;
}
return os_errno ? AVERROR(os_errno) : AVERROR_UNKNOWN;
@@ -102,11 +140,11 @@ static int libsrt_neterrno(URLContext *h)
static int libsrt_getsockopt(URLContext *h, SRTSOCKET fd, SRT_SOCKOPT optname, const char *optnamestr, void *optval,
int *optlen)
{
UNUSED_PARAMETER(h);
SRTContext *s = (SRTContext *)h->priv_data;
if (srt_getsockopt(fd, 0, optname, optval, optlen) < 0) {
blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt]: Failed to get option %s on socket: %s", optnamestr,
srt_getlasterror_str());
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
return AVERROR(EIO);
}
return 0;
@@ -119,7 +157,11 @@ static int libsrt_socket_nonblock(SRTSOCKET socket, int enable)
ret = srt_setsockopt(socket, 0, SRTO_SNDSYN, &blocking, sizeof(blocking));
if (ret < 0)
return ret;
return srt_setsockopt(socket, 0, SRTO_RCVSYN, &blocking, sizeof(blocking));
ret = srt_setsockopt(socket, 0, SRTO_RCVSYN, &blocking, sizeof(blocking));
if (ret < 0)
return ret;
return 0;
}
static int libsrt_epoll_create(URLContext *h, SRTSOCKET fd, int write)
@@ -138,34 +180,27 @@ static int libsrt_epoll_create(URLContext *h, SRTSOCKET fd, int write)
static int libsrt_network_wait_fd(URLContext *h, int eid, int write)
{
int ret, len = 1, errlen = 1;
SRTSOCKET ready[1];
SRTSOCKET error[1];
int err;
SRTSOCKET ready[1] = {SRT_INVALID_SOCK};
SRTSOCKET error[1] = {SRT_INVALID_SOCK};
SRTContext *s = (SRTContext *)h->priv_data;
if (write) {
ret = srt_epoll_wait(eid, error, &errlen, ready, &len, POLLING_TIME, 0, 0, 0, 0);
err = srt_epoll_wait(eid, error, &errlen, ready, &len, POLLING_TIME, 0, 0, 0, 0);
} else {
ret = srt_epoll_wait(eid, ready, &len, error, &errlen, POLLING_TIME, 0, 0, 0, 0);
err = srt_epoll_wait(eid, ready, &len, error, &errlen, POLLING_TIME, 0, 0, 0, 0);
}
if (len == 1 && errlen == 1 && s->mode == SRT_MODE_CALLER) {
/* Socket reported in wsock AND rsock signifies an error. */
int reason = srt_getrejectreason(*ready);
if (reason == SRT_REJ_BADSECRET || reason == SRT_REJ_UNSECURE || reason == SRT_REJ_TIMEOUT) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt]: Connection rejected, wrong password or invalid URL");
return OBS_OUTPUT_INVALID_STREAM;
} else {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Connection rejected, %s",
srt_rejectreason_str(reason));
}
}
if (ret < 0) {
if (srt_getlasterror(NULL) == SRT_ETIMEOUT)
if (err < 0) {
s->last_error.srt_error_code = err;
if (srt_getlasterror(NULL) == SRT_ETIMEOUT) {
s->last_error.obs_output_error_number = OBS_OUTPUT_TIMEDOUT;
ret = AVERROR(EAGAIN);
else
} else {
ret = libsrt_neterrno(h);
}
} else {
ret = errlen ? AVERROR(EIO) : 0;
ret = 0;
}
return ret;
}
@@ -179,20 +214,25 @@ int check_interrupt(AVIOInterruptCB *cb)
static int libsrt_network_wait_fd_timeout(URLContext *h, int eid, int write, int64_t timeout, AVIOInterruptCB *int_cb)
{
SRTContext *s = (SRTContext *)h->priv_data;
int ret;
int64_t wait_start = 0;
while (1) {
if (check_interrupt(int_cb))
if (check_interrupt(int_cb)) {
s->last_error.obs_output_error_number = OBS_OUTPUT_CONNECT_FAILED;
return AVERROR_EXIT;
}
ret = libsrt_network_wait_fd(h, eid, write);
if (ret != AVERROR(EAGAIN))
return ret;
if (timeout > 0) {
if (!wait_start)
if (!wait_start) {
wait_start = av_gettime_relative();
else if (av_gettime_relative() - wait_start > timeout)
} else if (av_gettime_relative() - wait_start > timeout) {
s->last_error.obs_output_error_number = OBS_OUTPUT_TIMEDOUT;
return AVERROR(ETIMEDOUT);
}
}
}
}
@@ -205,6 +245,7 @@ static int libsrt_listen(int eid, SRTSOCKET fd, const struct sockaddr *addr, soc
/* Max streamid length plus an extra space for the terminating null character */
char streamid[513];
int streamid_len = sizeof(streamid);
if (srt_setsockopt(fd, SOL_SOCKET, SRTO_REUSEADDR, &reuse, sizeof(reuse))) {
blog(LOG_WARNING, "[obs-ffmpeg mpegts muxer / libsrt]: setsockopt(SRTO_REUSEADDR) failed");
}
@@ -221,10 +262,11 @@ static int libsrt_listen(int eid, SRTSOCKET fd, const struct sockaddr *addr, soc
ret = srt_accept(fd, NULL, NULL);
if (ret < 0)
return libsrt_neterrno(h);
if (libsrt_socket_nonblock(ret, 1) < 0)
blog(LOG_DEBUG, "[obs-ffmpeg mpegts muxer / libsrt]: libsrt_socket_nonblock failed");
if (!libsrt_getsockopt(h, ret, SRTO_STREAMID, "SRTO_STREAMID", streamid, &streamid_len))
/* Note: returned streamid_len doesn't count the terminating null character */
blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt]: Accept streamid [%s], length %d", streamid,
streamid_len);
@@ -234,6 +276,7 @@ static int libsrt_listen(int eid, SRTSOCKET fd, const struct sockaddr *addr, soc
static int libsrt_listen_connect(int eid, SRTSOCKET fd, const struct sockaddr *addr, socklen_t addrlen, int64_t timeout,
URLContext *h, int will_try_next)
{
SRTContext *s = (SRTContext *)h->priv_data;
int ret;
if (srt_connect(fd, addr, addrlen) < 0)
return libsrt_neterrno(h);
@@ -248,6 +291,12 @@ static int libsrt_listen_connect(int eid, SRTSOCKET fd, const struct sockaddr *a
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Connection to %s failed: %s", h->url,
av_err2str(ret));
}
} else {
SRT_SOCKSTATUS state = srt_getsockstate(fd);
if (state != SRTS_CONNECTED) {
s->last_error.obs_output_error_number = OBS_OUTPUT_CONNECT_FAILED;
return AVERROR_EXIT;
}
}
return ret;
}
@@ -255,11 +304,13 @@ static int libsrt_listen_connect(int eid, SRTSOCKET fd, const struct sockaddr *a
static int libsrt_setsockopt(URLContext *h, SRTSOCKET fd, SRT_SOCKOPT optname, const char *optnamestr,
const void *optval, int optlen)
{
UNUSED_PARAMETER(h);
if (srt_setsockopt(fd, 0, optname, optval, optlen) < 0) {
SRTContext *s = (SRTContext *)h->priv_data;
int ret = srt_setsockopt(fd, 0, optname, optval, optlen);
if (ret < 0) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Failed to set option %s on socket: %s", optnamestr,
srt_getlasterror_str());
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
s->last_error.srt_error_code = ret;
return AVERROR(EIO);
}
return 0;
@@ -272,12 +323,12 @@ static int libsrt_setsockopt(URLContext *h, SRTSOCKET fd, SRT_SOCKOPT optname, c
static int libsrt_set_options_post(URLContext *h, SRTSOCKET fd)
{
SRTContext *s = (SRTContext *)h->priv_data;
if ((s->inputbw >= 0 &&
libsrt_setsockopt(h, fd, SRTO_INPUTBW, "SRTO_INPUTBW", &s->inputbw, sizeof(s->inputbw)) < 0) ||
(s->oheadbw >= 0 &&
libsrt_setsockopt(h, fd, SRTO_OHEADBW, "SRTO_OHEADBW", &s->oheadbw, sizeof(s->oheadbw)) < 0)) {
return AVERROR(EIO);
int ret = libsrt_setsockopt(h, fd, SRTO_INPUTBW, "SRTO_INPUTBW", &s->inputbw, sizeof(s->inputbw));
int ret2 = libsrt_setsockopt(h, fd, SRTO_OHEADBW, "SRTO_OHEADBW", &s->oheadbw, sizeof(s->oheadbw));
if ((s->inputbw >= 0 && ret < 0) || (s->oheadbw >= 0 && ret2 < 0)) {
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
s->last_error.srt_error_code = srt_getlasterror(NULL);
return OBS_OUTPUT_ERROR;
}
return 0;
}
@@ -365,6 +416,8 @@ static int libsrt_set_options_pre(URLContext *h, SRTSOCKET fd)
libsrt_setsockopt(h, fd, SRTO_SENDER, "SRTO_SENDER", &yes, sizeof(yes)) < 0) ||
(s->tsbpd >= 0 &&
libsrt_setsockopt(h, fd, SRTO_TSBPDMODE, "SRTO_TSBPDMODE", &s->tsbpd, sizeof(s->tsbpd)) < 0)) {
s->last_error.srt_error_code = srt_getlasterror(NULL);
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
return AVERROR(EIO);
}
@@ -372,8 +425,11 @@ static int libsrt_set_options_pre(URLContext *h, SRTSOCKET fd)
struct linger lin;
lin.l_linger = s->linger;
lin.l_onoff = lin.l_linger > 0 ? 1 : 0;
if (libsrt_setsockopt(h, fd, SRTO_LINGER, "SRTO_LINGER", &lin, sizeof(lin)) < 0)
if (libsrt_setsockopt(h, fd, SRTO_LINGER, "SRTO_LINGER", &lin, sizeof(lin)) < 0) {
s->last_error.srt_error_code = srt_getlasterror(NULL);
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
return AVERROR(EIO);
}
}
return 0;
}
@@ -394,11 +450,15 @@ static int libsrt_setup(URLContext *h, const char *uri)
struct sockaddr_in la;
av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, path, sizeof(path), uri);
if (strcmp(proto, "srt")) // should not happen !
if (strcmp(proto, "srt")) {
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
return AVERROR(EINVAL);
}
if (port <= 0 || port >= 65536) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Port missing in uri");
return OBS_OUTPUT_CONNECT_FAILED;
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
return AVERROR(EINVAL);
}
p = strchr(uri, '?');
if (p) {
@@ -426,26 +486,30 @@ static int libsrt_setup(URLContext *h, const char *uri)
gai_strerror(ret)
#endif
);
return OBS_OUTPUT_CONNECT_FAILED;
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
return AVERROR(EIO);
}
cur_ai = ai;
if (s->mode == SRT_MODE_RENDEZVOUS) {
if (s->localip == NULL || s->localport == NULL) {
blog(LOG_ERROR, "Invalid adapter configuration\n");
return OBS_OUTPUT_CONNECT_FAILED;
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
return AVERROR(EIO);
}
blog(LOG_DEBUG, "[obs-ffmpeg mpegts muxer / libsrt]: Adapter options %s:%s\n", s->localip,
s->localport);
int lp = strtol(s->localport, NULL, 10);
if (lp <= 0 || lp >= 65536) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: Local port missing in URL\n");
return OBS_OUTPUT_CONNECT_FAILED;
s->last_error.obs_output_error_number = OBS_OUTPUT_BAD_PATH;
return AVERROR(EIO);
}
la.sin_family = AF_INET;
la.sin_port = htons(port);
inet_pton(AF_INET, s->localip, &la.sin_addr.s_addr);
}
restart:
fd = srt_create_socket();
@@ -471,19 +535,26 @@ restart:
if (s->mode == SRT_MODE_LISTENER) {
int read_eid = ret = libsrt_epoll_create(h, fd, 0);
if (ret < 0)
if (ret < 0) {
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
goto fail1;
}
// multi-client
ret = libsrt_listen(read_eid, fd, cur_ai->ai_addr, (socklen_t)cur_ai->ai_addrlen, h, s->listen_timeout);
srt_epoll_release(read_eid);
if (ret < 0)
if (ret < 0) {
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
goto fail1;
}
srt_close(fd);
fd = ret;
} else {
int write_eid = ret = libsrt_epoll_create(h, fd, 1);
if (ret < 0)
if (ret < 0) {
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
goto fail1;
}
if (s->mode == SRT_MODE_RENDEZVOUS) {
if (srt_bind(fd, (struct sockaddr *)&la, sizeof(struct sockaddr_in))) {
ret = libsrt_neterrno(h);
@@ -496,10 +567,14 @@ restart:
open_timeout, h, !!cur_ai->ai_next);
srt_epoll_release(write_eid);
if (ret < 0) {
if (ret == AVERROR_EXIT)
s->last_error.srt_error_code = srt_getlasterror(NULL);
if (ret == AVERROR_EXIT) {
s->last_error.obs_output_error_number = OBS_OUTPUT_CONNECT_FAILED;
goto fail1;
else
} else {
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
goto fail;
}
}
}
if ((ret = libsrt_set_options_post(h, fd)) < 0) {
@@ -514,9 +589,12 @@ restart:
if (packet_size > 0)
h->max_packet_size = packet_size;
ret = eid = libsrt_epoll_create(h, fd, 1 /*flags & AVIO_FLAG_WRITE*/);
if (eid < 0)
eid = libsrt_epoll_create(h, fd, 1);
if (eid < 0) {
ret = eid;
s->last_error.obs_output_error_number = OBS_OUTPUT_ERROR;
goto fail1;
}
s->fd = fd;
s->eid = eid;
@@ -553,8 +631,8 @@ static void libsrt_set_defaults(SRTContext *s)
s->ffs = -1;
s->ipttl = -1;
s->iptos = -1;
s->inputbw = -1;
s->oheadbw = -1;
s->inputbw = 0;
s->oheadbw = 25;
s->latency = -1;
s->rcvlatency = -1;
s->peerlatency = -1;
@@ -573,6 +651,7 @@ static void libsrt_set_defaults(SRTContext *s)
s->tsbpd = -1;
}
static int libsrt_close(URLContext *h);
static int libsrt_open(URLContext *h, const char *uri)
{
SRTContext *s = (SRTContext *)h->priv_data;
@@ -582,12 +661,15 @@ static int libsrt_open(URLContext *h, const char *uri)
if (srt_startup() < 0) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: libsrt failed to load");
return OBS_OUTPUT_CONNECT_FAILED;
return OBS_OUTPUT_ERROR;
} else {
blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt]: libsrt version %s loaded", SRT_VERSION_STRING);
}
libsrt_set_defaults(s);
s->last_error.obs_output_error_number = OBS_OUTPUT_SUCCESS;
s->last_error.srt_error_code = SRT_SUCCESS;
/* SRT options (srt/srt.h) */
p = strchr(uri, '?');
if (p) {
@@ -666,7 +748,7 @@ static int libsrt_open(URLContext *h, const char *uri)
} else if (!strcmp(buf, "rendezvous")) {
s->mode = SRT_MODE_RENDEZVOUS;
} else {
ret = AVERROR(EINVAL);
ret = OBS_OUTPUT_ERROR;
goto err;
}
}
@@ -686,7 +768,7 @@ static int libsrt_open(URLContext *h, const char *uri)
av_freep(&s->streamid);
s->streamid = av_strdup(buf);
if (!s->streamid) {
ret = AVERROR(ENOMEM);
ret = OBS_OUTPUT_ERROR;
goto err;
}
}
@@ -694,7 +776,7 @@ static int libsrt_open(URLContext *h, const char *uri)
av_freep(&s->smoother);
s->smoother = av_strdup(buf);
if (!s->smoother) {
ret = AVERROR(ENOMEM);
ret = OBS_OUTPUT_ERROR;
goto err;
}
}
@@ -707,7 +789,7 @@ static int libsrt_open(URLContext *h, const char *uri)
} else if (!strcmp(buf, "file")) {
s->transtype = SRTT_FILE;
} else {
ret = AVERROR(EINVAL);
ret = OBS_OUTPUT_ERROR;
goto err;
}
}
@@ -722,8 +804,10 @@ static int libsrt_open(URLContext *h, const char *uri)
}
}
ret = libsrt_setup(h, uri);
if (ret < 0)
if (ret < 0) {
ret = s->last_error.obs_output_error_number;
goto err;
}
#ifdef _WIN32
struct timeb timebuffer;
@@ -735,7 +819,20 @@ static int libsrt_open(URLContext *h, const char *uri)
s->time = (double)timesp.tv_sec + 0.000000001 * (double)timesp.tv_nsec;
#endif
return 0;
/* In caller mode, srt_connect won't fail even if there is no established connection in non-blocking mode, so
* we explicitly check for the socket status before okaying OBS_OUTPUT_SUCCESS.
*/
SRT_SOCKSTATUS state = srt_getsockstate(s->fd);
if (state != SRTS_CONNECTED) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt]: SRT socket not connected after open (state=%d)",
state);
libsrt_close(h);
if (s->last_error.obs_output_error_number != OBS_OUTPUT_SUCCESS)
return s->last_error.obs_output_error_number;
else
return OBS_OUTPUT_CONNECT_FAILED;
}
return OBS_OUTPUT_SUCCESS;
err:
av_freep(&s->smoother);