access.c 16.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/*****************************************************************************
 * access.c: RTMP input.
 *****************************************************************************
 * Copyright (C) URJC - LADyR - Luis Lopez Fernandez
 *
 * Author: Miguel Angel Cabrera Moya
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc/vlc.h>
#include <vlc_access.h>

#include <vlc_network.h> /* DOWN: #include <network.h> */
#include <vlc_url.h>
#include <vlc_block.h>

#include "rtmp_amf_flv.h"

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
#define CACHING_TEXT N_("Caching value in ms")
#define CACHING_LONGTEXT N_( \
    "Caching value for RTMP streams. This " \
    "value should be set in milliseconds." )

static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

vlc_module_begin();
    set_description( _("RTMP input") );
    set_shortname( _("RTMP") );
    set_category( CAT_INPUT );
    set_subcategory( SUBCAT_INPUT_ACCESS );

    add_integer( "rtmp-caching", DEFAULT_PTS_DELAY / 1000, NULL, CACHING_TEXT,
57
                 CACHING_LONGTEXT, true );
58

59
    set_capability( "access", 10 );
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    set_callbacks( Open, Close );
    add_shortcut( "rtmp" );
vlc_module_end();


/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int Read( access_t *, uint8_t *, size_t ); /*DOWN: last parameter int */
static int Seek( access_t *, int64_t );
static int Control( access_t *, int, va_list );

static void ThreadControl( vlc_object_t * );

/*****************************************************************************
 * Open: open the rtmp connection
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    access_t *p_access = (access_t *) p_this;
    access_sys_t *p_sys;
    char *psz, *p; 
    int length_path, length_media_name;
    int i;

    STANDARD_READ_ACCESS_INIT

    /* Parse URI - remove spaces */
    p = psz = strdup( p_access->psz_path );
    while( (p = strchr( p, ' ' )) != NULL )
        *p = '+';
    vlc_UrlParse( &p_sys->url, psz, 0 );
    free( psz );

    if( !p_access->psz_access ||
        strncmp( p_access->psz_access, "rtmp", 4 ))
    {   
Ilkka Ollakka's avatar
Ilkka Ollakka committed
97 98 99 100
         msg_Warn( p_access, "invalid protocol" );
         vlc_UrlClean( &p_sys->url );
         free( p_sys );
         return VLC_EGENERIC;
101 102 103 104 105
    }

    if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
    {
         msg_Warn( p_access, "invalid host" );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
106 107 108
         vlc_UrlClean( &p_sys->url );
         free( p_sys );
         return VLC_EGENERIC;
109 110 111 112 113 114 115
    }

    if( p_sys->url.i_port <= 0 )
        p_sys->url.i_port = 1935;

    if ( p_sys->url.psz_path == NULL ) {
        msg_Warn( p_access, "invalid path" );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
116 117 118
        vlc_UrlClean( &p_sys->url );
        free( p_sys );
        return VLC_EGENERIC;
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    }

    length_path = strlen( p_sys->url.psz_path );
    length_media_name = strlen( strrchr( p_sys->url.psz_path, '/' ) ) - 1;

    p_sys->psz_application = strndup( p_sys->url.psz_path + 1, length_path - length_media_name - 2 );
    p_sys->psz_media = strdup( p_sys->url.psz_path + ( length_path - length_media_name ) );

    msg_Dbg( p_access, "rtmp: host='%s' port=%d path='%s'",
             p_sys->url.psz_host, p_sys->url.i_port, p_sys->url.psz_path );

    if( p_sys->url.psz_username && *p_sys->url.psz_username )
    {
        msg_Dbg( p_access, "      user='%s', pwd='%s'",
                 p_sys->url.psz_username, p_sys->url.psz_password );
    }

Ilkka Ollakka's avatar
Ilkka Ollakka committed
136 137 138 139 140 141 142 143 144 145 146 147 148
    p_sys->p_thread =
        vlc_object_create( p_access, sizeof( rtmp_control_thread_t ) );
    if( !p_sys->p_thread )
    {
        msg_Err( p_access, "out of memory" );
        vlc_UrlClean( &p_sys->url );
        free( p_sys );
        return VLC_EGENERIC;
    }

    vlc_object_attach( p_sys->p_thread, p_access );
    p_sys->p_thread->b_die = 0;
    p_sys->p_thread->b_error= 0;
149 150
    p_sys->p_thread->p_fifo_media = block_FifoNew();
    p_sys->p_thread->p_empty_blocks = block_FifoNew();
Ilkka Ollakka's avatar
Ilkka Ollakka committed
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    p_sys->p_thread->has_audio = 0;
    p_sys->p_thread->has_video = 0;
    p_sys->p_thread->metadata_received = 0;
    p_sys->p_thread->first_media_packet = 1;
    p_sys->p_thread->flv_tag_previous_tag_size = 0x00000000; /* FLV_TAG_FIRST_PREVIOUS_TAG_SIZE */
    for(i = 0; i < 64; i++)
    {
        memset( &p_sys->p_thread->rtmp_headers_recv[i], 0, sizeof( rtmp_packet_t ) );
        p_sys->p_thread->rtmp_headers_send[i].length_header = -1;
        p_sys->p_thread->rtmp_headers_send[i].stream_index = -1;
        p_sys->p_thread->rtmp_headers_send[i].timestamp = -1;
        p_sys->p_thread->rtmp_headers_send[i].timestamp_relative = -1;
        p_sys->p_thread->rtmp_headers_send[i].length_encoded = -1;
        p_sys->p_thread->rtmp_headers_send[i].length_body = -1;
        p_sys->p_thread->rtmp_headers_send[i].content_type = -1;
        p_sys->p_thread->rtmp_headers_send[i].src_dst = -1;
        p_sys->p_thread->rtmp_headers_send[i].body = NULL;
    }

    vlc_cond_init( p_sys->p_thread, &p_sys->p_thread->wait );
171
    vlc_mutex_init( &p_sys->p_thread->lock );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
172 173 174 175

    p_sys->p_thread->result_connect = 1;
    p_sys->p_thread->result_play = 1;

176 177
    /* Open connection */
    p_sys->fd = net_ConnectTCP( p_access, p_sys->url.psz_host, p_sys->url.i_port );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
178
    p_sys->p_thread->fd = p_sys->fd;
179 180 181 182 183 184 185 186 187 188 189 190 191
    if( p_sys->fd == -1 )
    {
        int *p_fd_listen;

        msg_Warn( p_access, "cannot connect to %s:%d", p_sys->url.psz_host, p_sys->url.i_port );
        msg_Dbg( p_access, "switching to passive mode" );

        p_sys->active = 0;

        p_fd_listen = net_ListenTCP( p_access, p_sys->url.psz_host, p_sys->url.i_port );
        if( p_fd_listen == NULL )
        {
            msg_Warn( p_access, "cannot listen to %s port %i", p_sys->url.psz_host, p_sys->url.i_port );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
192 193 194 195
            vlc_UrlClean( &p_sys->url );
            net_Close( p_sys-> fd );
            free( p_sys );
            return VLC_EGENERIC;
196 197 198 199 200 201
        }

        p_sys->fd = net_Accept( p_access, p_fd_listen, -1 );

        net_ListenClose( p_fd_listen );

Ilkka Ollakka's avatar
Ilkka Ollakka committed
202
        if( rtmp_handshake_passive( p_this ) < 0 )
203
        {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
204 205 206 207 208
            msg_Err( p_access, "Passive handshake failed");
            vlc_UrlClean( &p_sys->url );
            net_Close( p_sys-> fd );
            free( p_sys );
            return VLC_EGENERIC;
209 210 211 212 213 214 215
        }

        p_sys->p_thread->result_publish = 1;

    }
    else
    {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
216
        msg_Dbg( p_access, "using active connection");
217 218
        p_sys->active = 1;

Ilkka Ollakka's avatar
Ilkka Ollakka committed
219
        if( rtmp_handshake_active( p_this ) < 0 )
220
        {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
221 222 223 224 225
            msg_Err( p_access, "Active handshake failed");
            vlc_UrlClean( &p_sys->url );
            net_Close( p_sys-> fd );
            free( p_sys );
            return VLC_EGENERIC;
226 227 228
        }

        p_sys->p_thread->result_publish = 0;
Ilkka Ollakka's avatar
Ilkka Ollakka committed
229
    }
230

Ilkka Ollakka's avatar
Ilkka Ollakka committed
231
    if( vlc_thread_create( p_sys->p_thread, "rtmp control thread", ThreadControl,
232
                           VLC_THREAD_PRIORITY_INPUT, false ) )
Ilkka Ollakka's avatar
Ilkka Ollakka committed
233 234 235 236 237 238 239
    {
        msg_Err( p_access, "cannot spawn rtmp control thread" );
        vlc_UrlClean( &p_sys->url );
        net_Close( p_sys-> fd );
        free( p_sys );
        return VLC_EGENERIC;
    }
240

Ilkka Ollakka's avatar
Ilkka Ollakka committed
241 242 243 244
    if( p_sys->active ) 
    {
        msg_Dbg( p_access, "Activation active connection");
        if( rtmp_connect_active( p_this ) < 0)
245
        {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
246 247 248 249 250
            msg_Err( p_access, "Active connection failed");
            vlc_UrlClean( &p_sys->url );
            net_Close( p_sys-> fd );
            free( p_sys );
            return VLC_EGENERIC;
251 252 253 254 255 256 257
        }
    }

    /* Set vars for reading from fifo */
    p_access->p_sys->flv_packet = NULL;
    p_access->p_sys->read_packet = 1;

Ilkka Ollakka's avatar
Ilkka Ollakka committed
258
    msg_Dbg( p_access, "waiting for buffer to fill");
259 260 261
    /* Wait until enough data is received for extracting metadata */
    while( block_FifoCount( p_access->p_sys->p_thread->p_fifo_media ) < 10 )
    {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
262
        msg_Dbg( p_access, "waiting for buffer to fill");
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
        msleep(1000);
        continue;
    }

    /* Update default_pts to a suitable value for rtmp access */
    var_Create( p_access, "rtmp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the rtmp connection
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
    access_t     *p_access = (access_t *) p_this;
    access_sys_t *p_sys = p_access->p_sys;

Ilkka Ollakka's avatar
Ilkka Ollakka committed
281
    msg_Warn(p_access, "Close");
282

Ilkka Ollakka's avatar
Ilkka Ollakka committed
283 284 285 286
    vlc_object_kill( p_sys->p_thread );
    block_FifoWake( p_sys->p_thread->p_fifo_media );
    block_FifoWake( p_sys->p_thread->p_empty_blocks );
    /*
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    for( i = 0; i < 5; i++ )
    {
        block_t *p_dummy = block_New( p_access, 256 );
        p_dummy->i_dts = 0;
        p_dummy->i_pts = 0;
        p_dummy->i_length = 0;
        memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
        block_FifoPut( p_sys->p_thread->p_fifo_media, p_dummy );
    }
    for( i = 0; i < 5; i++ )
    {
        block_t *p_dummy = block_New( p_access, 256 );
        p_dummy->i_dts = 0;
        p_dummy->i_pts = 0;
        p_dummy->i_length = 0;
        memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
        block_FifoPut( p_sys->p_thread->p_empty_blocks, p_dummy );
Ilkka Ollakka's avatar
Ilkka Ollakka committed
304
    }*/
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
    vlc_thread_join( p_sys->p_thread );

    vlc_cond_destroy( &p_sys->p_thread->wait );
    vlc_mutex_destroy( &p_sys->p_thread->lock );

    block_FifoRelease( p_sys->p_thread->p_fifo_media );
    block_FifoRelease( p_sys->p_thread->p_empty_blocks );

    net_Close( p_sys->fd );

    var_Destroy( p_access, "rtmp-caching" );


    vlc_UrlClean( &p_sys->url );
    free( p_sys->psz_application );
    free( p_sys->psz_media );
    free( p_sys );
}

/*****************************************************************************
 * Read: standard read on a file descriptor.
 *****************************************************************************/
static int Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
{
    access_sys_t *p_sys = p_access->p_sys;
    int i_len_tmp;

    if( p_sys->fd < 0 )
    {
334
        p_access->info.b_eof = true;
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
        return 0;
    }

    i_len_tmp = 0;

    while( i_len_tmp < i_len )
    {
        if( p_sys->read_packet )
        {
            if( !p_sys->p_thread->metadata_received )
            {
                p_sys->flv_packet = flv_get_metadata( p_access );

                p_sys->p_thread->metadata_received = 1;
            }
            else
            {
                if( p_sys->active && block_FifoCount( p_sys->p_thread->p_fifo_media ) == 0 )
                {
354
                    p_access->info.b_eof = true;
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
                    break;
                }

                p_sys->flv_packet = block_FifoGet( p_sys->p_thread->p_fifo_media );
                if( p_sys->flv_packet == NULL )
                    continue; /* Forced wake-up */
            }

            if( p_sys->p_thread->first_media_packet )
            {
                p_sys->flv_packet = flv_insert_header( p_access, p_sys->flv_packet );

                p_sys->p_thread->first_media_packet = 0;
            }
        }
        if( i_len - i_len_tmp >= p_sys->flv_packet->i_buffer )
        {
            p_sys->read_packet = 1;

            memcpy( p_buffer + i_len_tmp, p_sys->flv_packet->p_buffer, p_sys->flv_packet->i_buffer );
            block_FifoPut( p_sys->p_thread->p_empty_blocks, p_sys->flv_packet );

            i_len_tmp += p_sys->flv_packet->i_buffer;
        }
        else
        {
            p_sys->read_packet = 0;

            memcpy( p_buffer + i_len_tmp, p_sys->flv_packet->p_buffer, i_len - i_len_tmp);
            p_sys->flv_packet->i_buffer -= i_len - i_len_tmp;
            memmove( p_sys->flv_packet->p_buffer, p_sys->flv_packet->p_buffer + i_len - i_len_tmp, p_sys->flv_packet->i_buffer );

            i_len_tmp += i_len - i_len_tmp;
        }
    }
/*int i;
for(i = 0; i < i_len_tmp; i += 16)
{
    msg_Warn(p_access,"%.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x",
p_buffer[i], p_buffer[i+1], p_buffer[i+2], p_buffer[i+3], p_buffer[i+4], p_buffer[i+5], p_buffer[i+6], p_buffer[i+7],
p_buffer[i+8], p_buffer[i+9], p_buffer[i+10], p_buffer[i+11], p_buffer[i+12], p_buffer[i+13], p_buffer[i+14], p_buffer[i+15]);
}*/
    if( i_len_tmp > 0 ) {
        if( p_sys->p_thread->result_publish )
        {
            /* Send publish onStatus event only once */
            p_sys->p_thread->result_publish = 0;

            rtmp_send_publish_start( p_access );
        }

        p_access->info.i_pos += i_len_tmp;

        rtmp_send_bytes_read( p_access, p_access->info.i_pos );
    }

    return i_len_tmp;
}

/*****************************************************************************
 * Seek: seek to a specific location in a file
 *****************************************************************************/
static int Seek( access_t *p_access, int64_t i_pos )
{
/*msg_Warn ( p_access, "Seek to %lld", i_pos);
    switch( rtmp_seek( p_access, i_pos ) )
    {
        case -1:
            return VLC_EGENERIC;
        case 0:
            break;
        default:
            msg_Err( p_access, "You should not be here" );
            abort();
    }
*/
    return VLC_EGENERIC;
}

/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
{
439
    bool   *pb_bool;
440 441 442 443 444 445 446 447
    int          *pi_int;
    int64_t      *pi_64;

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
        case ACCESS_CAN_FASTSEEK:
448 449
            pb_bool = (bool*)va_arg( args, bool* );
            *pb_bool = false; /* TODO */
450 451 452
            break;

        case ACCESS_CAN_PAUSE:
453 454
            pb_bool = (bool*)va_arg( args, bool* );
            *pb_bool = false; /* TODO */
455 456 457
            break;

        case ACCESS_CAN_CONTROL_PACE:
458 459
            pb_bool = (bool*)va_arg( args, bool* );
            *pb_bool = true;
460 461 462 463 464 465 466 467 468 469
            break;

        /* */
        case ACCESS_GET_MTU:
            pi_int = (int*)va_arg( args, int * );
            *pi_int = 0;
            break;

        case ACCESS_GET_PTS_DELAY:
            pi_64 = (int64_t*)va_arg( args, int64_t * );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
470
            *pi_64 = var_GetInteger( p_access, "rtmp-caching" ) * INT64_C(1000);
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
            break;

        /* */
        case ACCESS_SET_PAUSE_STATE:
            /* Nothing to do */
            break;

        case ACCESS_GET_TITLE_INFO:
        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
        case ACCESS_SET_PRIVATE_ID_STATE:
        case ACCESS_GET_META:
        case ACCESS_GET_CONTENT_TYPE: /* DOWN: comment this line */
            return VLC_EGENERIC;

        default:
            msg_Warn( p_access, "unimplemented query in control" );
            return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * ThreadControl: manage control messages and pipe media to Read
 *****************************************************************************/
static void ThreadControl( vlc_object_t *p_this )
{
    rtmp_control_thread_t *p_thread = (rtmp_control_thread_t *) p_this;
    rtmp_packet_t *rtmp_packet;

    rtmp_init_handler( p_thread->rtmp_handler );

    while( !p_thread->b_die )
    {

        rtmp_packet = rtmp_read_net_packet( p_thread );
        if( rtmp_packet != NULL )
        {
            if( rtmp_packet->content_type < 0x01 /* RTMP_CONTENT_TYPE_CHUNK_SIZE */
                || rtmp_packet->content_type > 0x14 ) /* RTMP_CONTENT_TYPE_INVOKE */
                msg_Warn( p_thread, "unknown content type received" );
            else
                p_thread->rtmp_handler[rtmp_packet->content_type]( p_thread, rtmp_packet );
        }
        else
        {
            /* Sometimes server close connection too soon */
            if( p_thread->result_connect )
            {
                vlc_mutex_lock( &p_thread->lock );
                vlc_cond_signal( &p_thread->wait );
                vlc_mutex_unlock( &p_thread->lock );
            }

            p_thread->b_die = 1;
        }
    }
}