stream.c 65.5 KB
Newer Older
1
2
3
/*****************************************************************************
 * stream.c
 *****************************************************************************
4
 * Copyright (C) 1999-2004 the VideoLAN team
gbazin's avatar
gbazin 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
dionoea's avatar
dionoea 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
#include <vlc_strings.h>
#include <vlc_osd.h>
33
#include <vlc_charset.h>
34

35
36
#include <assert.h>

Laurent Aimar's avatar
Laurent Aimar committed
37
#include "input_internal.h"
38

39
40
#undef STREAM_DEBUG

Laurent Aimar's avatar
Laurent Aimar committed
41
/* TODO:
42
 *  - tune the 2 methods (block/stream)
Laurent Aimar's avatar
Laurent Aimar committed
43
44
45
 *  - compute cost for seek
 *  - improve stream mode seeking with closest segments
 *  - ...
46
 *  - Maybe remove (block/stream) in favour of immediate
47
 */
Laurent Aimar's avatar
Laurent Aimar committed
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)).
Laurent Aimar's avatar
Laurent Aimar committed
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 */
Laurent Aimar's avatar
Laurent Aimar committed
71
#define STREAM_CACHE_PREBUFFER_SIZE (32767)
72
73
/* Maximum time we take to pre-buffer */
#define STREAM_CACHE_PREBUFFER_LENGTH (100*1000)
Laurent Aimar's avatar
Laurent Aimar committed
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

/* 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
97
{
Laurent Aimar's avatar
Laurent Aimar committed
98
    int64_t i_date;
hartman's avatar
hartman committed
99

Laurent Aimar's avatar
Laurent Aimar committed
100
101
102
103
    int64_t i_start;
    int64_t i_end;

    uint8_t *p_buffer;
104

Laurent Aimar's avatar
Laurent Aimar committed
105
106
} stream_track_t;

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

} access_entry_t;

114
115
typedef enum stream_read_method_t
{
116
117
118
    STREAM_METHOD_IMMEDIATE,
    STREAM_METHOD_BLOCK,
    STREAM_METHOD_STREAM
119
120
} stream_read_method_t;

Laurent Aimar's avatar
Laurent Aimar committed
121
122
123
124
struct stream_sys_t
{
    access_t    *p_access;

125
    stream_read_method_t   method;    /* method to use */
Laurent Aimar's avatar
Laurent Aimar committed
126
127
128
129
130

    int64_t     i_pos;      /* Current reading offset */

    /* Method 1: pf_block */
    struct
131
    {
Laurent Aimar's avatar
Laurent Aimar committed
132
        int64_t i_start;        /* Offset of block for p_first */
133
        int64_t i_offset;       /* Offset for data in p_current */
Laurent Aimar's avatar
Laurent Aimar committed
134
135
136
137
138
        block_t *p_current;     /* Current block */

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

Laurent Aimar's avatar
Laurent Aimar committed
140
141
142
143
    } block;

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

Laurent Aimar's avatar
Laurent Aimar committed
156
157
    } stream;

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

Laurent Aimar's avatar
Laurent Aimar committed
165
    /* Peek temporary buffer */
166
    unsigned int i_peek;
Laurent Aimar's avatar
Laurent Aimar committed
167
168
169
170
    uint8_t *p_peek;

    /* Stat for both method */
    struct
171
    {
172
        bool b_fastseek;  /* From access */
hartman's avatar
hartman committed
173

Laurent Aimar's avatar
Laurent Aimar committed
174
175
176
177
        /* Stat about reading data */
        int64_t i_read_count;
        int64_t i_bytes;
        int64_t i_read_time;
178

Laurent Aimar's avatar
Laurent Aimar committed
179
180
181
        /* Stat about seek */
        int     i_seek_count;
        int64_t i_seek_time;
182

Laurent Aimar's avatar
Laurent Aimar committed
183
    } stat;
184

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

191
    /* Preparse mode ? */
192
    bool      b_quick;
193
194
195
196
197
198

    /* */
    struct
    {
        bool b_active;

199
200
        FILE *f;        /* TODO it could be replaced by access_output_t one day */
        bool b_error;
201
    } record;
Laurent Aimar's avatar
Laurent Aimar committed
202
};
203

Laurent Aimar's avatar
Laurent Aimar committed
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 );
Laurent Aimar's avatar
Laurent Aimar committed
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

Laurent Aimar's avatar
Laurent Aimar committed
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 );
Laurent Aimar's avatar
Laurent Aimar committed
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 );

Laurent Aimar's avatar
Laurent Aimar committed
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

Laurent Aimar's avatar
Laurent Aimar committed
272
273
    if( !psz_url )
        return NULL;
274

Laurent Aimar's avatar
Laurent Aimar committed
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) );
Laurent Aimar's avatar
Laurent Aimar committed
301
    stream_sys_t *p_sys;
302
    char *psz_list = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
303

hartman's avatar
hartman committed
304
305
306
307
308
309
310
311
312
    if( !s )
        return NULL;

    s->p_sys = p_sys = malloc( sizeof( stream_sys_t ) );
    if( !p_sys )
    {
        vlc_object_release( s );
        return NULL;
    }
Laurent Aimar's avatar
Laurent Aimar committed
313
314
315
316
317
318

    /* 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
319
    s->pf_control = AStreamControl;
320
    s->pf_destroy = AStreamDestroy;
Laurent Aimar's avatar
Laurent Aimar committed
321

322
323
    /* UTF16 and UTF32 text file conversion */
    s->i_char_width = 1;
324
    s->b_little_endian = false;
hartman's avatar
hartman committed
325
    s->conv = (vlc_iconv_t)(-1);
326

Laurent Aimar's avatar
Laurent Aimar committed
327
328
    /* Common field */
    p_sys->p_access = p_access;
329
    if( p_access->pf_block )
330
331
332
        p_sys->method = STREAM_METHOD_BLOCK;
    else if( var_CreateGetBool( s, "use-stream-immediate" ) )
        p_sys->method = STREAM_METHOD_IMMEDIATE;
333
    else
334
        p_sys->method = STREAM_METHOD_STREAM;
335

336
337
    p_sys->record.b_active = false;

Laurent Aimar's avatar
Laurent Aimar committed
338
339
340
    p_sys->i_pos = p_access->info.i_pos;

    /* Stats */
341
    access_Control( p_access, ACCESS_CAN_FASTSEEK, &p_sys->stat.b_fastseek );
Laurent Aimar's avatar
Laurent Aimar committed
342
343
344
345
346
347
    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;

348
349
350
351
352
    p_sys->i_list = 0;
    p_sys->list = 0;
    p_sys->i_list_index = 0;
    p_sys->p_list_access = 0;

353
354
    p_sys->b_quick = b_quick;

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
393
                msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
394
395
396
                         psz_name, p_tmp->info.i_size );

                p_entry = malloc( sizeof(access_entry_t) );
397
398
                if( p_entry == NULL )
                    goto error;
399
400
401
402
                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 );

403
                access_Delete( p_tmp );
404
405
406
407
408
409
            }

            psz_name = psz_parser;
            if( psz_name ) psz_name++;
        }
    }
410
    FREENULL( psz_list );
411

Laurent Aimar's avatar
Laurent Aimar committed
412
413
414
415
    /* Peek */
    p_sys->i_peek = 0;
    p_sys->p_peek = NULL;

416
    if( p_sys->method == STREAM_METHOD_BLOCK )
Laurent Aimar's avatar
Laurent Aimar committed
417
    {
418
        msg_Dbg( s, "Using AStream*Block" );
Laurent Aimar's avatar
Laurent Aimar committed
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
        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;
        }
    }
439
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
440
441
442
443
444
445
446
447
448
449
    {
        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 );

450
451
452
        msg_Dbg( s, "p_buffer %p-%p",
                 p_sys->immediate.p_buffer,
                 &p_sys->immediate.p_buffer[STREAM_CACHE_SIZE] );
453
454
455
456
457
458
459
460

        if( p_sys->immediate.p_buffer == NULL )
        {
            msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
                        STREAM_CACHE_SIZE );
            goto error;
        }
    }
461
    else
462
    {
Laurent Aimar's avatar
Laurent Aimar committed
463
464
        int i;

465
466
        assert( p_sys->method == STREAM_METHOD_STREAM );

467
468
        msg_Dbg( s, "Using AStream*Stream" );

Laurent Aimar's avatar
Laurent Aimar committed
469
470
471
472
473
474
475
        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 );
476
477
478
479
480
481
        if( p_sys->stream.p_buffer == NULL )
        {
            msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
                        STREAM_CACHE_SIZE );
            goto error;
        }
Laurent Aimar's avatar
Laurent Aimar committed
482
        p_sys->stream.i_used   = 0;
483
        access_Control( p_access, ACCESS_GET_MTU,
484
                         &p_sys->stream.i_read_size );
Laurent Aimar's avatar
Laurent Aimar committed
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
        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 );
501

Laurent Aimar's avatar
Laurent Aimar committed
502
503
504
505
506
        if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
507
    }
Laurent Aimar's avatar
Laurent Aimar committed
508

509
    return s;
Laurent Aimar's avatar
Laurent Aimar committed
510
511

error:
512
    if( p_sys->method == STREAM_METHOD_BLOCK )
Laurent Aimar's avatar
Laurent Aimar committed
513
514
515
516
517
518
519
    {
        /* Nothing yet */
    }
    else
    {
        free( p_sys->stream.p_buffer );
    }
520
521
522
523
    while( p_sys->i_list > 0 )
        free( p_sys->list[--(p_sys->i_list)] );
    free( p_sys->list );
    free( psz_list );
Laurent Aimar's avatar
Laurent Aimar committed
524
525
    free( s->p_sys );
    vlc_object_detach( s );
526
    vlc_object_release( s );
Laurent Aimar's avatar
Laurent Aimar committed
527
    return NULL;
528
529
}

530
/****************************************************************************
531
 * AStreamDestroy:
532
 ****************************************************************************/
533
static void AStreamDestroy( stream_t *s )
534
{
Laurent Aimar's avatar
Laurent Aimar committed
535
536
537
538
    stream_sys_t *p_sys = s->p_sys;

    vlc_object_detach( s );

539
540
541
    if( p_sys->record.b_active )
        ARecordSetState( s, false, NULL );

542
    if( p_sys->method == STREAM_METHOD_BLOCK )
543
        block_ChainRelease( p_sys->block.p_first );
544
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
545
546
547
        free( p_sys->immediate.p_buffer );
    else
        free( p_sys->stream.p_buffer );
548

549
    free( p_sys->p_peek );
550
551

    if( p_sys->p_list_access && p_sys->p_list_access != p_sys->p_access )
552
        access_Delete( p_sys->p_list_access );
553
554

    while( p_sys->i_list-- )
Laurent Aimar's avatar
Laurent Aimar committed
555
    {
556
557
        free( p_sys->list[p_sys->i_list]->psz_path );
        free( p_sys->list[p_sys->i_list] );
Laurent Aimar's avatar
Laurent Aimar committed
558
559
    }

560
561
562
    free( p_sys->list );
    free( p_sys );

563
    vlc_object_release( s );
564
565
}

566
567
static void UStreamDestroy( stream_t *s )
{
568
    access_t *p_access = (access_t *)s->p_parent;
569
    AStreamDestroy( s );
570
    access_Delete( p_access );
571
572
}

Laurent Aimar's avatar
Laurent Aimar committed
573
574
575
576
577
578
579
580
581
/****************************************************************************
 * 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;

582
    if( p_sys->method == STREAM_METHOD_BLOCK )
Laurent Aimar's avatar
Laurent Aimar committed
583
584
585
586
587
588
589
590
591
592
593
594
595
596
    {
        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 );
    }
597
    else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
598
599
600
    {
        stream_buffer_empty( s, stream_buffered_size( s ) );
    }
601
    else
Laurent Aimar's avatar
Laurent Aimar committed
602
603
604
    {
        int i;

605
606
        assert( p_sys->method == STREAM_METHOD_STREAM );

Laurent Aimar's avatar
Laurent Aimar committed
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
        /* 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 );
    }
}
623

624
625
626
627
628
629
/****************************************************************************
 * stream_AccessUpdate:
 ****************************************************************************/
void stream_AccessUpdate( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;
630

631
    p_sys->i_pos = p_sys->p_access->info.i_pos;
632
633
634
635
636
637
638
639
640

    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;
        }
    }
641
642
}

643
/****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
644
 * AStreamControl:
645
 ****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
646
static int AStreamControl( stream_t *s, int i_query, va_list args )
647
{
Laurent Aimar's avatar
Laurent Aimar committed
648
649
    stream_sys_t *p_sys = s->p_sys;
    access_t     *p_access = p_sys->p_access;
650

651
    bool *p_bool;
652
653
    bool b_bool;
    const char *psz_string;
Laurent Aimar's avatar
Laurent Aimar committed
654
655
    int64_t    *pi_64, i_64;
    int        i_int;
656
657
658
659

    switch( i_query )
    {
        case STREAM_GET_SIZE:
Laurent Aimar's avatar
Laurent Aimar committed
660
            pi_64 = (int64_t*)va_arg( args, int64_t * );
661
662
663
664
665
666
667
668
            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;
            }
Laurent Aimar's avatar
Laurent Aimar committed
669
670
            *pi_64 = p_access->info.i_size;
            break;
671
672

        case STREAM_CAN_SEEK:
673
            p_bool = (bool*)va_arg( args, bool * );
674
            access_Control( p_access, ACCESS_CAN_SEEK, p_bool );
Laurent Aimar's avatar
Laurent Aimar committed
675
            break;
676
677

        case STREAM_CAN_FASTSEEK:
678
            p_bool = (bool*)va_arg( args, bool * );
679
            access_Control( p_access, ACCESS_CAN_FASTSEEK, p_bool );
Laurent Aimar's avatar
Laurent Aimar committed
680
            break;
681
682

        case STREAM_GET_POSITION:
Laurent Aimar's avatar
Laurent Aimar committed
683
684
685
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            *pi_64 = p_sys->i_pos;
            break;
686
687

        case STREAM_SET_POSITION:
Laurent Aimar's avatar
Laurent Aimar committed
688
            i_64 = (int64_t)va_arg( args, int64_t );
689
690
691
            switch( p_sys->method )
            {
            case STREAM_METHOD_BLOCK:
Laurent Aimar's avatar
Laurent Aimar committed
692
                return AStreamSeekBlock( s, i_64 );
693
            case STREAM_METHOD_IMMEDIATE:
694
                return AStreamSeekImmediate( s, i_64 );
695
            case STREAM_METHOD_STREAM:
Laurent Aimar's avatar
Laurent Aimar committed
696
                return AStreamSeekStream( s, i_64 );
697
698
699
700
            default:
                assert(0);
                return VLC_EGENERIC;
            }
701

702
        case STREAM_GET_MTU:
Laurent Aimar's avatar
Laurent Aimar committed
703
            return VLC_EGENERIC;
704

705
        case STREAM_CONTROL_ACCESS:
Laurent Aimar's avatar
Laurent Aimar committed
706
            i_int = (int) va_arg( args, int );
707
708
709
            if( i_int != ACCESS_SET_PRIVATE_ID_STATE &&
                i_int != ACCESS_SET_PRIVATE_ID_CA &&
                i_int != ACCESS_GET_PRIVATE_ID_STATE )
710
711
712
713
714
            {
                msg_Err( s, "Hey, what are you thinking ?"
                            "DON'T USE STREAM_CONTROL_ACCESS !!!" );
                return VLC_EGENERIC;
            }
715
            return access_vaControl( p_access, i_int, args );
716

717
        case STREAM_GET_CONTENT_TYPE:
718
            return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
719
                                    va_arg( args, char ** ) );
720
721
722
723
724
725
        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 );
726

727
728
729
730
        default:
            msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
            return VLC_EGENERIC;
    }
Laurent Aimar's avatar
Laurent Aimar committed
731
    return VLC_SUCCESS;
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
/****************************************************************************
 * 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 */
761
    psz_file = input_CreateFilename( VLC_OBJECT(s), psz_path, INPUT_RECORD_PREFIX, psz_extension );
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779

    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;
780
    p_sys->record.b_error = false;
781
782
783
784
785
786
787
    return VLC_SUCCESS;
}
static int  ARecordStop( stream_t *s )
{
    stream_sys_t *p_sys = s->p_sys;

    assert( p_sys->record.b_active );
Laurent Aimar's avatar
Laurent Aimar committed
788

789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    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 );

813
814
815
816
817
818
819
820
821
822
823
824
825
    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)" );
    }
826
}
Laurent Aimar's avatar
Laurent Aimar committed
827

828
/****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
829
 * Method 1:
830
 ****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
831
static void AStreamPrebufferBlock( stream_t *s )
832
{
Laurent Aimar's avatar
Laurent Aimar committed
833
    stream_sys_t *p_sys = s->p_sys;
834
    access_t     *p_access = p_sys->p_access;
835

Laurent Aimar's avatar
Laurent Aimar committed
836
837
    int64_t i_first = 0;
    int64_t i_start;
838

Laurent Aimar's avatar
Laurent Aimar committed
839
840
841
    msg_Dbg( s, "pre buffering" );
    i_start = mdate();
    for( ;; )
842
    {
Laurent Aimar's avatar
Laurent Aimar committed
843
        int64_t i_date = mdate();
844
        bool b_eof;
Laurent Aimar's avatar
Laurent Aimar committed
845
        block_t *b;
846

847
        if( s->b_die || p_sys->block.i_size > STREAM_CACHE_PREBUFFER_SIZE ||
Laurent Aimar's avatar
Laurent Aimar committed
848
849
850
851
852
853
854
            ( 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
855
            i_byterate = ( INT64_C(1000000) * p_sys->stat.i_bytes ) /
856
                         (p_sys->stat.i_read_time + 1);
Laurent Aimar's avatar
Laurent Aimar committed
857

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
858
            msg_Dbg( s, "prebuffering done %"PRId64" bytes in %"PRId64"s - "
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
859
                     "%"PRId64" kbytes/s",
Laurent Aimar's avatar
Laurent Aimar committed
860
                     p_sys->stat.i_bytes,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
861
                     p_sys->stat.i_read_time / INT64_C(1000000),
Laurent Aimar's avatar
Laurent Aimar committed
862
863
864
                     i_byterate / 1024 );
            break;
        }
865

Laurent Aimar's avatar
Laurent Aimar committed
866
        /* Fetch a block */
867
        if( ( b = AReadBlock( s, &b_eof ) ) == NULL )
868
        {
869
870
            if( b_eof )
                break;
Laurent Aimar's avatar
Laurent Aimar committed
871
            continue;
872
        }
Laurent Aimar's avatar
Laurent Aimar committed
873

874
875
876
877
878
879
        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;
Laurent Aimar's avatar
Laurent Aimar committed
880

881
882
883
            p_sys->stat.i_read_count++;
            b = b->p_next;
        }
884

885
        if( p_access->info.b_prebuffered )
886
887
888
889
890
891
892
893
894
895
896
897
898
        {
            /* 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");
        }

899
900
    }

Laurent Aimar's avatar
Laurent Aimar committed
901
902
903
904
905
    p_sys->block.p_current = p_sys->block.p_first;
}

static int AStreamRefillBlock( stream_t *s );

906
static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read )
Laurent Aimar's avatar
Laurent Aimar committed
907
908
909
{
    stream_sys_t *p_sys = s->p_sys;

910
911
    uint8_t *p_data = p_read;
    uint8_t *p_record = p_data;
912
    unsigned int i_data = 0;
Laurent Aimar's avatar
Laurent Aimar committed
913
914
915
916
917

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

918
919
920
921
    if( p_sys->record.b_active && !p_data )
        p_record = p_data = malloc( i_read );

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

Laurent Aimar's avatar
Laurent Aimar committed
932
    while( i_data < i_read )
933
    {
934
935
        int i_current =
            p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
936
        unsigned int i_copy = __MIN( (unsigned int)__MAX(i_current,0), i_read - i_data);
gbazin's avatar
   
gbazin committed
937

Laurent Aimar's avatar
Laurent Aimar committed
938
        /* Copy data */
939
940
941
        if( p_data )
        {
            memcpy( p_data,
942
943
                    &p_sys->block.p_current->p_buffer[p_sys->block.i_offset],
                    i_copy );
944
945
            p_data += i_copy;
        }
Laurent Aimar's avatar
Laurent Aimar committed
946
        i_data += i_copy;
947

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

965
966
967
968
969
970
971
    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 );
    }
972

Laurent Aimar's avatar
Laurent Aimar committed
973
974
    p_sys->i_pos += i_data;
    return i_data;
975
}
gbazin's avatar
gbazin committed
976

977
static int AStreamPeekBlock( stream_t *s, const uint8_t **pp_peek, unsigned int i_read )
sigmunau's avatar
sigmunau committed
978
{
Laurent Aimar's avatar
Laurent Aimar committed
979
980
    stream_sys_t *p_sys = s->p_sys;
    uint8_t *p_data;
981
    unsigned int i_data = 0;
Laurent Aimar's avatar
Laurent Aimar committed
982
    block_t *b;
983
    unsigned int i_offset;
Laurent Aimar's avatar
Laurent Aimar committed
984

985
    if( p_sys->block.p_current == NULL ) return 0; /* EOF */
Laurent Aimar's avatar
Laurent Aimar committed
986
987
988
989
990
991
992
993
994
995
996

    /* 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 )
    {
997
998
999
1000
1001
1002
        p_sys->p_peek = realloc( p_sys->p_peek, i_read );
        if( !p_sys->p_peek )
        {
            p_sys->i_peek = 0;
            return 0;
        }
Laurent Aimar's avatar
Laurent Aimar committed
1003
1004
1005
1006
        p_sys->i_peek = i_read;
    }

    /* Fill enough data */
1007
1008
    while( p_sys->block.i_size - (p_sys->i_pos - p_sys->block.i_start)
           < i_read )
Laurent Aimar's avatar
Laurent Aimar committed
1009
1010
1011
    {
        block_t **pp_last = p_sys->block.pp_last;

1012
        if( AStreamRefillBlock( s ) ) break;
Laurent Aimar's avatar
Laurent Aimar committed
1013
1014

        /* Our buffer are probably filled enough, don't try anymore */
1015
        if( pp_last == p_sys->block.pp_last ) break;
Laurent Aimar's avatar
Laurent Aimar committed
1016
1017
1018
1019
1020
1021
1022
1023
1024
    }

    /* 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 )
    {
1025
        unsigned int i_current = __MAX(b->i_buffer - i_offset,0);
Laurent Aimar's avatar
Laurent Aimar committed
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
        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;
sigmunau's avatar
sigmunau committed
1042
}
1043

Laurent Aimar's avatar
Laurent Aimar committed
1044
static int AStreamSeekBlock( stream_t *s, int64_t i_pos )
1045
{
Laurent Aimar's avatar
Laurent Aimar committed
1046
1047
1048
    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;
1049
    bool b_seek;
1050

Laurent Aimar's avatar
Laurent Aimar committed
1051
1052
1053
1054
1055
    /* 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;
1056

Laurent Aimar's avatar
Laurent Aimar committed
1057
1058
1059
1060
1061
        while( i_current + b->i_buffer < i_offset )
        {
            i_current += b->i_buffer;
            b = b->p_next;
        }
1062

Laurent Aimar's avatar
Laurent Aimar committed
1063
1064
        p_sys->block.p_current = b;
        p_sys->block.i_offset = i_offset - i_current;
1065

Laurent Aimar's avatar
Laurent Aimar committed
1066
        p_sys->i_pos = i_pos;
1067

Laurent Aimar's avatar
Laurent Aimar committed
1068
1069
        return VLC_SUCCESS;
    }
1070

Laurent Aimar's avatar
Laurent Aimar committed
1071
1072
    /* We may need to seek or to read data */
    if( i_offset < 0 )
1073
    {
1074
        bool b_aseek;
1075
        access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
Laurent Aimar's avatar
Laurent Aimar committed
1076
1077
1078

        if( !b_aseek )
        {
1079
            msg_Err( s, "backward seekin