stream.c 65.7 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
 *  - Maybe remove (block/stream) in favour of immediate
51
 */
52 53 54 55 56 57

/* Two methods:
 *  - using pf_block
 *      One linked list of data read
 *  - using pf_read
 *      More complex scheme using mutliple track to avoid seeking
58 59 60
 *  - 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)).
61 62
 */

63 64
/* How many tracks we have, currently only used for stream mode */
#ifdef OPTIMIZE_MEMORY
65 66 67 68 69 70 71 72 73 74
#   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

/* How many data we try to prebuffer */
75
#define STREAM_CACHE_PREBUFFER_SIZE (32767)
76 77
/* Maximum time we take to pre-buffer */
#define STREAM_CACHE_PREBUFFER_LENGTH (100*1000)
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

/* 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
 *        - ?
 */
#define STREAM_READ_ATONCE 32767
#define STREAM_CACHE_TRACK_SIZE (STREAM_CACHE_SIZE/STREAM_CACHE_TRACK)

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

104 105 106 107
    int64_t i_start;
    int64_t i_end;

    uint8_t *p_buffer;
108

109 110
} stream_track_t;

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

} access_entry_t;

118 119
typedef enum stream_read_method_t
{
120 121 122
    STREAM_METHOD_IMMEDIATE,
    STREAM_METHOD_BLOCK,
    STREAM_METHOD_STREAM
123 124
} stream_read_method_t;

125 126 127 128
struct stream_sys_t
{
    access_t    *p_access;

129
    stream_read_method_t   method;    /* method to use */
130 131 132 133 134

    int64_t     i_pos;      /* Current reading offset */

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

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

144 145 146 147
    } block;

    /* Method 2: for pf_read */
    struct
148
    {
149
        int i_offset;   /* Buffer offset in the current track */
150 151 152 153 154 155 156 157 158
        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;
159

160 161
    } stream;

162 163 164 165 166 167 168
    /* Method 3: for pf_read */
    struct
    {
        int64_t i_end;
        uint8_t *p_buffer;
    } immediate;

169
    /* Peek temporary buffer */
170
    unsigned int i_peek;
171 172 173 174
    uint8_t *p_peek;

    /* Stat for both method */
    struct
175
    {
176
        bool b_fastseek;  /* From access */
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
177

178 179 180 181
        /* Stat about reading data */
        int64_t i_read_count;
        int64_t i_bytes;
        int64_t i_read_time;
182

183 184 185
        /* Stat about seek */
        int     i_seek_count;
        int64_t i_seek_time;
186

187
    } stat;
188

189 190 191 192 193 194
    /* Streams list */
    int            i_list;
    access_entry_t **list;
    int            i_list_index;
    access_t       *p_list_access;

195
    /* Preparse mode ? */
196
    bool      b_quick;
197 198 199 200 201 202

    /* */
    struct
    {
        bool b_active;

203 204
        FILE *f;        /* TODO it could be replaced by access_output_t one day */
        bool b_error;
205
    } record;
206
};
207

208
/* Method 1: */
209 210
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 );
211
static int  AStreamSeekBlock( stream_t *s, int64_t i_pos );
212
static void AStreamPrebufferBlock( stream_t *s );
213
static block_t *AReadBlock( stream_t *s, bool *pb_eof );
214

215
/* Method 2 */
216 217
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 );
218
static int  AStreamSeekStream( stream_t *s, int64_t i_pos );
219
static void AStreamPrebufferStream( stream_t *s );
220
static int  AReadStream( stream_t *s, void *p_read, unsigned int i_read );
221

222
/* Method 3 */
223 224
static int  AStreamReadImmediate( stream_t *s, void *p_read, unsigned int i_read );
static int  AStreamPeekImmediate( stream_t *s, const uint8_t **pp_peek, unsigned int i_read );
225 226
static int  AStreamSeekImmediate( stream_t *s, int64_t i_pos );

227
/* Common */
228
static int AStreamControl( stream_t *s, int i_query, va_list );
229 230
static void AStreamDestroy( stream_t *s );
static void UStreamDestroy( stream_t *s );
231
static int  ASeek( stream_t *s, int64_t i_pos );
232 233
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 );
234

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
/****************************************************************************
 * Method 3 helpers:
 ****************************************************************************/

static inline int64_t stream_buffered_size( stream_t *s )
{
    return s->p_sys->immediate.i_end;
}

static inline void stream_buffer_empty( stream_t *s, int length )
{
    length = __MAX( stream_buffered_size( s ), length );
    if( length )
    {
        memmove( s->p_sys->immediate.p_buffer,
                 s->p_sys->immediate.p_buffer + length,
                 stream_buffered_size( s ) - length );
    }
    s->p_sys->immediate.i_end -= length;
}

static inline void stream_buffer_fill( stream_t *s, int length )
{
    s->p_sys->immediate.i_end += length;
}

static inline uint8_t * stream_buffer( stream_t *s )
{
    return s->p_sys->immediate.p_buffer;
}
265

Laurent Aimar's avatar
Laurent Aimar committed
266 267 268 269 270 271 272 273 274
/****************************************************************************
 * stream_CommonNew: create an empty stream structure
 ****************************************************************************/
stream_t *stream_CommonNew( vlc_object_t *p_obj )
{
    return (stream_t *)vlc_custom_create( p_obj, sizeof(stream_t),
                                          VLC_OBJECT_GENERIC, "stream" );
}

275
/****************************************************************************
276
 * stream_UrlNew: create a stream from a access
277
 ****************************************************************************/
278
stream_t *__stream_UrlNew( vlc_object_t *p_parent, const char *psz_url )
279
{
280 281
    const char *psz_access, *psz_demux;
    char *psz_path;
282 283
    access_t *p_access;
    stream_t *p_res;
284

285 286
    if( !psz_url )
        return NULL;
287

288 289 290
    char psz_dup[strlen( psz_url ) + 1];
    strcpy( psz_dup, psz_url );
    input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_dup );
291

292
    /* Now try a real access */
293
    p_access = access_New( p_parent, psz_access, psz_demux, psz_path );
294 295 296 297 298 299

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

301
    if( !( p_res = stream_AccessNew( p_access, true ) ) )
302
    {
303
        access_Delete( p_access );
304
        return NULL;
305
    }
306 307 308

    p_res->pf_destroy = UStreamDestroy;
    return p_res;
309 310
}

311
stream_t *stream_AccessNew( access_t *p_access, bool b_quick )
312
{
Laurent Aimar's avatar
Laurent Aimar committed
313
    stream_t *s = stream_CommonNew( VLC_OBJECT(p_access) );
314
    stream_sys_t *p_sys;
315
    char *psz_list = NULL;
316

317 318 319 320 321 322 323 324 325
    if( !s )
        return NULL;

    s->p_sys = p_sys = malloc( sizeof( stream_sys_t ) );
    if( !p_sys )
    {
        vlc_object_release( s );
        return NULL;
    }
326 327 328 329 330 331

    /* 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
332
    s->pf_control = AStreamControl;
333
    s->pf_destroy = AStreamDestroy;
334

335 336
    /* UTF16 and UTF32 text file conversion */
    s->i_char_width = 1;
337
    s->b_little_endian = false;
338
    s->conv = (vlc_iconv_t)(-1);
339

340 341
    /* Common field */
    p_sys->p_access = p_access;
342
    if( p_access->pf_block )
343 344 345
        p_sys->method = STREAM_METHOD_BLOCK;
    else if( var_CreateGetBool( s, "use-stream-immediate" ) )
        p_sys->method = STREAM_METHOD_IMMEDIATE;
346
    else
347
        p_sys->method = STREAM_METHOD_STREAM;
348

349 350
    p_sys->record.b_active = false;

351 352 353
    p_sys->i_pos = p_access->info.i_pos;

    /* Stats */
354
    access_Control( p_access, ACCESS_CAN_FASTSEEK, &p_sys->stat.b_fastseek );
355 356 357 358 359 360
    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;

361 362 363 364 365
    p_sys->i_list = 0;
    p_sys->list = 0;
    p_sys->i_list_index = 0;
    p_sys->p_list_access = 0;

366 367
    p_sys->b_quick = b_quick;

368 369 370 371
    /* 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) );
372 373
        if( p_entry == NULL )
            goto error;
374 375 376 377 378
        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 );
379 380 381 382 383
        if( p_entry->psz_path == NULL )
        {
            free( p_entry );
            goto error;
        }
384
        TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
385
        msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
386 387 388 389 390 391 392 393 394 395
                 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 )
            {
396
                access_t *p_tmp = access_New( p_access, p_access->psz_access,
397
                                               "", psz_name );
398 399 400 401 402 403 404 405

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
406
                msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
407 408 409
                         psz_name, p_tmp->info.i_size );

                p_entry = malloc( sizeof(access_entry_t) );
410 411
                if( p_entry == NULL )
                    goto error;
412 413 414 415
                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 );

416
                access_Delete( p_tmp );
417 418 419 420 421 422
            }

            psz_name = psz_parser;
            if( psz_name ) psz_name++;
        }
    }
423
    FREENULL( psz_list );
424

425 426 427 428
    /* Peek */
    p_sys->i_peek = 0;
    p_sys->p_peek = NULL;

429
    if( p_sys->method == STREAM_METHOD_BLOCK )
430
    {
431
        msg_Dbg( s, "Using AStream*Block" );
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
        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;
        }
    }
452
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
453 454 455 456 457 458 459 460 461 462
    {
        msg_Dbg( s, "Using AStream*Immediate" );

        s->pf_read = AStreamReadImmediate;
        s->pf_peek = AStreamPeekImmediate;

        /* Allocate/Setup our tracks (useful to peek)*/
        p_sys->immediate.i_end = 0;
        p_sys->immediate.p_buffer = malloc( STREAM_CACHE_SIZE );

463 464 465
        if( p_sys->immediate.p_buffer == NULL )
            goto error;

466 467 468
        msg_Dbg( s, "p_buffer %p-%p",
                 p_sys->immediate.p_buffer,
                 &p_sys->immediate.p_buffer[STREAM_CACHE_SIZE] );
469
    }
470
    else
471
    {
472 473
        int i;

474 475
        assert( p_sys->method == STREAM_METHOD_STREAM );

476 477
        msg_Dbg( s, "Using AStream*Stream" );

478 479 480 481 482 483 484
        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 );
485 486
        if( p_sys->stream.p_buffer == NULL )
            goto error;
487
        p_sys->stream.i_used   = 0;
488
        access_Control( p_access, ACCESS_GET_MTU,
489
                         &p_sys->stream.i_read_size );
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
        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 );
506

507 508 509 510 511
        if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
512
    }
513

514
    return s;
515 516

error:
517
    if( p_sys->method == STREAM_METHOD_BLOCK )
518 519 520 521 522 523 524
    {
        /* Nothing yet */
    }
    else
    {
        free( p_sys->stream.p_buffer );
    }
525 526 527 528
    while( p_sys->i_list > 0 )
        free( p_sys->list[--(p_sys->i_list)] );
    free( p_sys->list );
    free( psz_list );
529 530
    free( s->p_sys );
    vlc_object_detach( s );
531
    vlc_object_release( s );
532
    return NULL;
533 534
}

535
/****************************************************************************
536
 * AStreamDestroy:
537
 ****************************************************************************/
538
static void AStreamDestroy( stream_t *s )
539
{
540 541 542 543
    stream_sys_t *p_sys = s->p_sys;

    vlc_object_detach( s );

544 545 546
    if( p_sys->record.b_active )
        ARecordSetState( s, false, NULL );

547
    if( p_sys->method == STREAM_METHOD_BLOCK )
548
        block_ChainRelease( p_sys->block.p_first );
549
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
550 551 552
        free( p_sys->immediate.p_buffer );
    else
        free( p_sys->stream.p_buffer );
553

554
    free( p_sys->p_peek );
555 556

    if( p_sys->p_list_access && p_sys->p_list_access != p_sys->p_access )
557
        access_Delete( p_sys->p_list_access );
558 559

    while( p_sys->i_list-- )
560
    {
561 562
        free( p_sys->list[p_sys->i_list]->psz_path );
        free( p_sys->list[p_sys->i_list] );
563 564
    }

565 566 567
    free( p_sys->list );
    free( p_sys );

568
    vlc_object_release( s );
569 570
}

571 572
static void UStreamDestroy( stream_t *s )
{
573
    access_t *p_access = (access_t *)s->p_parent;
574
    AStreamDestroy( s );
575
    access_Delete( p_access );
576 577
}

578 579 580 581 582 583 584 585 586
/****************************************************************************
 * 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;

587
    if( p_sys->method == STREAM_METHOD_BLOCK )
588 589 590 591 592 593 594 595 596 597 598 599 600 601
    {
        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 );
    }
602
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
603 604 605
    {
        stream_buffer_empty( s, stream_buffered_size( s ) );
    }
606
    else
607 608 609
    {
        int i;

610 611
        assert( p_sys->method == STREAM_METHOD_STREAM );

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
        /* 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 );
    }
}
628

629 630 631 632 633 634
/****************************************************************************
 * stream_AccessUpdate:
 ****************************************************************************/
void stream_AccessUpdate( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;
635

636
    p_sys->i_pos = p_sys->p_access->info.i_pos;
637 638 639 640 641 642 643 644 645

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

648
/****************************************************************************
649
 * AStreamControl:
650
 ****************************************************************************/
651
static int AStreamControl( stream_t *s, int i_query, va_list args )
652
{
653 654
    stream_sys_t *p_sys = s->p_sys;
    access_t     *p_access = p_sys->p_access;
655

656
    bool *p_bool;
657 658
    bool b_bool;
    const char *psz_string;
659 660
    int64_t    *pi_64, i_64;
    int        i_int;
661 662 663 664

    switch( i_query )
    {
        case STREAM_GET_SIZE:
665
            pi_64 = (int64_t*)va_arg( args, int64_t * );
666 667 668 669 670 671 672 673
            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;
            }
674 675
            *pi_64 = p_access->info.i_size;
            break;
676 677

        case STREAM_CAN_SEEK:
678
            p_bool = (bool*)va_arg( args, bool * );
679
            access_Control( p_access, ACCESS_CAN_SEEK, p_bool );
680
            break;
681 682

        case STREAM_CAN_FASTSEEK:
683
            p_bool = (bool*)va_arg( args, bool * );
684
            access_Control( p_access, ACCESS_CAN_FASTSEEK, p_bool );
685
            break;
686 687

        case STREAM_GET_POSITION:
688 689 690
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            *pi_64 = p_sys->i_pos;
            break;
691 692

        case STREAM_SET_POSITION:
693
            i_64 = (int64_t)va_arg( args, int64_t );
694 695 696
            switch( p_sys->method )
            {
            case STREAM_METHOD_BLOCK:
697
                return AStreamSeekBlock( s, i_64 );
698
            case STREAM_METHOD_IMMEDIATE:
699
                return AStreamSeekImmediate( s, i_64 );
700
            case STREAM_METHOD_STREAM:
701
                return AStreamSeekStream( s, i_64 );
702 703 704 705
            default:
                assert(0);
                return VLC_EGENERIC;
            }
706

707
        case STREAM_GET_MTU:
708
            return VLC_EGENERIC;
709

710
        case STREAM_CONTROL_ACCESS:
711
            i_int = (int) va_arg( args, int );
712 713 714
            if( i_int != ACCESS_SET_PRIVATE_ID_STATE &&
                i_int != ACCESS_SET_PRIVATE_ID_CA &&
                i_int != ACCESS_GET_PRIVATE_ID_STATE )
715 716 717 718 719
            {
                msg_Err( s, "Hey, what are you thinking ?"
                            "DON'T USE STREAM_CONTROL_ACCESS !!!" );
                return VLC_EGENERIC;
            }
720
            return access_vaControl( p_access, i_int, args );
721

722
        case STREAM_GET_CONTENT_TYPE:
723
            return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
724
                                    va_arg( args, char ** ) );
725 726 727 728 729 730
        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 );
731

732 733 734 735
        default:
            msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
            return VLC_EGENERIC;
    }
736
    return VLC_SUCCESS;
737 738
}

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
/****************************************************************************
 * 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 */
766
    psz_file = input_CreateFilename( VLC_OBJECT(s), psz_path, INPUT_RECORD_PREFIX, psz_extension );
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784

    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;
785
    p_sys->record.b_error = false;
786 787 788 789 790 791 792
    return VLC_SUCCESS;
}
static int  ARecordStop( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;

    assert( p_sys->record.b_active );
793

794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
    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 );

818 819 820 821 822 823 824 825 826 827 828 829 830
    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)" );
    }
831
}
832

833
/****************************************************************************
834
 * Method 1:
835
 ****************************************************************************/
836
static void AStreamPrebufferBlock( stream_t *s )
837
{
838
    stream_sys_t *p_sys = s->p_sys;
839
    access_t     *p_access = p_sys->p_access;
840

841 842
    int64_t i_first = 0;
    int64_t i_start;
843

844 845 846
    msg_Dbg( s, "pre buffering" );
    i_start = mdate();
    for( ;; )
847
    {
848
        int64_t i_date = mdate();
849
        bool b_eof;
850
        block_t *b;
851

852
        if( s->b_die || p_sys->block.i_size > STREAM_CACHE_PREBUFFER_SIZE ||
853 854 855 856 857 858 859
            ( i_first > 0 && i_first + STREAM_CACHE_PREBUFFER_LENGTH < i_date ) )
        {
            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
860
            i_byterate = ( INT64_C(1000000) * p_sys->stat.i_bytes ) /
861
                         (p_sys->stat.i_read_time + 1);
862

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
863
            msg_Dbg( s, "prebuffering done %"PRId64" bytes in %"PRId64"s - "
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
864
                     "%"PRId64" kbytes/s",
865
                     p_sys->stat.i_bytes,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
866
                     p_sys->stat.i_read_time / INT64_C(1000000),
867 868 869
                     i_byterate / 1024 );
            break;
        }
870

871
        /* Fetch a block */
872
        if( ( b = AReadBlock( s, &b_eof ) ) == NULL )
873
        {
874 875
            if( b_eof )
                break;
876
            continue;
877
        }
878

879 880 881 882 883 884
        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;
885

886 887 888
            p_sys->stat.i_read_count++;
            b = b->p_next;
        }
889

890
        if( p_access->info.b_prebuffered )
891 892 893 894 895 896 897 898 899 900 901 902 903
        {
            /* Access has already prebufferred - update stats and exit */
            p_sys->stat.i_bytes = p_sys->block.i_size;
            p_sys->stat.i_read_time = mdate() - i_start;
            break;
        }

        if( i_first == 0 )
        {
            i_first = mdate();
            msg_Dbg( s, "received first data for our buffer");
        }

904 905
    }

906 907 908 909 910
    p_sys->block.p_current = p_sys->block.p_first;
}

static int AStreamRefillBlock( stream_t *s );

911
static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read )
912 913 914
{
    stream_sys_t *p_sys = s->p_sys;

915 916
    uint8_t *p_data = p_read;
    uint8_t *p_record = p_data;
917
    unsigned int i_data = 0;
918 919 920 921 922

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

923 924 925 926
    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
927
    {
928
        /* seek within this stream if possible, else use plain old read and discard */
Damien Fouilleul's avatar
 
Damien Fouilleul committed
929 930
        stream_sys_t *p_sys = s->p_sys;
        access_t     *p_access = p_sys->p_access;
931
        bool   b_aseek;
932
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
Damien Fouilleul's avatar
 
Damien Fouilleul committed
933 934 935
        if( b_aseek )
            return AStreamSeekBlock( s, p_sys->i_pos + i_read ) ? 0 : i_read;
    }
936

937
    while( i_data < i_read )
938
    {
939 940
        int i_current =
            p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
941
        unsigned int i_copy = __MIN( (unsigned int)__MAX(i_current,0), i_read - i_data);
Gildas Bazin's avatar
 
Gildas Bazin committed
942

943
        /* Copy data */
944 945 946
        if( p_data )
        {
            memcpy( p_data,
947 948
                    &p_sys->block.p_current->p_buffer[p_sys->block.i_offset],
                    i_copy );
949 950
            p_data += i_copy;
        }
951
        i_data += i_copy;
952

953 954 955 956 957 958 959 960 961
        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
962 963
            /*Get a new block if needed */
            if( !p_sys->block.p_current && AStreamRefillBlock( s ) )
Gildas Bazin's avatar
Gildas Bazin committed
964 965 966
            {
                break;
            }
967 968 969
        }
    }

970 971 972 973 974 975 976
    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 );
    }
977

978 979
    p_sys->i_pos += i_data;
    return i_data;
980
}
Gildas Bazin's avatar
Gildas Bazin committed
981

982
static int AStreamPeekBlock( stream_t *s, const uint8_t **pp_peek, unsigned int i_read )
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
983
{
984 985
    stream_sys_t *p_sys = s->p_sys;
    uint8_t *p_data;
986
    unsigned int i_data = 0;
987
    block_t *b;
988
    unsigned int i_offset;
989

990
    if( p_sys->block.p_current == NULL ) return 0; /* EOF */
991 992 993 994 995 996 997 998 999 1000 1001

    /* 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 )
    {
1002 1003 1004 1005 1006 1007
        p_sys->p_peek = realloc( p_sys->p_peek, i_read );
        if( !p_sys->p_peek )
        {
            p_sys->i_peek = 0;
            return 0;
        }
1008 1009 1010 1011
        p_sys->i_peek = i_read;
    }

    /* Fill enough data */
1012 1013
    while( p_sys->block.i_size - (p_sys->i_pos - p_sys->block.i_start)
           < i_read )
1014 1015 1016
    {
        block_t **pp_last = p_sys->block.pp_last;

1017
        if( AStreamRefillBlock( s ) ) break;
1018 1019

        /* Our buffer are probably filled enough, don't try anymore */
1020
        if( pp_last == p_sys->block.pp_last ) break;
1021 1022 1023 1024 1025 1026 1027 1028 1029
    }

    /* 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 )
    {
1030
        unsigned int i_current = __MAX(b->i_buffer - i_offset,0);
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
        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
1047
}
1048

1049
static int AStreamSeekBlock( stream_t *s, int64_t i_pos )
1050
{
1051 1052 1053
    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;
1054
    bool b_seek;
1055

1056 1057 1058 1059 1060
    /* 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;
1061

1062 1063 1064 1065 1066
        while( i_current + b->i_buffer < i_offset )
        {
            i_current += b->i_buffer;
            b = b->p_next;
        }
1067

1068 1069
        p_sys->block.p_current = b;
        p_sys->block.i_offset = i_offset - i_current;
1070

1071
        p_sys->i_pos = i_pos;
1072

1073 1074
        return VLC_SUCCESS;
    }
1075

1076 1077
    /* We may need to seek or to read data */
    if( i_offset < 0 )
1078
    {
1079
        bool b_aseek;
1080
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
1081 1082 1083

        if( !b_aseek )
        {
1084
            msg_Err( s, "backward seeking impossible (access not seekable)" );
1085 1086 1087
            return VLC_EGENERIC;
        }

1088
        b_seek = true;
1089
    }
1090 1091
    else
    {
1092
        bool b_aseek, b_aseekfast;
1093

1094 1095
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
        access_Control( p_access, ACCESS_CAN_FASTSEEK, &b_aseekfast );
1096 1097 1098

        if( !b_aseek )
        {
1099
            b_seek = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1100
            msg_Warn( s, "%"PRId64" bytes need to be skipped "
1101
                      "(access non seekable)",
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
                      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 )
1115
                b_seek = false;
1116
            else
1117
                b_seek = true;
1118

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1119
            msg_Dbg( s, "b_seek=%d th*avg=%d skip=%"PRId64,
1120 1121
                     b_seek, i_th*i_avg, i_skip );
        }
1122 1123
    }

1124 1125 1126 1127 1128
    if( b_seek )
    {
        int64_t i_start, i_end;
        /* Do the access seek */
        i_start = mdate();
1129
        if( ASeek( s, i_pos ) ) return VLC_EGENERIC;
1130
        i_end = mdate();
1131

1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
        /* 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;