stream.c 60.1 KB
Newer Older
1 2 3
/*****************************************************************************
 * stream.c
 *****************************************************************************
4
 * Copyright (C) 1999-2004 the VideoLAN team
Gildas Bazin's avatar
Gildas Bazin committed
5
 * $Id$
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23
 *****************************************************************************/

24 25 26 27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

28
#include <dirent.h>
29
#include <assert.h>
30

31
#include <vlc_common.h>
32 33
#include <vlc_strings.h>
#include <vlc_osd.h>
34
#include <vlc_charset.h>
35

36
#include <libvlc.h>
37

38
#include "access.h"
39 40 41
#include "stream.h"

#include "input_internal.h"
42

43 44
#undef STREAM_DEBUG

45
/* TODO:
46
 *  - tune the 2 methods (block/stream)
47 48 49
 *  - compute cost for seek
 *  - improve stream mode seeking with closest segments
 *  - ...
50
 */
51 52 53 54 55 56

/* Two methods:
 *  - using pf_block
 *      One linked list of data read
 *  - using pf_read
 *      More complex scheme using mutliple track to avoid seeking
57 58 59
 *  - using directly the access (only indirection for peeking).
 *      This method is known to introduce much less latency.
 *      It should probably defaulted (instead of the stream method (2)).
60 61
 */

62 63
/* How many tracks we have, currently only used for stream mode */
#ifdef OPTIMIZE_MEMORY
64 65 66 67 68 69 70 71 72
#   define STREAM_CACHE_TRACK 1
    /* Max size of our cache 128Ko per track */
#   define STREAM_CACHE_SIZE  (STREAM_CACHE_TRACK*1024*128)
#else
#   define STREAM_CACHE_TRACK 3
    /* Max size of our cache 4Mo per track */
#   define STREAM_CACHE_SIZE  (4*STREAM_CACHE_TRACK*1024*1024)
#endif

73 74 75 76
/* How many data we try to prebuffer
 * XXX it should be small to avoid useless latency but big enough for
 * efficient demux probing */
#define STREAM_CACHE_PREBUFFER_SIZE (128)
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

/* Method1: Simple, for pf_block.
 *  We get blocks and put them in the linked list.
 *  We release blocks once the total size is bigger than CACHE_BLOCK_SIZE
 */

/* Method2: A bit more complex, for pf_read
 *  - We use ring buffers, only one if unseekable, all if seekable
 *  - Upon seek date current ring, then search if one ring match the pos,
 *      yes: switch to it, seek the access to match the end of the ring
 *      no: search the ring with i_end the closer to i_pos,
 *          if close enough, read data and use this ring
 *          else use the oldest ring, seek and use it.
 *
 *  TODO: - with access non seekable: use all space available for only one ring, but
 *          we have to support seekable/non-seekable switch on the fly.
 *        - compute a good value for i_read_size
 *        - ?
 */
96
#define STREAM_READ_ATONCE 1024
97 98 99
#define STREAM_CACHE_TRACK_SIZE (STREAM_CACHE_SIZE/STREAM_CACHE_TRACK)

typedef struct
100
{
101
    int64_t i_date;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
102

103 104 105 106
    int64_t i_start;
    int64_t i_end;

    uint8_t *p_buffer;
107

108 109
} stream_track_t;

110 111 112 113 114 115 116
typedef struct
{
    char     *psz_path;
    int64_t  i_size;

} access_entry_t;

Laurent Aimar's avatar
Laurent Aimar committed
117
typedef enum
118
{
119 120
    STREAM_METHOD_BLOCK,
    STREAM_METHOD_STREAM
121 122
} stream_read_method_t;

123 124 125 126
struct stream_sys_t
{
    access_t    *p_access;

127
    stream_read_method_t   method;    /* method to use */
128 129 130 131 132

    int64_t     i_pos;      /* Current reading offset */

    /* Method 1: pf_block */
    struct
133
    {
134
        int64_t i_start;        /* Offset of block for p_first */
135
        int64_t i_offset;       /* Offset for data in p_current */
136 137 138 139 140
        block_t *p_current;     /* Current block */

        int     i_size;         /* Total amount of data in the list */
        block_t *p_first;
        block_t **pp_last;
141

142 143 144 145
    } block;

    /* Method 2: for pf_read */
    struct
146
    {
147
        int i_offset;   /* Buffer offset in the current track */
148 149 150 151 152 153 154 155 156
        int i_tk;       /* Current track */
        stream_track_t tk[STREAM_CACHE_TRACK];

        /* Global buffer */
        uint8_t *p_buffer;

        /* */
        int i_used; /* Used since last read */
        int i_read_size;
157

158 159 160
    } stream;

    /* Peek temporary buffer */
161
    unsigned int i_peek;
162 163 164 165
    uint8_t *p_peek;

    /* Stat for both method */
    struct
166
    {
167
        bool b_fastseek;  /* From access */
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
168

169 170 171 172
        /* Stat about reading data */
        int64_t i_read_count;
        int64_t i_bytes;
        int64_t i_read_time;
173

174 175 176
        /* Stat about seek */
        int     i_seek_count;
        int64_t i_seek_time;
177

178
    } stat;
179

180 181 182 183 184 185
    /* Streams list */
    int            i_list;
    access_entry_t **list;
    int            i_list_index;
    access_t       *p_list_access;

186
    /* Preparse mode ? */
187
    bool      b_quick;
188 189 190 191 192 193

    /* */
    struct
    {
        bool b_active;

194 195
        FILE *f;        /* TODO it could be replaced by access_output_t one day */
        bool b_error;
196
    } record;
197
};
198

199
/* Method 1: */
200 201
static int  AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read );
static int  AStreamPeekBlock( stream_t *s, const uint8_t **p_peek, unsigned int i_read );
202
static int  AStreamSeekBlock( stream_t *s, int64_t i_pos );
203
static void AStreamPrebufferBlock( stream_t *s );
204
static block_t *AReadBlock( stream_t *s, bool *pb_eof );
205

206
/* Method 2 */
207 208
static int  AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read );
static int  AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int i_read );
209
static int  AStreamSeekStream( stream_t *s, int64_t i_pos );
210
static void AStreamPrebufferStream( stream_t *s );
211
static int  AReadStream( stream_t *s, void *p_read, unsigned int i_read );
212

213
/* Common */
214
static int AStreamControl( stream_t *s, int i_query, va_list );
215 216
static void AStreamDestroy( stream_t *s );
static void UStreamDestroy( stream_t *s );
217
static int  ASeek( stream_t *s, int64_t i_pos );
218 219
static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension );
static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer );
220

Laurent Aimar's avatar
Laurent Aimar committed
221 222 223 224 225
/****************************************************************************
 * stream_CommonNew: create an empty stream structure
 ****************************************************************************/
stream_t *stream_CommonNew( vlc_object_t *p_obj )
{
Laurent Aimar's avatar
Laurent Aimar committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    stream_t *s = (stream_t *)vlc_custom_create( p_obj, sizeof(*s),
                                                 VLC_OBJECT_GENERIC, "stream" );

    if( !s )
        return NULL;

    s->p_text = malloc( sizeof(*s->p_text) );
    if( !s->p_text )
    {
        vlc_object_release( s );
        return NULL;
    }

    /* UTF16 and UTF32 text file conversion */
    s->p_text->conv = (vlc_iconv_t)(-1);
    s->p_text->i_char_width = 1;
    s->p_text->b_little_endian = false;

    return s;
}
void stream_CommonDelete( stream_t *s )
{
    if( s->p_text )
    {
        if( s->p_text->conv != (vlc_iconv_t)(-1) )
            vlc_iconv_close( s->p_text->conv );
        free( s->p_text );
    }
    vlc_object_release( s );
Laurent Aimar's avatar
Laurent Aimar committed
255 256
}

257
/****************************************************************************
258
 * stream_UrlNew: create a stream from a access
259
 ****************************************************************************/
260
stream_t *__stream_UrlNew( vlc_object_t *p_parent, const char *psz_url )
261
{
262 263
    const char *psz_access, *psz_demux;
    char *psz_path;
264 265
    access_t *p_access;
    stream_t *p_res;
266

267 268
    if( !psz_url )
        return NULL;
269

270 271 272
    char psz_dup[strlen( psz_url ) + 1];
    strcpy( psz_dup, psz_url );
    input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_dup );
273

274
    /* Now try a real access */
275
    p_access = access_New( p_parent, psz_access, psz_demux, psz_path );
276 277 278 279 280 281

    if( p_access == NULL )
    {
        msg_Err( p_parent, "no suitable access module for `%s'", psz_url );
        return NULL;
    }
282

283
    if( !( p_res = stream_AccessNew( p_access, true ) ) )
284
    {
285
        access_Delete( p_access );
286
        return NULL;
287
    }
288 289 290

    p_res->pf_destroy = UStreamDestroy;
    return p_res;
291 292
}

293
stream_t *stream_AccessNew( access_t *p_access, bool b_quick )
294
{
Laurent Aimar's avatar
Laurent Aimar committed
295
    stream_t *s = stream_CommonNew( VLC_OBJECT(p_access) );
296
    stream_sys_t *p_sys;
297
    char *psz_list = NULL;
298

299 300 301 302 303 304
    if( !s )
        return NULL;

    s->p_sys = p_sys = malloc( sizeof( stream_sys_t ) );
    if( !p_sys )
    {
Laurent Aimar's avatar
Laurent Aimar committed
305
        stream_CommonDelete( s );
306 307
        return NULL;
    }
308 309 310 311 312 313

    /* Attach it now, needed for b_die */
    vlc_object_attach( s, p_access );

    s->pf_read   = NULL;    /* Set up later */
    s->pf_peek   = NULL;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
314
    s->pf_control = AStreamControl;
315
    s->pf_destroy = AStreamDestroy;
316 317 318

    /* Common field */
    p_sys->p_access = p_access;
319
    if( p_access->pf_block )
320
        p_sys->method = STREAM_METHOD_BLOCK;
321
    else
322
        p_sys->method = STREAM_METHOD_STREAM;
323

324 325
    p_sys->record.b_active = false;

326 327 328
    p_sys->i_pos = p_access->info.i_pos;

    /* Stats */
329
    access_Control( p_access, ACCESS_CAN_FASTSEEK, &p_sys->stat.b_fastseek );
330 331 332 333 334 335
    p_sys->stat.i_bytes = 0;
    p_sys->stat.i_read_time = 0;
    p_sys->stat.i_read_count = 0;
    p_sys->stat.i_seek_count = 0;
    p_sys->stat.i_seek_time = 0;

336 337 338 339 340
    p_sys->i_list = 0;
    p_sys->list = 0;
    p_sys->i_list_index = 0;
    p_sys->p_list_access = 0;

341 342
    p_sys->b_quick = b_quick;

343 344 345 346
    /* Get the additional list of inputs if any (for concatenation) */
    if( (psz_list = var_CreateGetString( s, "input-list" )) && *psz_list )
    {
        access_entry_t *p_entry = malloc( sizeof(access_entry_t) );
347 348
        if( p_entry == NULL )
            goto error;
349 350 351 352 353
        char *psz_name, *psz_parser = psz_name = psz_list;

        p_sys->p_list_access = p_access;
        p_entry->i_size = p_access->info.i_size;
        p_entry->psz_path = strdup( p_access->psz_path );
354 355 356 357 358
        if( p_entry->psz_path == NULL )
        {
            free( p_entry );
            goto error;
        }
359
        TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
360
        msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
361 362 363 364 365 366 367 368 369 370
                 p_entry->psz_path, p_access->info.i_size );

        while( psz_name && *psz_name )
        {
            psz_parser = strchr( psz_name, ',' );
            if( psz_parser ) *psz_parser = 0;

            psz_name = strdup( psz_name );
            if( psz_name )
            {
371
                access_t *p_tmp = access_New( p_access, p_access->psz_access,
372
                                               "", psz_name );
373 374 375 376 377 378 379 380

                if( !p_tmp )
                {
                    psz_name = psz_parser;
                    if( psz_name ) psz_name++;
                    continue;
                }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
381
                msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
382 383 384
                         psz_name, p_tmp->info.i_size );

                p_entry = malloc( sizeof(access_entry_t) );
385 386
                if( p_entry == NULL )
                    goto error;
387 388 389 390
                p_entry->i_size = p_tmp->info.i_size;
                p_entry->psz_path = psz_name;
                TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );

391
                access_Delete( p_tmp );
392 393 394 395 396 397
            }

            psz_name = psz_parser;
            if( psz_name ) psz_name++;
        }
    }
398
    FREENULL( psz_list );
399

400 401 402 403
    /* Peek */
    p_sys->i_peek = 0;
    p_sys->p_peek = NULL;

404
    if( p_sys->method == STREAM_METHOD_BLOCK )
405
    {
406
        msg_Dbg( s, "Using AStream*Block" );
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
        s->pf_read = AStreamReadBlock;
        s->pf_peek = AStreamPeekBlock;

        /* Init all fields of p_sys->block */
        p_sys->block.i_start = p_sys->i_pos;
        p_sys->block.i_offset = 0;
        p_sys->block.p_current = NULL;
        p_sys->block.i_size = 0;
        p_sys->block.p_first = NULL;
        p_sys->block.pp_last = &p_sys->block.p_first;

        /* Do the prebuffering */
        AStreamPrebufferBlock( s );

        if( p_sys->block.i_size <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
    }
427
    else
428
    {
429 430
        int i;

431 432
        assert( p_sys->method == STREAM_METHOD_STREAM );

433 434
        msg_Dbg( s, "Using AStream*Stream" );

435 436 437 438 439 440 441
        s->pf_read = AStreamReadStream;
        s->pf_peek = AStreamPeekStream;

        /* Allocate/Setup our tracks */
        p_sys->stream.i_offset = 0;
        p_sys->stream.i_tk     = 0;
        p_sys->stream.p_buffer = malloc( STREAM_CACHE_SIZE );
442 443
        if( p_sys->stream.p_buffer == NULL )
            goto error;
444
        p_sys->stream.i_used   = 0;
445
        access_Control( p_access, ACCESS_GET_MTU,
446
                         &p_sys->stream.i_read_size );
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
        if( p_sys->stream.i_read_size <= 0 )
            p_sys->stream.i_read_size = STREAM_READ_ATONCE;
        else if( p_sys->stream.i_read_size <= 256 )
            p_sys->stream.i_read_size = 256;

        for( i = 0; i < STREAM_CACHE_TRACK; i++ )
        {
            p_sys->stream.tk[i].i_date  = 0;
            p_sys->stream.tk[i].i_start = p_sys->i_pos;
            p_sys->stream.tk[i].i_end   = p_sys->i_pos;
            p_sys->stream.tk[i].p_buffer=
                &p_sys->stream.p_buffer[i * STREAM_CACHE_TRACK_SIZE];
        }

        /* Do the prebuffering */
        AStreamPrebufferStream( s );
463

464 465 466 467 468
        if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
469
    }
470

471
    return s;
472 473

error:
474
    if( p_sys->method == STREAM_METHOD_BLOCK )
475 476 477 478 479 480 481
    {
        /* Nothing yet */
    }
    else
    {
        free( p_sys->stream.p_buffer );
    }
482 483 484 485
    while( p_sys->i_list > 0 )
        free( p_sys->list[--(p_sys->i_list)] );
    free( p_sys->list );
    free( psz_list );
486 487
    free( s->p_sys );
    vlc_object_detach( s );
Laurent Aimar's avatar
Laurent Aimar committed
488
    stream_CommonDelete( s );
489
    return NULL;
490 491
}

492
/****************************************************************************
493
 * AStreamDestroy:
494
 ****************************************************************************/
495
static void AStreamDestroy( stream_t *s )
496
{
497 498 499 500
    stream_sys_t *p_sys = s->p_sys;

    vlc_object_detach( s );

501 502 503
    if( p_sys->record.b_active )
        ARecordSetState( s, false, NULL );

504
    if( p_sys->method == STREAM_METHOD_BLOCK )
505 506 507
        block_ChainRelease( p_sys->block.p_first );
    else
        free( p_sys->stream.p_buffer );
508

509
    free( p_sys->p_peek );
510 511

    if( p_sys->p_list_access && p_sys->p_list_access != p_sys->p_access )
512
        access_Delete( p_sys->p_list_access );
513 514

    while( p_sys->i_list-- )
515
    {
516 517
        free( p_sys->list[p_sys->i_list]->psz_path );
        free( p_sys->list[p_sys->i_list] );
518 519
    }

520 521 522
    free( p_sys->list );
    free( p_sys );

Laurent Aimar's avatar
Laurent Aimar committed
523
    stream_CommonDelete( s );
524 525
}

526 527
static void UStreamDestroy( stream_t *s )
{
528
    access_t *p_access = (access_t *)s->p_parent;
529
    AStreamDestroy( s );
530
    access_Delete( p_access );
531 532
}

533 534 535 536 537 538 539 540 541
/****************************************************************************
 * stream_AccessReset:
 ****************************************************************************/
void stream_AccessReset( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;

    p_sys->i_pos = p_sys->p_access->info.i_pos;

542
    if( p_sys->method == STREAM_METHOD_BLOCK )
543 544 545 546 547 548 549 550 551 552 553 554 555 556
    {
        block_ChainRelease( p_sys->block.p_first );

        /* Init all fields of p_sys->block */
        p_sys->block.i_start = p_sys->i_pos;
        p_sys->block.i_offset = 0;
        p_sys->block.p_current = NULL;
        p_sys->block.i_size = 0;
        p_sys->block.p_first = NULL;
        p_sys->block.pp_last = &p_sys->block.p_first;

        /* Do the prebuffering */
        AStreamPrebufferBlock( s );
    }
557
    else
558 559 560
    {
        int i;

561 562
        assert( p_sys->method == STREAM_METHOD_STREAM );

563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
        /* Setup our tracks */
        p_sys->stream.i_offset = 0;
        p_sys->stream.i_tk     = 0;
        p_sys->stream.i_used   = 0;

        for( i = 0; i < STREAM_CACHE_TRACK; i++ )
        {
            p_sys->stream.tk[i].i_date  = 0;
            p_sys->stream.tk[i].i_start = p_sys->i_pos;
            p_sys->stream.tk[i].i_end   = p_sys->i_pos;
        }

        /* Do the prebuffering */
        AStreamPrebufferStream( s );
    }
}
579

580 581 582 583 584 585
/****************************************************************************
 * stream_AccessUpdate:
 ****************************************************************************/
void stream_AccessUpdate( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;
586

587
    p_sys->i_pos = p_sys->p_access->info.i_pos;
588 589 590 591 592 593 594 595 596

    if( p_sys->i_list )
    {
        int i;
        for( i = 0; i < p_sys->i_list_index; i++ )
        {
            p_sys->i_pos += p_sys->list[i]->i_size;
        }
    }
597 598
}

599
/****************************************************************************
600
 * AStreamControl:
601
 ****************************************************************************/
602
static int AStreamControl( stream_t *s, int i_query, va_list args )
603
{
604 605
    stream_sys_t *p_sys = s->p_sys;
    access_t     *p_access = p_sys->p_access;
606

607
    bool *p_bool;
608 609
    bool b_bool;
    const char *psz_string;
610 611
    int64_t    *pi_64, i_64;
    int        i_int;
612 613 614 615

    switch( i_query )
    {
        case STREAM_GET_SIZE:
616
            pi_64 = (int64_t*)va_arg( args, int64_t * );
617 618 619 620 621 622 623 624
            if( s->p_sys->i_list )
            {
                int i;
                *pi_64 = 0;
                for( i = 0; i < s->p_sys->i_list; i++ )
                    *pi_64 += s->p_sys->list[i]->i_size;
                break;
            }
625 626
            *pi_64 = p_access->info.i_size;
            break;
627 628

        case STREAM_CAN_SEEK:
629
            p_bool = (bool*)va_arg( args, bool * );
630
            access_Control( p_access, ACCESS_CAN_SEEK, p_bool );
631
            break;
632 633

        case STREAM_CAN_FASTSEEK:
634
            p_bool = (bool*)va_arg( args, bool * );
635
            access_Control( p_access, ACCESS_CAN_FASTSEEK, p_bool );
636
            break;
637 638

        case STREAM_GET_POSITION:
639 640 641
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            *pi_64 = p_sys->i_pos;
            break;
642 643

        case STREAM_SET_POSITION:
644
            i_64 = (int64_t)va_arg( args, int64_t );
645 646 647
            switch( p_sys->method )
            {
            case STREAM_METHOD_BLOCK:
648
                return AStreamSeekBlock( s, i_64 );
649
            case STREAM_METHOD_STREAM:
650
                return AStreamSeekStream( s, i_64 );
651 652 653 654
            default:
                assert(0);
                return VLC_EGENERIC;
            }
655

656
        case STREAM_GET_MTU:
657
            return VLC_EGENERIC;
658

659
        case STREAM_CONTROL_ACCESS:
660
            i_int = (int) va_arg( args, int );
661 662 663
            if( i_int != ACCESS_SET_PRIVATE_ID_STATE &&
                i_int != ACCESS_SET_PRIVATE_ID_CA &&
                i_int != ACCESS_GET_PRIVATE_ID_STATE )
664 665 666 667 668
            {
                msg_Err( s, "Hey, what are you thinking ?"
                            "DON'T USE STREAM_CONTROL_ACCESS !!!" );
                return VLC_EGENERIC;
            }
669
            return access_vaControl( p_access, i_int, args );
670

671
        case STREAM_GET_CONTENT_TYPE:
672
            return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
673
                                    va_arg( args, char ** ) );
674 675 676 677 678 679
        case STREAM_SET_RECORD_STATE:
            b_bool = (bool)va_arg( args, int );
            psz_string = NULL;
            if( b_bool )
                psz_string = (const char*)va_arg( args, const char* );
            return ARecordSetState( s, b_bool, psz_string );
680

681 682 683 684
        default:
            msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
            return VLC_EGENERIC;
    }
685
    return VLC_SUCCESS;
686 687
}

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
/****************************************************************************
 * ARecord*: record stream functions
 ****************************************************************************/
static int  ARecordStart( stream_t *s, const char *psz_extension )
{
    stream_sys_t *p_sys = s->p_sys;

    char *psz_file;
    FILE *f;

    /* */
    if( !psz_extension )
        psz_extension = "dat";

    /* Retreive path */
    char *psz_path = var_CreateGetString( s, "input-record-path" );
    if( !psz_path || *psz_path == '\0' )
    {
        free( psz_path );
        psz_path = strdup( config_GetHomeDir() );
    }

    if( !psz_path )
        return VLC_ENOMEM;

    /* Create file name
     * TODO allow prefix configuration */
715
    psz_file = input_CreateFilename( VLC_OBJECT(s), psz_path, INPUT_RECORD_PREFIX, psz_extension );
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733

    free( psz_path );

    if( !psz_file )
        return VLC_ENOMEM;

    f = utf8_fopen( psz_file, "wb" );
    if( !f )
    {
        free( psz_file );
        return VLC_EGENERIC;
    }
    msg_Dbg( s, "Recording into %s", psz_file );
    free( psz_file );

    /* */
    p_sys->record.f = f;
    p_sys->record.b_active = true;
734
    p_sys->record.b_error = false;
735 736 737 738 739 740 741
    return VLC_SUCCESS;
}
static int  ARecordStop( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;

    assert( p_sys->record.b_active );
742

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
    msg_Dbg( s, "Recording completed" );
    fclose( p_sys->record.f );
    p_sys->record.b_active = false;
    return VLC_SUCCESS;
}

static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension )
{
    stream_sys_t *p_sys = s->p_sys;

    if( !!p_sys->record.b_active == !!b_record )
        return VLC_SUCCESS;

    if( b_record )
        return ARecordStart( s, psz_extension );
    else
        return ARecordStop( s );
}
static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer )
{
    stream_sys_t *p_sys = s->p_sys;

    assert( p_sys->record.b_active );

767 768 769 770 771 772 773 774 775 776 777 778 779
    if( i_buffer > 0 )
    {
        const bool b_previous_error = p_sys->record.b_error;
        const size_t i_written = fwrite( p_buffer, 1, i_buffer, p_sys->record.f );

        p_sys->record.b_error = i_written != i_buffer;

        /* TODO maybe a intf_UserError or something like that ? */
        if( p_sys->record.b_error && !b_previous_error )
            msg_Err( s, "Failed to record data (begin)" );
        else if( !p_sys->record.b_error && b_previous_error )
            msg_Err( s, "Failed to record data (end)" );
    }
780
}
781

782
/****************************************************************************
783
 * Method 1:
784
 ****************************************************************************/
785
static void AStreamPrebufferBlock( stream_t *s )
786
{
787
    stream_sys_t *p_sys = s->p_sys;
788
    access_t     *p_access = p_sys->p_access;
789

790 791
    int64_t i_first = 0;
    int64_t i_start;
792

793 794 795
    msg_Dbg( s, "pre buffering" );
    i_start = mdate();
    for( ;; )
796
    {
797
        const int64_t i_date = mdate();
798
        bool b_eof;
799
        block_t *b;
800

801
        if( s->b_die || p_sys->block.i_size > STREAM_CACHE_PREBUFFER_SIZE )
802 803 804 805 806 807
        {
            int64_t i_byterate;

            /* Update stat */
            p_sys->stat.i_bytes = p_sys->block.i_size;
            p_sys->stat.i_read_time = i_date - i_start;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
808
            i_byterate = ( INT64_C(1000000) * p_sys->stat.i_bytes ) /
809
                         (p_sys->stat.i_read_time + 1);
810

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
811
            msg_Dbg( s, "prebuffering done %"PRId64" bytes in %"PRId64"s - "
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
812
                     "%"PRId64" kbytes/s",
813
                     p_sys->stat.i_bytes,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
814
                     p_sys->stat.i_read_time / INT64_C(1000000),
815 816 817
                     i_byterate / 1024 );
            break;
        }
818

819
        /* Fetch a block */
820
        if( ( b = AReadBlock( s, &b_eof ) ) == NULL )
821
        {
822 823
            if( b_eof )
                break;
824
            continue;
825
        }
826

827 828 829 830 831 832
        while( b )
        {
            /* Append the block */
            p_sys->block.i_size += b->i_buffer;
            *p_sys->block.pp_last = b;
            p_sys->block.pp_last = &b->p_next;
833

834 835 836
            p_sys->stat.i_read_count++;
            b = b->p_next;
        }
837 838 839 840

        if( i_first == 0 )
        {
            i_first = mdate();
841 842
            msg_Dbg( s, "received first data after %d ms",
                     (int)((i_first-i_start)/1000) );
843
        }
844 845
    }

846 847 848 849 850
    p_sys->block.p_current = p_sys->block.p_first;
}

static int AStreamRefillBlock( stream_t *s );

851
static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read )
852 853 854
{
    stream_sys_t *p_sys = s->p_sys;

855 856
    uint8_t *p_data = p_read;
    uint8_t *p_record = p_data;
857
    unsigned int i_data = 0;
858 859 860 861 862

    /* It means EOF */
    if( p_sys->block.p_current == NULL )
        return 0;

863 864 865 866
    if( p_sys->record.b_active && !p_data )
        p_record = p_data = malloc( i_read );

    if( p_data == NULL )
Damien Fouilleul's avatar
 
Damien Fouilleul committed
867
    {
868
        /* seek within this stream if possible, else use plain old read and discard */
Damien Fouilleul's avatar
 
Damien Fouilleul committed
869 870
        stream_sys_t *p_sys = s->p_sys;
        access_t     *p_access = p_sys->p_access;
871
        bool   b_aseek;
872
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
Damien Fouilleul's avatar
 
Damien Fouilleul committed
873 874 875
        if( b_aseek )
            return AStreamSeekBlock( s, p_sys->i_pos + i_read ) ? 0 : i_read;
    }
876

877
    while( i_data < i_read )
878
    {
879 880
        int i_current =
            p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
881
        unsigned int i_copy = __MIN( (unsigned int)__MAX(i_current,0), i_read - i_data);
Gildas Bazin's avatar
 
Gildas Bazin committed
882

883
        /* Copy data */
884 885 886
        if( p_data )
        {
            memcpy( p_data,
887 888
                    &p_sys->block.p_current->p_buffer[p_sys->block.i_offset],
                    i_copy );
889 890
            p_data += i_copy;
        }
891
        i_data += i_copy;
892

893 894 895 896 897 898 899 900 901
        p_sys->block.i_offset += i_copy;
        if( p_sys->block.i_offset >= p_sys->block.p_current->i_buffer )
        {
            /* Current block is now empty, switch to next */
            if( p_sys->block.p_current )
            {
                p_sys->block.i_offset = 0;
                p_sys->block.p_current = p_sys->block.p_current->p_next;
            }
Rafaël Carré's avatar
Rafaël Carré committed
902 903
            /*Get a new block if needed */
            if( !p_sys->block.p_current && AStreamRefillBlock( s ) )
Gildas Bazin's avatar
Gildas Bazin committed
904 905 906
            {
                break;
            }
907 908 909
        }
    }

910 911 912 913 914 915 916
    if( p_sys->record.b_active )
    {
        if( i_data > 0 && p_record != NULL)
            ARecordWrite( s, p_record, i_data );
        if( !p_read )
            free( p_record );
    }
917

918 919
    p_sys->i_pos += i_data;
    return i_data;
920
}
Gildas Bazin's avatar
Gildas Bazin committed
921

922
static int AStreamPeekBlock( stream_t *s, const uint8_t **pp_peek, unsigned int i_read )
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
923
{
924 925
    stream_sys_t *p_sys = s->p_sys;
    uint8_t *p_data;
926
    unsigned int i_data = 0;
927
    block_t *b;
928
    unsigned int i_offset;
929

930
    if( p_sys->block.p_current == NULL ) return 0; /* EOF */
931 932 933 934 935 936 937 938 939 940 941

    /* We can directly give a pointer over our buffer */
    if( i_read <= p_sys->block.p_current->i_buffer - p_sys->block.i_offset )
    {
        *pp_peek = &p_sys->block.p_current->p_buffer[p_sys->block.i_offset];
        return i_read;
    }

    /* We need to create a local copy */
    if( p_sys->i_peek < i_read )
    {
942 943 944 945 946 947
        p_sys->p_peek = realloc( p_sys->p_peek, i_read );
        if( !p_sys->p_peek )
        {
            p_sys->i_peek = 0;
            return 0;
        }
948 949 950 951
        p_sys->i_peek = i_read;
    }

    /* Fill enough data */
952 953
    while( p_sys->block.i_size - (p_sys->i_pos - p_sys->block.i_start)
           < i_read )
954 955 956
    {
        block_t **pp_last = p_sys->block.pp_last;

957
        if( AStreamRefillBlock( s ) ) break;
958 959

        /* Our buffer are probably filled enough, don't try anymore */
960
        if( pp_last == p_sys->block.pp_last ) break;
961 962 963 964 965 966 967 968 969
    }

    /* Copy what we have */
    b = p_sys->block.p_current;
    i_offset = p_sys->block.i_offset;
    p_data = p_sys->p_peek;

    while( b && i_data < i_read )
    {
970
        unsigned int i_current = __MAX(b->i_buffer - i_offset,0);
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
        int i_copy = __MIN( i_current, i_read - i_data );

        memcpy( p_data, &b->p_buffer[i_offset], i_copy );
        i_data += i_copy;
        p_data += i_copy;
        i_offset += i_copy;

        if( i_offset >= b->i_buffer )
        {
            i_offset = 0;
            b = b->p_next;
        }
    }

    *pp_peek = p_sys->p_peek;
    return i_data;
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
987
}
988

989
static int AStreamSeekBlock( stream_t *s, int64_t i_pos )
990
{
991 992 993
    stream_sys_t *p_sys = s->p_sys;
    access_t   *p_access = p_sys->p_access;
    int64_t    i_offset = i_pos - p_sys->block.i_start;
994
    bool b_seek;
995

996 997 998 999 1000
    /* We already have thoses data, just update p_current/i_offset */
    if( i_offset >= 0 && i_offset < p_sys->block.i_size )
    {
        block_t *b = p_sys->block.p_first;
        int i_current = 0;
1001

1002 1003 1004 1005 1006
        while( i_current + b->i_buffer < i_offset )
        {
            i_current += b->i_buffer;
            b = b->p_next;
        }
1007

1008 1009
        p_sys->block.p_current = b;
        p_sys->block.i_offset = i_offset - i_current;
1010

1011
        p_sys->i_pos = i_pos;
1012

1013 1014
        return VLC_SUCCESS;
    }
1015

1016 1017
    /* We may need to seek or to read data */
    if( i_offset < 0 )
1018
    {
1019
        bool b_aseek;
1020
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
1021 1022 1023

        if( !b_aseek )
        {
1024
            msg_Err( s, "backward seeking impossible (access not seekable)" );
1025 1026 1027
            return VLC_EGENERIC;
        }

1028
        b_seek = true;
1029
    }
1030 1031
    else
    {
1032
        bool b_aseek, b_aseekfast;
1033

1034 1035
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
        access_Control( p_access, ACCESS_CAN_FASTSEEK, &b_aseekfast );
1036 1037 1038

        if( !b_aseek )
        {
1039
            b_seek = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1040
            msg_Warn( s, "%"PRId64" bytes need to be skipped "
1041
                      "(access non seekable)",
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
                      i_offset - p_sys->block.i_size );
        }
        else
        {
            int64_t i_skip = i_offset - p_sys->block.i_size;

            /* Avg bytes per packets */
            int i_avg = p_sys->stat.i_bytes / p_sys->stat.i_read_count;
            /* TODO compute a seek cost instead of fixed threshold */
            int i_th = b_aseekfast ? 1 : 5;

            if( i_skip <= i_th * i_avg &&
                i_skip < STREAM_CACHE_SIZE )
1055
                b_seek = false;
1056
            else
1057
                b_seek = true;
1058

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1059
            msg_Dbg( s, "b_seek=%d th*avg=%d skip=%"PRId64,
1060 1061
                     b_seek, i_th*i_avg, i_skip );
        }
1062 1063
    }

1064 1065 1066 1067 1068
    if( b_seek )
    {
        int64_t i_start, i_end;
        /* Do the access seek */
        i_start = mdate();
1069
        if( ASeek( s, i_pos ) ) return VLC_EGENERIC;
1070
        i_end = mdate();
1071

1072 1073 1074 1075 1076 1077 1078 1079 1080 1081
        /* Release data */
        block_ChainRelease( p_sys->block.p_first );

        /* Reinit */
        p_sys->block.i_start = p_sys->i_pos = i_pos;
        p_sys->block.i_offset = 0;
        p_sys->block.p_current = NULL;
        p_sys->block.i_size = 0;
        p_sys->block.p_first = NULL;
        p_sys->block.pp_last = &p_sys->block.p_first;
1082

1083 1084 1085
        /* Refill a block */
        if( AStreamRefillBlock( s ) )
            return VLC_EGENERIC;
1086

1087 1088 1089 1090 1091 1092
        /* Update stat */
        p_sys->stat.i_seek_time += i_end - i_start;
        p_sys->stat.i_seek_count++;
        return VLC_SUCCESS;
    }
    else
1093
    {
1094
        do
1095
        {
1096
            /* Read and skip enough data */
1097 1098
            if( AStreamRefillBlock( s ) )
                return VLC_EGENERIC;
1099

1100
            while( p_sys->block.p_current &&
1101
                   p_sys->i_pos + p_sys->block.p_current->i_buffer - p_sys->block.i_offset < i_pos )
1102
            {
1103
                p_sys->i_pos += p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
1104
                p_sys->block.p_current = p_sys->block.p_current->p_next;
1105
                p_sys->block.i_offset = 0;
1106 1107
            }
        }
1108
        while( p_sys->block.i_start + p_sys->block.i_size < i_pos );
1109

1110 1111
        p_sys->block.i_offset = i_pos - p_sys->i_pos;
        p_sys->i_pos = i_pos;
1112

1113
        return VLC_SUCCESS;
1114 1115
    }

1116
    return VLC_EGENERIC;
1117 1118
}

Laurent Aimar's avatar