Opened 9 months ago

Last modified 8 weeks ago

#8372 new defect

SRT Handling Does Not Clean Up Connection

Reported by: whitik Owned by:
Priority: normal Component: avformat
Version: unspecified Keywords: libsrt
Cc: Blocked By:
Blocking: Reproduced by developer: no
Analyzed by developer: no

Description

Using the FFMPEG in C++ for SRT listening connections.

If I use:

avformat_open_input(&context1, "srt://0.0.0.0:9000/mode=listener", nullptr, &options);
avformat_open_input(&context2, "srt://0.0.0.0:9005/mode=listener", nullptr, &options);

to open two listeners, and then use the Larix app to stream to the first, it works. i.e. av_read_frame gives back valid frames.

If the Larix app then stops streaming (a 'UMSG_SHUTDOWN' happens in the SRT library), then no error is returned from av_read_frame. However, if I set the rw_timeout option for SRT, an error happens then.

Issue: If I then close and reopen the context (avformat_close_input and avformat_close_input) an error is received saying that the socket is already open. There is no way to restart streaming.

(Note: If I don't recreate the context, streaming also doesn't work since 'no space to hold' errors are then received - the SRT library isn't initialized properly for the new connection.

The issue seems to do with properly closing all the resources with the context (i.e. the listener and connection socket resources). Reconnection only works when a single context is being used - because of usage counts in the library, closing the only context happens to completely restart the SRT library with srt_cleanup() / srt_startup().

Change History (5)

comment:1 Changed 9 months ago by whitik

I meant to say (avformat_close_input and avformat_open_input) to recreate the context.

comment:2 Changed 9 months ago by whitik

I think I've spotted the problem.

In libsrt.c, in libsrt_setup, in line 424 the context fd (the listener socket) is overwritten with the result of libsrt_listen (the new connection socket). So, when srt_close is called, it is not called with the listener socket.

comment:3 Changed 9 months ago by whitik

I've fixed the problem, at least with my usage. I changed the libsrt_setup function in libsrt.c as follows:

static int libsrt_setup(URLContext *h, const char *uri, int flags)
{
  struct addrinfo hints = {0}, *ai, *cur_ai;
  int port;
  SRTSOCKET fd = SRT_INVALID_SOCK; // Socket that will be created on this side (will be stored in context).

  // whitik- added socket to hold original socket (for listener).
  SRTSOCKET listenerSock = SRT_INVALID_SOCK;

  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;

  av_log(h, AV_LOG_INFO, "libsrt_setup\n");

  eid = srt_epoll_create();
  if (eid < 0)
    return libsrt_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:

  // whitik - create a socket. e.g. This will be the listener socket. Will be stored in context.
  fd = srt_create_socket(); // srt_socket(cur_ai->ai_family, cur_ai->ai_socktype, 0);
  if (fd < 0)
  {
    ret = libsrt_neterrno(h);
    goto fail;
  }

  if ((ret = libsrt_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
    // whitik - this returns the connection-side socket, if one has connected.

    if ((ret = libsrt_listen(s->eid, fd, cur_ai->ai_addr, cur_ai->ai_addrlen, h, open_timeout / 1000)) < 0)
      goto fail1;
    // whitik - make sure we remember original listener socket, since we need to close this at the end.
    listenerSock = fd;
    fd = ret; // .. replace socket we're going to read from as context socket, but listenerSock is original one.
  }
  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 = libsrt_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 = libsrt_set_options_post(h, fd)) < 0)
  {
    goto fail;
  }

  if (flags & AVIO_FLAG_WRITE)
  {
    int packet_size = 0;
    int optlen = sizeof(packet_size);
    ret = libsrt_getsockopt(h, fd, SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE", &packet_size, &optlen);
    if (ret < 0)
      goto fail1;
    if (packet_size > 0)
      h->max_packet_size = packet_size;
  }

  h->is_streamed = 1;
  s->fd = fd;
  s->listenerSock = listenerSock; // whitik - Keep original socket in context for closing.

  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;
}

Note the use of 'listenerSock' which stores the listener socket separately in the context to fd which is the 'accept'ed connection socket. In 'libsrt_close', 's->listenerSock' is then closed in addition to s->fd.

comment:4 Changed 8 weeks ago by pkv

consider submitting a patch to ffmpeg-devel list

comment:5 Changed 8 weeks ago by cehoyos

  • Keywords libsrt added; SRT Reconnection removed
Note: See TracTickets for help on using tickets.