stream.c 66 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 29
#include <dirent.h>

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

35 36
#include <assert.h>

37
#include "input_internal.h"
38

39 40
#undef STREAM_DEBUG

41
/* TODO:
42
 *  - tune the 2 methods (block/stream)
43 44 45
 *  - compute cost for seek
 *  - improve stream mode seeking with closest segments
 *  - ...
46
 *  - Maybe remove (block/stream) in favour of immediate
47
 */
48 49 50 51 52 53

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

59 60
/* How many tracks we have, currently only used for stream mode */
#ifdef OPTIMIZE_MEMORY
61 62 63 64 65 66 67 68 69 70
#   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 */
71
#define STREAM_CACHE_PREBUFFER_SIZE (32767)
72 73
/* Maximum time we take to pre-buffer */
#define STREAM_CACHE_PREBUFFER_LENGTH (100*1000)
74 75 76 77 78

/* 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
 */
79
#define STREAM_DATA_WAIT 40000       /* Time between before a pf_block retry */
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

/* 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
98
{
99
    int64_t i_date;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
100

101 102 103 104
    int64_t i_start;
    int64_t i_end;

    uint8_t *p_buffer;
105

106 107
} stream_track_t;

108 109 110 111 112 113 114
typedef struct
{
    char     *psz_path;
    int64_t  i_size;

} access_entry_t;

115 116 117 118 119 120 121
typedef enum stream_read_method_t
{
    Immediate,
    Block,
    Stream
} stream_read_method_t;

122 123 124 125
struct stream_sys_t
{
    access_t    *p_access;

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

    int64_t     i_pos;      /* Current reading offset */

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

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

141 142 143 144
    } block;

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

157 158
    } stream;

159 160 161 162 163 164 165
    /* Method 3: for pf_read */
    struct
    {
        int64_t i_end;
        uint8_t *p_buffer;
    } immediate;

166
    /* Peek temporary buffer */
167
    unsigned int i_peek;
168 169 170 171
    uint8_t *p_peek;

    /* Stat for both method */
    struct
172
    {
173
        bool b_fastseek;  /* From access */
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
174

175 176 177 178
        /* Stat about reading data */
        int64_t i_read_count;
        int64_t i_bytes;
        int64_t i_read_time;
179

180 181 182
        /* Stat about seek */
        int     i_seek_count;
        int64_t i_seek_time;
183

184
    } stat;
185

186 187 188 189 190 191
    /* Streams list */
    int            i_list;
    access_entry_t **list;
    int            i_list_index;
    access_t       *p_list_access;

192
    /* Preparse mode ? */
193
    bool      b_quick;
194 195 196 197 198 199 200 201

    /* */
    struct
    {
        bool b_active;

        FILE *f;    /* TODO it could be replaced by access_output_t one day */
    } record;
202
};
203

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

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

218
/* Method 3 */
219 220
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 );
221 222
static int  AStreamSeekImmediate( stream_t *s, int64_t i_pos );

223
/* Common */
224
static int AStreamControl( stream_t *s, int i_query, va_list );
225 226
static void AStreamDestroy( stream_t *s );
static void UStreamDestroy( stream_t *s );
227
static int  ASeek( stream_t *s, int64_t i_pos );
228 229
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 );
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 255 256 257 258 259 260
/****************************************************************************
 * 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;
}
261

262
/****************************************************************************
263
 * stream_UrlNew: create a stream from a access
264
 ****************************************************************************/
265
stream_t *__stream_UrlNew( vlc_object_t *p_parent, const char *psz_url )
266
{
267 268
    const char *psz_access, *psz_demux;
    char *psz_path;
269 270
    access_t *p_access;
    stream_t *p_res;
271

272 273
    if( !psz_url )
        return NULL;
274

275 276 277
    char psz_dup[strlen( psz_url ) + 1];
    strcpy( psz_dup, psz_url );
    input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_dup );
278

279
    /* Now try a real access */
280
    p_access = access_New( p_parent, psz_access, psz_demux, psz_path );
281 282 283 284 285 286

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

288
    if( !( p_res = stream_AccessNew( p_access, true ) ) )
289
    {
290
        access_Delete( p_access );
291
        return NULL;
292
    }
293 294 295

    p_res->pf_destroy = UStreamDestroy;
    return p_res;
296 297
}

298
stream_t *stream_AccessNew( access_t *p_access, bool b_quick )
299
{
300
    stream_t *s = vlc_stream_create( VLC_OBJECT(p_access) );
301
    stream_sys_t *p_sys;
302
    char *psz_list = NULL;
303

304
    if( !s ) return NULL;
305 306 307 308 309 310

    /* 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
311
    s->pf_control = AStreamControl;
312
    s->pf_destroy = AStreamDestroy;
313 314

    s->p_sys = p_sys = malloc( sizeof( stream_sys_t ) );
315 316
    if( p_sys == NULL )
        goto error;
317

318 319
    /* UTF16 and UTF32 text file conversion */
    s->i_char_width = 1;
320
    s->b_little_endian = false;
321
    s->conv = (vlc_iconv_t)(-1);
322

323 324
    /* Common field */
    p_sys->p_access = p_access;
325 326 327 328 329 330 331
    if( p_access->pf_block )
        p_sys->method = Block;
    else if (var_CreateGetBool( s, "use-stream-immediate"))
        p_sys->method = Immediate;
    else
        p_sys->method = Stream;

332 333
    p_sys->record.b_active = false;

334 335 336
    p_sys->i_pos = p_access->info.i_pos;

    /* Stats */
337
    access_Control( p_access, ACCESS_CAN_FASTSEEK, &p_sys->stat.b_fastseek );
338 339 340 341 342 343
    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;

344 345 346 347 348
    p_sys->i_list = 0;
    p_sys->list = 0;
    p_sys->i_list_index = 0;
    p_sys->p_list_access = 0;

349 350
    p_sys->b_quick = b_quick;

351 352 353 354
    /* 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) );
355 356
        if( p_entry == NULL )
            goto error;
357 358 359 360 361
        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 );
362 363 364 365 366
        if( p_entry->psz_path == NULL )
        {
            free( p_entry );
            goto error;
        }
367
        TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
368
        msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
369 370 371 372 373 374 375 376 377 378
                 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 )
            {
379
                access_t *p_tmp = access_New( p_access, p_access->psz_access,
380
                                               "", psz_name );
381 382 383 384 385 386 387 388

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
389
                msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
390 391 392
                         psz_name, p_tmp->info.i_size );

                p_entry = malloc( sizeof(access_entry_t) );
393 394
                if( p_entry == NULL )
                    goto error;
395 396 397 398
                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 );

399
                access_Delete( p_tmp );
400 401 402 403 404 405
            }

            psz_name = psz_parser;
            if( psz_name ) psz_name++;
        }
    }
406
    FREENULL( psz_list );
407

408 409 410 411
    /* Peek */
    p_sys->i_peek = 0;
    p_sys->p_peek = NULL;

412
    if( p_sys->method == Block )
413
    {
414
        msg_Dbg( s, "Using AStream*Block" );
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
        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;
        }
    }
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
    else if (p_sys->method == Immediate)
    {
        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 );

        msg_Dbg( s, "p_buffer %p-%p", p_sys->immediate.p_buffer,
                p_sys->immediate.p_buffer + STREAM_CACHE_SIZE );

        if( p_sys->immediate.p_buffer == NULL )
        {
            msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
                        STREAM_CACHE_SIZE );
            goto error;
        }
    }
    else /* ( p_sys->method == Stream ) */
457
    {
458 459
        int i;

460 461
        msg_Dbg( s, "Using AStream*Stream" );

462 463 464 465 466 467 468
        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 );
469 470 471 472 473 474
        if( p_sys->stream.p_buffer == NULL )
        {
            msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
                        STREAM_CACHE_SIZE );
            goto error;
        }
475
        p_sys->stream.i_used   = 0;
476
        access_Control( p_access, ACCESS_GET_MTU,
477
                         &p_sys->stream.i_read_size );
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
        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 );
494

495 496 497 498 499
        if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
500
    }
501

502
    return s;
503 504

error:
505
    if( p_sys->method == Block )
506 507 508 509 510 511 512
    {
        /* Nothing yet */
    }
    else
    {
        free( p_sys->stream.p_buffer );
    }
513 514 515 516
    while( p_sys->i_list > 0 )
        free( p_sys->list[--(p_sys->i_list)] );
    free( p_sys->list );
    free( psz_list );
517 518
    free( s->p_sys );
    vlc_object_detach( s );
519
    vlc_object_release( s );
520
    return NULL;
521 522
}

523
/****************************************************************************
524
 * AStreamDestroy:
525
 ****************************************************************************/
526
static void AStreamDestroy( stream_t *s )
527
{
528 529 530 531
    stream_sys_t *p_sys = s->p_sys;

    vlc_object_detach( s );

532 533 534 535 536 537 538 539 540
    if( p_sys->record.b_active )
        ARecordSetState( s, false, NULL );

    if( p_sys->method == Block )
        block_ChainRelease( p_sys->block.p_first );
    else if ( p_sys->method == Immediate )
        free( p_sys->immediate.p_buffer );
    else
        free( p_sys->stream.p_buffer );
541

542
    free( p_sys->p_peek );
543 544

    if( p_sys->p_list_access && p_sys->p_list_access != p_sys->p_access )
545
        access_Delete( p_sys->p_list_access );
546 547

    while( p_sys->i_list-- )
548
    {
549 550
        free( p_sys->list[p_sys->i_list]->psz_path );
        free( p_sys->list[p_sys->i_list] );
551 552
    }

553 554 555
    free( p_sys->list );
    free( p_sys );

556
    vlc_object_release( s );
557 558
}

559 560
static void UStreamDestroy( stream_t *s )
{
561
    access_t *p_access = (access_t *)s->p_parent;
562
    AStreamDestroy( s );
563
    access_Delete( p_access );
564 565
}

566 567 568 569 570 571 572 573 574
/****************************************************************************
 * 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;

575
    if( p_sys->method == Block )
576 577 578 579 580 581 582 583 584 585 586 587 588 589
    {
        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 );
    }
590 591 592 593 594
    else if( p_sys->method == Immediate )
    {
        stream_buffer_empty( s, stream_buffered_size( s ) );
    }
    else /* ( p_sys->method == Stream ) */
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    {
        int i;

        /* 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 );
    }
}
614

615 616 617 618 619 620
/****************************************************************************
 * stream_AccessUpdate:
 ****************************************************************************/
void stream_AccessUpdate( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;
621

622
    p_sys->i_pos = p_sys->p_access->info.i_pos;
623 624 625 626 627 628 629 630 631

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

634
/****************************************************************************
635
 * AStreamControl:
636
 ****************************************************************************/
637
static int AStreamControl( stream_t *s, int i_query, va_list args )
638
{
639 640
    stream_sys_t *p_sys = s->p_sys;
    access_t     *p_access = p_sys->p_access;
641

642
    bool *p_bool;
643 644
    bool b_bool;
    const char *psz_string;
645 646
    int64_t    *pi_64, i_64;
    int        i_int;
647 648 649 650

    switch( i_query )
    {
        case STREAM_GET_SIZE:
651
            pi_64 = (int64_t*)va_arg( args, int64_t * );
652 653 654 655 656 657 658 659
            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;
            }
660 661
            *pi_64 = p_access->info.i_size;
            break;
662 663

        case STREAM_CAN_SEEK:
664
            p_bool = (bool*)va_arg( args, bool * );
665
            access_Control( p_access, ACCESS_CAN_SEEK, p_bool );
666
            break;
667 668

        case STREAM_CAN_FASTSEEK:
669
            p_bool = (bool*)va_arg( args, bool * );
670
            access_Control( p_access, ACCESS_CAN_FASTSEEK, p_bool );
671
            break;
672 673

        case STREAM_GET_POSITION:
674 675 676
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            *pi_64 = p_sys->i_pos;
            break;
677 678

        case STREAM_SET_POSITION:
679
            i_64 = (int64_t)va_arg( args, int64_t );
680
            if( p_sys->method == Block )
681
                return AStreamSeekBlock( s, i_64 );
682 683 684
            else if( p_sys->method == Immediate )
                return AStreamSeekImmediate( s, i_64 );
            else /* ( p_sys->method == Stream ) */
685
                return AStreamSeekStream( s, i_64 );
686

687
        case STREAM_GET_MTU:
688
            return VLC_EGENERIC;
689

690
        case STREAM_CONTROL_ACCESS:
691
            i_int = (int) va_arg( args, int );
692 693 694
            if( i_int != ACCESS_SET_PRIVATE_ID_STATE &&
                i_int != ACCESS_SET_PRIVATE_ID_CA &&
                i_int != ACCESS_GET_PRIVATE_ID_STATE )
695 696 697 698 699
            {
                msg_Err( s, "Hey, what are you thinking ?"
                            "DON'T USE STREAM_CONTROL_ACCESS !!!" );
                return VLC_EGENERIC;
            }
700
            return access_vaControl( p_access, i_int, args );
701

702
        case STREAM_GET_CONTENT_TYPE:
703
            return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
704
                                    va_arg( args, char ** ) );
705 706 707 708 709 710
        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 );
711

712 713 714 715
        default:
            msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
            return VLC_EGENERIC;
    }
716
    return VLC_SUCCESS;
717 718
}

719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 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 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
/****************************************************************************
 * ARecord*: record stream functions
 ****************************************************************************/

/* TODO FIXME nearly the same logic that snapshot code */
static char *ARecordGetFileName( stream_t *s, const char *psz_path, const char *psz_prefix, const char *psz_extension )
{
    char *psz_file;
    DIR *path;

    path = utf8_opendir( psz_path );
    if( path )
    {
        closedir( path );

        const char *psz_prefix = "vlc-record-%Y-%m-%d-%H:%M:%S-$p";  // TODO allow conf ?
        char *psz_tmp = str_format( s, psz_prefix );
        if( !psz_tmp )
            return NULL;

        filename_sanitize( psz_tmp );
        if( asprintf( &psz_file, "%s"DIR_SEP"%s.%s",
                      psz_path, psz_tmp, psz_extension ) < 0 )
            psz_file = NULL;
        free( psz_tmp );
        return psz_file;
    }
    else
    {
        psz_file = str_format( s, psz_path );
        path_sanitize( psz_file );
        return psz_file;
    }
}

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

    DIR *path;
    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 */
    psz_file = ARecordGetFileName( s, psz_path, "vlc-record-%Y-%m-%d-%H:%M:%S-$p", psz_extension );

    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;
    return VLC_SUCCESS;
}
static int  ARecordStop( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;

    assert( p_sys->record.b_active );
805

806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
    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 );

    if( i_buffer )
        fwrite( p_buffer, 1, i_buffer, p_sys->record.f );
}
833

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

842 843
    int64_t i_first = 0;
    int64_t i_start;
844

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

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

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

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

            msleep( STREAM_DATA_WAIT );
            continue;
879
        }
880

881 882 883 884 885 886
        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;
887

888 889 890
            p_sys->stat.i_read_count++;
            b = b->p_next;
        }
891

892
        if( p_access->info.b_prebuffered )
893 894 895 896 897 898 899 900 901 902 903 904 905
        {
            /* 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");
        }

906 907
    }

908 909 910 911 912
    p_sys->block.p_current = p_sys->block.p_first;
}

static int AStreamRefillBlock( stream_t *s );

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

    uint8_t *p_data= (uint8_t*)p_read;
918
    unsigned int i_data = 0;
919 920 921 922 923

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

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

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

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

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

968 969 970
    if( p_sys->record.b_active && i_data > 0 )
        ARecordWrite( s, p_read, i_data );

971 972
    p_sys->i_pos += i_data;
    return i_data;
973
}
Gildas Bazin's avatar
Gildas Bazin committed
974

975
static int AStreamPeekBlock( stream_t *s, const uint8_t **pp_peek, unsigned int i_read )
Sigmund Augdal Helberg's avatar
Sigmund Augdal Helberg committed
976
{
977 978
    stream_sys_t *p_sys = s->p_sys;
    uint8_t *p_data;
979
    unsigned int i_data = 0;
980
    block_t *b;
981
    unsigned int i_offset;
982

983
    if( p_sys->block.p_current == NULL ) return 0; /* EOF */
984 985 986 987 988 989 990 991 992 993 994

    /* 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 )
    {
995 996 997 998 999 1000
        p_sys->p_peek = realloc( p_sys->p_peek, i_read );
        if( !p_sys->p_peek )
        {
            p_sys->i_peek = 0;
            return 0;
        }
1001 1002 1003 1004
        p_sys->i_peek = i_read;
    }

    /* Fill enough data */
1005 1006
    while( p_sys->block.i_size - (p_sys->i_pos - p_sys->block.i_start)
           < i_read )
1007 1008 1009
    {
        block_t **pp_last = p_sys->block.pp_last;

1010
        if( AStreamRefillBlock( s ) ) break;
1011 1012

        /* Our buffer are probably filled enough, don't try anymore */
1013
        if( pp_last == p_sys->block.pp_last ) break;
1014 1015 1016 1017 1018 1019 1020 1021 1022
    }

    /* 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 )
    {
1023
        unsigned int i_current = __MAX(b->i_buffer - i_offset,0);
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
        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
1040
}
1041

1042
static int AStreamSeekBlock( stream_t *s, int64_t i_pos )
1043
{
1044 1045 1046
    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;
1047
    bool b_seek;
1048

1049 1050 1051 1052 1053
    /* 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;
1054

1055 1056 1057 1058 1059
        while( i_current + b->i_buffer < i_offset )
        {
            i_current += b->i_buffer;
            b = b->p_next;
        }
1060

1061 1062
        p_sys->block.p_current = b;
        p_sys->block.i_offset = i_offset - i_current;
1063

1064
        p_sys->i_pos = i_pos;
1065

1066 1067
        return VLC_SUCCESS;
    }
1068

1069 1070
    /* We may need to seek or to read data */
    if( i_offset < 0 )
1071
    {
1072
        bool b_aseek;
1073
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
1074 1075 1076

        if( !b_aseek )
        {
1077
            msg_Err( s, "backward seeking impossible (access not seekable)" );
1078 1079 1080
            return VLC_EGENERIC;
        }

1081
        b_seek = true;
1082
    }
1083 1084
    else
    {
1085
        bool b_aseek, b_aseekfast;
1086

1087 1088
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
        access_Control( p_access, ACCESS_CAN_FASTSEEK, &b_aseekfast );
1089 1090 1091

        if( !b_aseek )
        {
1092
            b_seek = false;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1093
            msg_Warn( s, "%"PRId64" bytes need to be skipped "
1094
                      "(access non seekable)",
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
                      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 )
1108
                b_seek = false;
1109
            else
1110
                b_seek = true;
1111

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1112
            msg_Dbg( s, "b_seek=%d th*avg=%d skip=%"PRId64,
1113 1114
                     b_seek, i_th*i_avg, i_skip );
        }
1115 1116
    }

1117 1118 1119 1120 1121
    if( b_seek )
    {
        int64_t i_start, i_end;
        /* Do the access seek */
        i_start = mdate();
1122
        if( ASeek( s, i_pos ) ) return VLC_EGENERIC;
1123
        i_end = mdate();
1124

1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
        /* 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;
1135

1136 1137 1138
        /* Refill a block */
        if( AStreamRefillBlock( s ) )
            return VLC_EGENERIC;
1139

1140 1141 1142 1143 1144 1145
        /* Update stat */
        p_sys->stat.i_seek_time += i_end - i_start;
        p_sys->stat.i_seek_count++;
        return VLC_SUCCESS;
    }
    else
1146
    {
1147
        do
1148
        {
1149
            /* Read and skip enough data */
1150 1151
            if( AStreamRefillBlock( s ) )
                return VLC_EGENERIC;
1152

1153
            while( p_sys->block.p_current &&
1154
                   p_sys->i_pos + p_sys->block.p_current->i_buffer - p_sys->block.i_offset < i_pos )
1155
            {
1156
                p_sys->i_pos += p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
1157
                p_sys->block.p_current = p_sys->block.p_current->p_next;
1158
                p_sys->block.i_offset = 0;
1159 1160
            }
        }
1161
        while( p_sys->block.i_start + p_sys->block.i_size < i_pos );
1162

1163 1164
        p_sys->block.i_offset = i_pos - p_sys->i_pos;
        p_sys->i_pos = i_pos;
1165

1166
        return VLC_SUCCESS;
1167 1168
    }

1169
    return VLC_EGENERIC;
1170 1171
}

1172
static int AStreamRefillBlock( stream_t *s )
1173
{
1174 1175 1176 1177