srt.c 11.4 KB
Newer Older
1 2 3
/*****************************************************************************
 * srt.c: SRT (Secure Reliable Transport) input module
 *****************************************************************************
4 5
 * Copyright (C) 2017-2018, Collabora Ltd.
 * Copyright (C) 2018, Haivision Systems Inc.
6 7
 *
 * Authors: Justin Kim <justin.kim@collabora.com>
8
 *          Roman Diouskine <rdiouskine@haivision.com>
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 *
 * This program 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.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_interrupt.h>
31
#include <vlc_fs.h>
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include <vlc_plugin.h>
#include <vlc_access.h>

#include <vlc_network.h>
#include <vlc_url.h>

#include <srt/srt.h>

/* libsrt defines default packet size as 1316 internally
 * so srt module takes same value. */
#define SRT_DEFAULT_CHUNK_SIZE 1316
/* The default timeout is -1 (infinite) */
#define SRT_DEFAULT_POLL_TIMEOUT -1
/* The default latency is 125
 * which uses srt library internally */
#define SRT_DEFAULT_LATENCY 125
48 49 50 51 52 53 54 55 56 57
/* Crypto key length in bytes. */
#define SRT_KEY_LENGTH_TEXT N_("Crypto key length in bytes")
#define SRT_DEFAULT_KEY_LENGTH 16
static const int srt_key_lengths[] = {
    16, 24, 32,
};

static const char *const srt_key_length_names[] = {
    N_("16 bytes"), N_("24 bytes"), N_("32 bytes"),
};
58

59
typedef struct
60 61 62
{
    SRTSOCKET   sock;
    int         i_poll_id;
63 64
    vlc_mutex_t lock;
    bool        b_interrupted;
Justin Kim's avatar
Justin Kim committed
65 66
    char       *psz_host;
    int         i_port;
67
} stream_sys_t;
68 69 70 71 72

static void srt_wait_interrupted(void *p_data)
{
    stream_t *p_stream = p_data;
    stream_sys_t *p_sys = p_stream->p_sys;
73 74 75

    vlc_mutex_lock( &p_sys->lock );
    if ( p_sys->i_poll_id >= 0 &&  p_sys->sock != SRT_INVALID_SOCK )
76
    {
77 78 79 80 81 82 83
        p_sys->b_interrupted = true;

        msg_Dbg( p_stream, "Waking up srt_epoll_wait");

        /* Removing all socket descriptors from the monitoring list
         * wakes up SRT's threads. We only have one to remove. */
        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
84
    }
85
    vlc_mutex_unlock( &p_sys->lock );
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
}

static int Control(stream_t *p_stream, int i_query, va_list args)
{
    int i_ret = VLC_SUCCESS;

    switch( i_query )
    {
        case STREAM_CAN_SEEK:
        case STREAM_CAN_FASTSEEK:
        case STREAM_CAN_PAUSE:
        case STREAM_CAN_CONTROL_PACE:
            *va_arg( args, bool * ) = false;
            break;
        case STREAM_GET_PTS_DELAY:
            *va_arg( args, int64_t * ) = INT64_C(1000)
                   * var_InheritInteger(p_stream, "network-caching");
            break;
        default:
            i_ret = VLC_EGENERIC;
            break;
    }

    return i_ret;
}

112
static bool srt_schedule_reconnect(stream_t *p_stream)
113
{
114 115 116
    int         i_latency;
    int         stat;
    char        *psz_passphrase = NULL;
117 118 119 120

    struct addrinfo hints = {
        .ai_socktype = SOCK_DGRAM,
    }, *res = NULL;
121

122 123
    stream_sys_t *p_sys = p_stream->p_sys;
    bool failed = false;
124

Justin Kim's avatar
Justin Kim committed
125
    stat = vlc_getaddrinfo( p_sys->psz_host, p_sys->i_port, &hints, &res );
126 127 128
    if ( stat )
    {
        msg_Err( p_stream, "Cannot resolve [%s]:%d (reason: %s)",
Justin Kim's avatar
Justin Kim committed
129 130
                 p_sys->psz_host,
                 p_sys->i_port,
131 132
                 gai_strerror( stat ) );

133 134 135 136 137 138 139 140 141
        failed = true;
        goto out;
    }

    /* Always start with a fresh socket */
    if (p_sys->sock != SRT_INVALID_SOCK)
    {
        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
        srt_close( p_sys->sock );
142 143 144
    }

    p_sys->sock = srt_socket( res->ai_family, SOCK_DGRAM, 0 );
145
    if ( p_sys->sock == SRT_INVALID_SOCK )
146 147
    {
        msg_Err( p_stream, "Failed to open socket." );
148 149
        failed = true;
        goto out;
150 151 152
    }

    /* Make SRT non-blocking */
153 154 155 156
    srt_setsockopt( p_sys->sock, 0, SRTO_SNDSYN,
        &(bool) { false }, sizeof( bool ) );
    srt_setsockopt( p_sys->sock, 0, SRTO_RCVSYN,
        &(bool) { false }, sizeof( bool ) );
157 158

    /* Make sure TSBPD mode is enable (SRT mode) */
159 160 161 162 163 164
    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDMODE,
        &(int) { 1 }, sizeof( int ) );

    /* This is an access module so it is always a receiver */
    srt_setsockopt( p_sys->sock, 0, SRTO_SENDER,
        &(int) { 0 }, sizeof( int ) );
165 166

    /* Set latency */
167 168 169
    i_latency = var_InheritInteger( p_stream, "latency" );
    srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDDELAY,
        &i_latency, sizeof( int ) );
170

171
    psz_passphrase = var_InheritString( p_stream, "passphrase" );
172 173 174 175 176 177 178 179 180
    if ( psz_passphrase != NULL && psz_passphrase[0] != '\0')
    {
        int i_key_length = var_InheritInteger( p_stream, "key-length" );
        srt_setsockopt( p_sys->sock, 0, SRTO_PASSPHRASE,
            psz_passphrase, strlen( psz_passphrase ) );
        srt_setsockopt( p_sys->sock, 0, SRTO_PBKEYLEN,
            &i_key_length, sizeof( int ) );
    }

181 182 183 184 185
    srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
        &(int) { SRT_EPOLL_ERR | SRT_EPOLL_IN });

    /* Schedule a connect */
    msg_Dbg( p_stream, "Schedule SRT connect (dest addresss: %s, port: %d).",
Justin Kim's avatar
Justin Kim committed
186
        p_sys->psz_host, p_sys->i_port);
187 188 189

    stat = srt_connect( p_sys->sock, res->ai_addr, res->ai_addrlen);
    if ( stat == SRT_ERROR )
190
    {
191 192 193
        msg_Err( p_stream, "Failed to connect to server (reason: %s)",
                 srt_getlasterror_str() );
        failed = true;
194 195
    }

196 197
out:
    if (failed && p_sys->sock != SRT_INVALID_SOCK)
198
    {
199 200 201
        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
        srt_close(p_sys->sock);
        p_sys->sock = SRT_INVALID_SOCK;
202 203
    }

204 205
    freeaddrinfo( res );
    free( psz_passphrase );
206

207 208
    return !failed;
}
209

210 211 212 213 214
static block_t *BlockSRT(stream_t *p_stream, bool *restrict eof)
{
    stream_sys_t *p_sys = p_stream->p_sys;
    int i_chunk_size = var_InheritInteger( p_stream, "chunk-size" );
    int i_poll_timeout = var_InheritInteger( p_stream, "poll-timeout" );
215

216
    if ( vlc_killed() )
217
    {
218 219
        /* We are told to stop. Stop. */
        return NULL;
220 221
    }

222 223 224 225 226
    block_t *pkt = block_Alloc( i_chunk_size );
    if ( unlikely( pkt == NULL ) )
    {
        return NULL;
    }
227

228
    vlc_interrupt_register( srt_wait_interrupted, p_stream);
229

230 231 232 233 234
    SRTSOCKET ready[1];
    int readycnt = 1;
    while ( srt_epoll_wait( p_sys->i_poll_id,
        ready, &readycnt, 0, 0,
        i_poll_timeout, NULL, 0, NULL, 0 ) >= 0)
235
    {
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
        if ( readycnt < 0  || ready[0] != p_sys->sock )
        {
            /* should never happen, force recovery */
            srt_close(p_sys->sock);
            p_sys->sock = SRT_INVALID_SOCK;
        }

        switch( srt_getsockstate( p_sys->sock ) )
        {
            case SRTS_CONNECTED:
                /* Good to go */
                break;
            case SRTS_BROKEN:
            case SRTS_NONEXIST:
            case SRTS_CLOSED:
                /* Failed. Schedule recovery. */
                if ( !srt_schedule_reconnect( p_stream ) )
                    msg_Err( p_stream, "Failed to schedule connect" );
                /* Fall-through */
            default:
                /* Not ready */
                continue;
        }

        int stat = srt_recvmsg( p_sys->sock,
            (char *)pkt->p_buffer, i_chunk_size );
        if ( stat > 0 )
        {
            pkt->i_buffer = stat;
            goto out;
        }

        msg_Err( p_stream, "failed to receive packet, set EOS (reason: %s)",
            srt_getlasterror_str() );
        *eof = true;
        break;
272 273
    }

274 275 276 277 278 279 280 281 282 283 284 285 286 287
    /* if the poll reports errors for any reason at all
     * including a timeout, or there is a read error,
     * we skip the turn.
     */

    block_Release(pkt);
    pkt = NULL;

out:
    vlc_interrupt_unregister();

    /* Re-add the socket to the poll if we were interrupted */
    vlc_mutex_lock( &p_sys->lock );
    if ( p_sys->b_interrupted )
288
    {
289 290 291
        srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
            &(int) { SRT_EPOLL_ERR | SRT_EPOLL_IN } );
        p_sys->b_interrupted = false;
292
    }
293 294 295 296 297 298 299 300 301
    vlc_mutex_unlock( &p_sys->lock );

    return pkt;
}

static int Open(vlc_object_t *p_this)
{
    stream_t     *p_stream = (stream_t*)p_this;
    stream_sys_t *p_sys = NULL;
Justin Kim's avatar
Justin Kim committed
302
    vlc_url_t     parsed_url = { 0 };
303 304 305 306 307 308

    p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
    if( unlikely( p_sys == NULL ) )
        return VLC_ENOMEM;

    srt_startup();
309

310
    vlc_mutex_init( &p_sys->lock );
311

312 313
    p_stream->p_sys = p_sys;

Justin Kim's avatar
Justin Kim committed
314 315 316 317 318 319 320 321 322 323 324 325
    if ( vlc_UrlParse( &parsed_url, p_stream->psz_url ) == -1 )
    {
        msg_Err( p_stream, "Failed to parse input URL (%s)",
            p_stream->psz_url );
        goto failed;
    }

    p_sys->psz_host = strdup( parsed_url.psz_host );
    p_sys->i_port = parsed_url.i_port;

    vlc_UrlClean( &parsed_url );

326 327
    p_sys->i_poll_id = srt_epoll_create();
    if ( p_sys->i_poll_id == -1 )
328
    {
329 330
        msg_Err( p_stream, "Failed to create poll id for SRT socket." );
        goto failed;
331 332
    }

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
    if ( !srt_schedule_reconnect( p_stream ) )
    {
        msg_Err( p_stream, "Failed to schedule connect");

        goto failed;
    }

    p_stream->pf_block = BlockSRT;
    p_stream->pf_control = Control;

    return VLC_SUCCESS;

failed:
    vlc_mutex_destroy( &p_sys->lock );

    if ( p_sys != NULL )
    {
        if ( p_sys->sock != -1 ) srt_close( p_sys->sock );
        if ( p_sys->i_poll_id != -1 ) srt_epoll_release( p_sys->i_poll_id );

Justin Kim's avatar
Justin Kim committed
353 354
        free( p_sys->psz_host );

355 356 357
        vlc_obj_free( p_this, p_sys );
        p_stream->p_sys = NULL;
    }
358

359 360 361 362 363 364 365 366
    return VLC_EGENERIC;
}

static void Close(vlc_object_t *p_this)
{
    stream_t     *p_stream = (stream_t*)p_this;
    stream_sys_t *p_sys = p_stream->p_sys;

367
    if ( p_sys )
368
    {
369 370 371 372
        vlc_mutex_destroy( &p_sys->lock );

        srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
        srt_close( p_sys->sock );
373
        srt_epoll_release( p_sys->i_poll_id );
Justin Kim's avatar
Justin Kim committed
374 375

        free( p_sys->psz_host );
376 377 378

        vlc_obj_free( p_this, p_sys );
        p_stream->p_sys = NULL;
379 380
    }

381
    srt_cleanup();
382 383 384 385 386 387 388 389 390 391 392 393
}

/* Module descriptor */
vlc_module_begin ()
    set_shortname( N_("SRT") )
    set_description( N_("SRT input") )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_ACCESS )

    add_integer( "chunk-size", SRT_DEFAULT_CHUNK_SIZE,
            N_("SRT chunk size (bytes)"), NULL, true )
    add_integer( "poll-timeout", SRT_DEFAULT_POLL_TIMEOUT,
Michał's avatar
Michał committed
394
            N_("Return poll wait after timeout milliseconds (-1 = infinite)"), NULL, true )
395
    add_integer( "latency", SRT_DEFAULT_LATENCY, N_("SRT latency (ms)"), NULL, true )
396
    add_password("passphrase", "", N_("Password for stream encryption"), NULL)
397 398 399
    add_integer( "key-length", SRT_DEFAULT_KEY_LENGTH,
            SRT_KEY_LENGTH_TEXT, SRT_KEY_LENGTH_TEXT, false )
        change_integer_list( srt_key_lengths, srt_key_length_names )
400 401 402 403 404 405

    set_capability( "access", 0 )
    add_shortcut( "srt" )

    set_callbacks( Open, Close )
vlc_module_end ()