root/src/input/stream.c

Revision 9f9f4cf8852fef5634f5c0f021b252611da277ab, 65.6 kB (checked in by Rémi Denis-Courmont <rdenis@simphalempin.com>, 1 week ago)

Simplifications

  • Property mode set to 100644
<
Line 
1 /*****************************************************************************
2  * stream.c
3  *****************************************************************************
4  * Copyright (C) 1999-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <dirent.h>
29
30 #include <vlc_common.h>
31 #include <vlc_strings.h>
32 #include <vlc_osd.h>
33 #include <vlc_charset.h>
34
35 #include <assert.h>
36
37 #include "input_internal.h"
38
39 #undef STREAM_DEBUG
40
41 /* TODO:
42  *  - tune the 2 methods (block/stream)
43  *  - compute cost for seek
44  *  - improve stream mode seeking with closest segments
45  *  - ...
46  *  - Maybe remove (block/stream) in favour of immediate
47  */
48
49 /* Two methods:
50  *  - using pf_block
51  *      One linked list of data read
52  *  - using pf_read
53  *      More complex scheme using mutliple track to avoid seeking
54  *  - using directly the access (only indirection for peeking).
55  *      This method is known to introduce much less latency.
56  *      It should probably defaulted (instead of the stream method (2)).
57  */
58
59 /* How many tracks we have, currently only used for stream mode */
60 #ifdef OPTIMIZE_MEMORY
61 #   define STREAM_CACHE_TRACK 1
62     /* Max size of our cache 128Ko per track */
63 #   define STREAM_CACHE_SIZE  (STREAM_CACHE_TRACK*1024*128)
64 #else
65 #   define STREAM_CACHE_TRACK 3
66     /* Max size of our cache 4Mo per track */
67 #   define STREAM_CACHE_SIZE  (4*STREAM_CACHE_TRACK*1024*1024)
68 #endif
69
70 /* How many data we try to prebuffer */
71 #define STREAM_CACHE_PREBUFFER_SIZE (32767)
72 /* Maximum time we take to pre-buffer */
73 #define STREAM_CACHE_PREBUFFER_LENGTH (100*1000)
74
75 /* Method1: Simple, for pf_block.
76  *  We get blocks and put them in the linked list.
77  *  We release blocks once the total size is bigger than CACHE_BLOCK_SIZE
78  */
79 #define STREAM_DATA_WAIT 40000       /* Time between before a pf_block retry */
80
81 /* Method2: A bit more complex, for pf_read
82  *  - We use ring buffers, only one if unseekable, all if seekable
83  *  - Upon seek date current ring, then search if one ring match the pos,
84  *      yes: switch to it, seek the access to match the end of the ring
85  *      no: search the ring with i_end the closer to i_pos,
86  *          if close enough, read data and use this ring
87  *          else use the oldest ring, seek and use it.
88  *
89  *  TODO: - with access non seekable: use all space available for only one ring, but
90  *          we have to support seekable/non-seekable switch on the fly.
91  *        - compute a good value for i_read_size
92  *        - ?
93  */
94 #define STREAM_READ_ATONCE 32767
95 #define STREAM_CACHE_TRACK_SIZE (STREAM_CACHE_SIZE/STREAM_CACHE_TRACK)
96
97 typedef struct
98 {
99     int64_t i_date;
100
101     int64_t i_start;
102     int64_t i_end;
103
104     uint8_t *p_buffer;
105
106 } stream_track_t;
107
108 typedef struct
109 {
110     char     *psz_path;
111     int64_t  i_size;
112
113 } access_entry_t;
114
115 typedef enum stream_read_method_t
116 {
117     STREAM_METHOD_IMMEDIATE,
118     STREAM_METHOD_BLOCK,
119     STREAM_METHOD_STREAM
120 } stream_read_method_t;
121
122 struct stream_sys_t
123 {
124     access_t    *p_access;
125
126     stream_read_method_t   method;    /* method to use */
127
128     int64_t     i_pos;      /* Current reading offset */
129
130     /* Method 1: pf_block */
131     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         block_t *p_current;     /* Current block */
136
137         int     i_size;         /* Total amount of data in the list */
138         block_t *p_first;
139         block_t **pp_last;
140
141     } block;
142
143     /* Method 2: for pf_read */
144     struct
145     {
146         int i_offset;   /* Buffer offset in the current track */
147         int i_tk;       /* Current track */
148         stream_track_t tk[STREAM_CACHE_TRACK];
149
150         /* Global buffer */
151         uint8_t *p_buffer;
152
153         /* */
154         int i_used; /* Used since last read */
155         int i_read_size;
156
157     } stream;
158
159     /* Method 3: for pf_read */
160     struct
161     {
162         int64_t i_end;
163         uint8_t *p_buffer;
164     } immediate;
165
166     /* Peek temporary buffer */
167     unsigned int i_peek;
168     uint8_t *p_peek;
169
170     /* Stat for both method */
171     struct
172     {
173         bool b_fastseek;  /* From access */
174
175         /* Stat about reading data */
176         int64_t i_read_count;
177         int64_t i_bytes;
178         int64_t i_read_time;
179
180         /* Stat about seek */
181         int     i_seek_count;
182         int64_t i_seek_time;
183
184     } stat;
185
186     /* Streams list */
187     int            i_list;
188     access_entry_t **list;
189     int            i_list_index;
190     access_t       *p_list_access;
191
192     /* Preparse mode ? */
193     bool      b_quick;
194
195     /* */
196     struct
197     {
198         bool b_active;
199
200         FILE *f;        /* TODO it could be replaced by access_output_t one day */
201         bool b_error;
202     } record;
203 };
204
205 /* Method 1: */
206 static int  AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read );
207 static int  AStreamPeekBlock( stream_t *s, const uint8_t **p_peek, unsigned int i_read );
208 static int  AStreamSeekBlock( stream_t *s, int64_t i_pos );
209 static void AStreamPrebufferBlock( stream_t *s );
210 static block_t *AReadBlock( stream_t *s, bool *pb_eof );
211
212 /* Method 2 */
213 static int  AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read );
214 static int  AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int i_read );
215 static int  AStreamSeekStream( stream_t *s, int64_t i_pos );
216 static void AStreamPrebufferStream( stream_t *s );
217 static int  AReadStream( stream_t *s, void *p_read, unsigned int i_read );
218
219 /* Method 3 */
220 static int  AStreamReadImmediate( stream_t *s, void *p_read, unsigned int i_read );
221 static int  AStreamPeekImmediate( stream_t *s, const uint8_t **pp_peek, unsigned int i_read );
222 static int  AStreamSeekImmediate( stream_t *s, int64_t i_pos );
223
224 /* Common */
225 static int AStreamControl( stream_t *s, int i_query, va_list );
226 static void AStreamDestroy( stream_t *s );
227 static void UStreamDestroy( stream_t *s );
228 static int  ASeek( stream_t *s, int64_t i_pos );
229 static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension );
230 static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer );
231
232 /****************************************************************************
233  * Method 3 helpers:
234  ****************************************************************************/
235
236 static inline int64_t stream_buffered_size( stream_t *s )
237 {
238     return s->p_sys->immediate.i_end;
239 }
240
241 static inline void stream_buffer_empty( stream_t *s, int length )
242 {
243     length = __MAX( stream_buffered_size( s ), length );
244     if( length )
245     {
246         memmove( s->p_sys->immediate.p_buffer,
247                  s->p_sys->immediate.p_buffer + length,
248                  stream_buffered_size( s ) - length );
249     }
250     s->p_sys->immediate.i_end -= length;
251 }
252
253 static inline void stream_buffer_fill( stream_t *s, int length )
254 {
255     s->p_sys->immediate.i_end += length;
256 }
257
258 static inline uint8_t * stream_buffer( stream_t *s )
259 {
260     return s->p_sys->immediate.p_buffer;
261 }
262
263 /****************************************************************************
264  * stream_UrlNew: create a stream from a access
265  ****************************************************************************/
266 stream_t *__stream_UrlNew( vlc_object_t *p_parent, const char *psz_url )
267 {
268     const char *psz_access, *psz_demux;
269     char *psz_path;
270     access_t *p_access;
271     stream_t *p_res;
272
273     if( !psz_url )
274         return NULL;
275
276     char psz_dup[strlen( psz_url ) + 1];
277     strcpy( psz_dup, psz_url );
278     input_SplitMRL( &psz_access, &psz_demux, &psz_path, psz_dup );
279
280     /* Now try a real access */
281     p_access = access_New( p_parent, psz_access, psz_demux, psz_path );
282
283     if( p_access == NULL )
284     {
285         msg_Err( p_parent, "no suitable access module for `%s'", psz_url );
286         return NULL;
287     }
288
289     if( !( p_res = stream_AccessNew( p_access, true ) ) )
290     {
291         access_Delete( p_access );
292         return NULL;
293     }
294
295     p_res->pf_destroy = UStreamDestroy;
296     return p_res;
297 }
298
299 stream_t *stream_AccessNew( access_t *p_access, bool b_quick )
300 {
301     stream_t *s = vlc_stream_create( VLC_OBJECT(p_access) );
302     stream_sys_t *p_sys;
303     char *psz_list = NULL;
304
305     if( !s ) return NULL;
306
307     /* Attach it now, needed for b_die */
308     vlc_object_attach( s, p_access );
309
310     s->pf_read   = NULL;    /* Set up later */
311     s->pf_peek   = NULL;
312     s->pf_control = AStreamControl;
313     s->pf_destroy = AStreamDestroy;
314
315     s->p_sys = p_sys = malloc( sizeof( stream_sys_t ) );
316     if( p_sys == NULL )
317         goto error;
318
319     /* UTF16 and UTF32 text file conversion */
320     s->i_char_width = 1;
321     s->b_little_endian = false;
322     s->conv = (vlc_iconv_t)(-1);
323
324     /* Common field */
325     p_sys->p_access = p_access;
326     if( p_access->pf_block )
327         p_sys->method = STREAM_METHOD_BLOCK;
328     else if( var_CreateGetBool( s, "use-stream-immediate" ) )
329         p_sys->method = STREAM_METHOD_IMMEDIATE;
330     else
331         p_sys->method = STREAM_METHOD_STREAM;
332
333     p_sys->record.b_active = false;
334
335     p_sys->i_pos = p_access->info.i_pos;
336
337     /* Stats */
338     access_Control( p_access, ACCESS_CAN_FASTSEEK, &p_sys->stat.b_fastseek );
339     p_sys->stat.i_bytes = 0;
340     p_sys->stat.i_read_time = 0;
341     p_sys->stat.i_read_count = 0;
342     p_sys->stat.i_seek_count = 0;
343     p_sys->stat.i_seek_time = 0;
344
345     p_sys->i_list = 0;
346     p_sys->list = 0;
347     p_sys->i_list_index = 0;
348     p_sys->p_list_access = 0;
349
350     p_sys->b_quick = b_quick;
351
352     /* Get the additional list of inputs if any (for concatenation) */
353     if( (psz_list = var_CreateGetString( s, "input-list" )) && *psz_list )
354     {
355         access_entry_t *p_entry = malloc( sizeof(access_entry_t) );
356         if( p_entry == NULL )
357             goto error;
358         char *psz_name, *psz_parser = psz_name = psz_list;
359
360         p_sys->p_list_access = p_access;
361         p_entry->i_size = p_access->info.i_size;
362         p_entry->psz_path = strdup( p_access->psz_path );
363         if( p_entry->psz_path == NULL )
364         {
365             free( p_entry );
366             goto error;
367         }
368         TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );
369         msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
370                  p_entry->psz_path, p_access->info.i_size );
371
372         while( psz_name && *psz_name )
373         {
374             psz_parser = strchr( psz_name, ',' );
375             if( psz_parser ) *psz_parser = 0;
376
377             psz_name = strdup( psz_name );
378             if( psz_name )
379             {
380                 access_t *p_tmp = access_New( p_access, p_access->psz_access,
381                                                "", psz_name );
382
383                 if( !p_tmp )
384                 {
385                     psz_name = psz_parser;
386                     if( psz_name ) psz_name++;
387                     continue;
388                 }
389
390                 msg_Dbg( p_access, "adding file `%s', (%"PRId64" bytes)",
391                          psz_name, p_tmp->info.i_size );
392
393                 p_entry = malloc( sizeof(access_entry_t) );
394                 if( p_entry == NULL )
395                     goto error;
396                 p_entry->i_size = p_tmp->info.i_size;
397                 p_entry->psz_path = psz_name;
398                 TAB_APPEND( p_sys->i_list, p_sys->list, p_entry );
399
400                 access_Delete( p_tmp );
401             }
402
403             psz_name = psz_parser;
404             if( psz_name ) psz_name++;
405         }
406     }
407     FREENULL( psz_list );
408
409     /* Peek */
410     p_sys->i_peek = 0;
411     p_sys->p_peek = NULL;
412
413     if( p_sys->method == STREAM_METHOD_BLOCK )
414     {
415         msg_Dbg( s, "Using AStream*Block" );
416         s->pf_read = AStreamReadBlock;
417         s->pf_peek = AStreamPeekBlock;
418
419         /* Init all fields of p_sys->block */
420         p_sys->block.i_start = p_sys->i_pos;
421         p_sys->block.i_offset = 0;
422         p_sys->block.p_current = NULL;
423         p_sys->block.i_size = 0;
424         p_sys->block.p_first = NULL;
425         p_sys->block.pp_last = &p_sys->block.p_first;
426
427         /* Do the prebuffering */
428         AStreamPrebufferBlock( s );
429
430         if( p_sys->block.i_size <= 0 )
431         {
432             msg_Err( s, "cannot pre fill buffer" );
433             goto error;
434         }
435     }
436     else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
437     {
438         msg_Dbg( s, "Using AStream*Immediate" );
439
440         s->pf_read = AStreamReadImmediate;
441         s->pf_peek = AStreamPeekImmediate;
442
443         /* Allocate/Setup our tracks (useful to peek)*/
444         p_sys->immediate.i_end = 0;
445         p_sys->immediate.p_buffer = malloc( STREAM_CACHE_SIZE );
446
447         msg_Dbg( s, "p_buffer %p-%p",
448                  p_sys->immediate.p_buffer,
449                  &p_sys->immediate.p_buffer[STREAM_CACHE_SIZE] );
450
451         if( p_sys->immediate.p_buffer == NULL )
452         {
453             msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
454                         STREAM_CACHE_SIZE );
455             goto error;
456         }
457     }
458     else
459     {
460         int i;
461
462         assert( p_sys->method == STREAM_METHOD_STREAM );
463
464         msg_Dbg( s, "Using AStream*Stream" );
465
466         s->pf_read = AStreamReadStream;
467         s->pf_peek = AStreamPeekStream;
468
469         /* Allocate/Setup our tracks */
470         p_sys->stream.i_offset = 0;
471         p_sys->stream.i_tk     = 0;
472         p_sys->stream.p_buffer = malloc( STREAM_CACHE_SIZE );
473         if( p_sys->stream.p_buffer == NULL )
474         {
475             msg_Err( s, "Out of memory when allocating stream cache (%d bytes)",
476                         STREAM_CACHE_SIZE );
477             goto error;
478         }
479         p_sys->stream.i_used   = 0;
480         access_Control( p_access, ACCESS_GET_MTU,
481                          &p_sys->stream.i_read_size );
482         if( p_sys->stream.i_read_size <= 0 )
483             p_sys->stream.i_read_size = STREAM_READ_ATONCE;
484         else if( p_sys->stream.i_read_size <= 256 )
485             p_sys->stream.i_read_size = 256;
486
487         for( i = 0; i < STREAM_CACHE_TRACK; i++ )
488         {
489             p_sys->stream.tk[i].i_date  = 0;
490             p_sys->stream.tk[i].i_start = p_sys->i_pos;
491             p_sys->stream.tk[i].i_end   = p_sys->i_pos;
492             p_sys->stream.tk[i].p_buffer=
493                 &p_sys->stream.p_buffer[i * STREAM_CACHE_TRACK_SIZE];
494         }
495
496         /* Do the prebuffering */
497         AStreamPrebufferStream( s );
498
499         if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
500         {
501             msg_Err( s, "cannot pre fill buffer" );
502             goto error;
503         }
504     }
505
506     return s;
507
508 error:
509     if( p_sys->method == STREAM_METHOD_BLOCK )
510     {
511         /* Nothing yet */
512     }
513     else
514     {
515         free( p_sys->stream.p_buffer );
516     }
517     while( p_sys->i_list > 0 )
518         free( p_sys->list[--(p_sys->i_list)] );
519     free( p_sys->list );
520     free( psz_list );
521     free( s->p_sys );
522     vlc_object_detach( s );
523     vlc_object_release( s );
524     return NULL;
525 }
526
527 /****************************************************************************
528  * AStreamDestroy:
529  ****************************************************************************/
530 static void AStreamDestroy( stream_t *s )
531 {
532     stream_sys_t *p_sys = s->p_sys;
533
534     vlc_object_detach( s );
535
536     if( p_sys->record.b_active )
537         ARecordSetState( s, false, NULL );
538
539     if( p_sys->method == STREAM_METHOD_BLOCK )
540         block_ChainRelease( p_sys->block.p_first );
541     else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
542         free( p_sys->immediate.p_buffer );
543     else
544         free( p_sys->stream.p_buffer );
545
546     free( p_sys->p_peek );
547
548     if( p_sys->p_list_access && p_sys->p_list_access != p_sys->p_access )
549         access_Delete( p_sys->p_list_access );
550
551     while( p_sys->i_list-- )
552     {
553         free( p_sys->list[p_sys->i_list]->psz_path );
554         free( p_sys->list[p_sys->i_list] );
555     }
556
557     free( p_sys->list );
558     free( p_sys );
559
560     vlc_object_release( s );
561 }
562
563 static void UStreamDestroy( stream_t *s )
564 {
565     access_t *p_access = (access_t *)s->p_parent;
566     AStreamDestroy( s );
567     access_Delete( p_access );
568 }
569
570 /****************************************************************************
571  * stream_AccessReset:
572  ****************************************************************************/
573 void stream_AccessReset( stream_t *s )
574 {
575     stream_sys_t *p_sys = s->p_sys;
576
577     p_sys->i_pos = p_sys->p_access->info.i_pos;
578
579     if( p_sys->method == STREAM_METHOD_BLOCK )
580     {
581         block_ChainRelease( p_sys->block.p_first );
582
583         /* Init all fields of p_sys->block */
584         p_sys->block.i_start = p_sys->i_pos;
585         p_sys->block.i_offset = 0;
586         p_sys->block.p_current = NULL;
587         p_sys->block.i_size = 0;
588         p_sys->block.p_first = NULL;
589         p_sys->block.pp_last = &p_sys->block.p_first;
590
591         /* Do the prebuffering */
592         AStreamPrebufferBlock( s );
593     }
594     else if( p_sys->method == STREAM_METHOD_IMMEDIATE )
595     {
596         stream_buffer_empty( s, stream_buffered_size( s ) );
597     }
598     else
599     {
600         int i;
601
602         assert( p_sys->method == STREAM_METHOD_STREAM );
603
604         /* Setup our tracks */
605         p_sys->stream.i_offset = 0;
606         p_sys->stream.i_tk     = 0;
607         p_sys->stream.i_used   = 0;
608
609         for( i = 0; i < STREAM_CACHE_TRACK; i++ )
610         {
611             p_sys->stream.tk[i].i_date  = 0;
612             p_sys->stream.tk[i].i_start = p_sys->i_pos;
613             p_sys->stream.tk[i].i_end   = p_sys->i_pos;
614         }
615
616         /* Do the prebuffering */
617         AStreamPrebufferStream( s );
618     }
619 }
620
621 /****************************************************************************
622  * stream_AccessUpdate:
623  ****************************************************************************/
624 void stream_AccessUpdate( stream_t *s )
625 {
626     stream_sys_t *p_sys = s->p_sys;
627
628     p_sys->i_pos = p_sys->p_access->info.i_pos;
629
630     if( p_sys->i_list )
631     {
632         int i;
633         for( i = 0; i < p_sys->i_list_index; i++ )
634         {
635             p_sys->i_pos += p_sys->list[i]->i_size;
636         }
637     }
638 }
639
640 /****************************************************************************
641  * AStreamControl:
642  ****************************************************************************/
643 static int AStreamControl( stream_t *s, int i_query, va_list args )
644 {
645     stream_sys_t *p_sys = s->p_sys;
646     access_t     *p_access = p_sys->p_access;
647
648     bool *p_bool;
649     bool b_bool;
650     const char *psz_string;
651     int64_t    *pi_64, i_64;
652     int        i_int;
653
654     switch( i_query )
655     {
656         case STREAM_GET_SIZE:
657             pi_64 = (int64_t*)va_arg( args, int64_t * );
658             if( s->p_sys->i_list )
659             {
660                 int i;
661                 *pi_64 = 0;
662                 for( i = 0; i < s->p_sys->i_list; i++ )
663                     *pi_64 += s->p_sys->list[i]->i_size;
664                 break;
665             }
666             *pi_64 = p_access->info.i_size;
667             break;
668
669         case STREAM_CAN_SEEK:
670             p_bool = (bool*)va_arg( args, bool * );
671             access_Control( p_access, ACCESS_CAN_SEEK, p_bool );
672             break;
673
674         case STREAM_CAN_FASTSEEK:
675             p_bool = (bool*)va_arg( args, bool * );
676             access_Control( p_access, ACCESS_CAN_FASTSEEK, p_bool );
677             break;
678
679         case STREAM_GET_POSITION:
680             pi_64 = (int64_t*)va_arg( args, int64_t * );
681             *pi_64 = p_sys->i_pos;
682             break;
683
684         case STREAM_SET_POSITION:
685             i_64 = (int64_t)va_arg( args, int64_t );
686             switch( p_sys->method )
687             {
688             case STREAM_METHOD_BLOCK:
689                 return AStreamSeekBlock( s, i_64 );
690             case STREAM_METHOD_IMMEDIATE:
691                 return AStreamSeekImmediate( s, i_64 );
692             case STREAM_METHOD_STREAM:
693                 return AStreamSeekStream( s, i_64 );
694             default:
695                 assert(0);
696                 return VLC_EGENERIC;
697             }
698
699         case STREAM_GET_MTU:
700             return VLC_EGENERIC;
701
702         case STREAM_CONTROL_ACCESS:
703             i_int = (int) va_arg( args, int );
704             if( i_int != ACCESS_SET_PRIVATE_ID_STATE &&
705                 i_int != ACCESS_SET_PRIVATE_ID_CA &&
706                 i_int != ACCESS_GET_PRIVATE_ID_STATE )
707             {
708                 msg_Err( s, "Hey, what are you thinking ?"
709                             "DON'T USE STREAM_CONTROL_ACCESS !!!" );
710                 return VLC_EGENERIC;
711             }
712             return access_vaControl( p_access, i_int, args );
713
714         case STREAM_GET_CONTENT_TYPE:
715             return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
716                                     va_arg( args, char ** ) );
717         case STREAM_SET_RECORD_STATE:
718             b_bool = (bool)va_arg( args, int );
719             psz_string = NULL;
720             if( b_bool )
721                 psz_string = (const char*)va_arg( args, const char* );
722             return ARecordSetState( s, b_bool, psz_string );
723
724         default:
725             msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
726             return VLC_EGENERIC;
727     }
728     return VLC_SUCCESS;
729 }
730
731 /****************************************************************************
732  * ARecord*: record stream functions
733  ****************************************************************************/
734 static int  ARecordStart( stream_t *s, const char *psz_extension )
735 {
736     stream_sys_t *p_sys = s->p_sys;
737
738     char *psz_file;
739     FILE *f;
740
741     /* */
742     if( !psz_extension )
743         psz_extension = "dat";
744
745     /* Retreive path */
746     char *psz_path = var_CreateGetString( s, "input-record-path" );
747     if( !psz_path || *psz_path == '\0' )
748     {
749         free( psz_path );
750         psz_path = strdup( config_GetHomeDir() );
751     }
752
753     if( !psz_path )
754         return VLC_ENOMEM;
755
756     /* Create file name
757      * TODO allow prefix configuration */
758     psz_file = input_CreateFilename( VLC_OBJECT(s), psz_path, INPUT_RECORD_PREFIX, psz_extension );
759
760     free( psz_path );
761
762     if( !psz_file )
763         return VLC_ENOMEM;
764
765     f = utf8_fopen( psz_file, "wb" );
766     if( !f )
767     {
768         free( psz_file );
769         return VLC_EGENERIC;
770     }
771     msg_Dbg( s, "Recording into %s", psz_file );
772     free( psz_file );
773
774     /* */
775     p_sys->record.f = f;
776     p_sys->record.b_active = true;
777     p_sys->record.b_error = false;
778     return VLC_SUCCESS;
779 }
780 static int  ARecordStop( stream_t *s )
781 {
782     stream_sys_t *p_sys = s->p_sys;
783
784     assert( p_sys->record.b_active );
785
786     msg_Dbg( s, "Recording completed" );
787     fclose( p_sys->record.f );
788     p_sys->record.b_active = false;
789     return VLC_SUCCESS;
790 }
791
792 static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension )
793 {
794     stream_sys_t *p_sys = s->p_sys;
795
796     if( !!p_sys->record.b_active == !!b_record )
797         return VLC_SUCCESS;
798
799     if( b_record )
800         return ARecordStart( s, psz_extension );
801     else
802         return ARecordStop( s );
803 }
804 static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer )
805 {
806     stream_sys_t *p_sys = s->p_sys;
807
808     assert( p_sys->record.b_active );
809
810     if( i_buffer > 0 )
811     {
812         const bool b_previous_error = p_sys->record.b_error;
813         const size_t i_written = fwrite( p_buffer, 1, i_buffer, p_sys->record.f );
814
815         p_sys->record.b_error = i_written != i_buffer;
816
817         /* TODO maybe a intf_UserError or something like that ? */
818         if( p_sys->record.b_error && !b_previous_error )
819             msg_Err( s, "Failed to record data (begin)" );
820         else if( !p_sys->record.b_error && b_previous_error )
821             msg_Err( s, "Failed to record data (end)" );
822     }
823 }
824
825 /****************************************************************************
826  * Method 1:
827  ****************************************************************************/
828 static void AStreamPrebufferBlock( stream_t *s )
829 {
830     stream_sys_t *p_sys = s->p_sys;
831     access_t     *p_access = p_sys->p_access;
832
833     int64_t i_first = 0;
834     int64_t i_start;
835
836     msg_Dbg( s, "pre buffering" );
837     i_start = mdate();
838     for( ;; )
839     {
840         int64_t i_date = mdate();
841         bool b_eof;
842         block_t *b;
843
844         if( s->b_die || p_sys->block.i_size > STREAM_CACHE_PREBUFFER_SIZE ||
845             ( i_first > 0 && i_first + STREAM_CACHE_PREBUFFER_LENGTH < i_date ) )
846         {
847             int64_t i_byterate;
848
849             /* Update stat */
850             p_sys->stat.i_bytes = p_sys->block.i_size;
851             p_sys->stat.i_read_time = i_date - i_start;
852             i_byterate = ( INT64_C(1000000) * p_sys->stat.i_bytes ) /
853                          (p_sys->stat.i_read_time + 1);
854
855             msg_Dbg( s, "prebuffering done %"PRId64" bytes in %"PRId64"s - "
856                      "%"PRId64" kbytes/s",
857                      p_sys->stat.i_bytes,
858                      p_sys->stat.i_read_time / INT64_C(1000000),
859                      i_byterate / 1024 );
860             break;
861         }
862
863         /* Fetch a block */
864         if( ( b = AReadBlock( s, &b_eof ) ) == NULL )
865         {
866             if( b_eof ) break;
867
868             msleep( STREAM_DATA_WAIT );
869             continue;
870         }
871
872         while( b )
873         {
874             /* Append the block */
875             p_sys->block.i_size += b->i_buffer;
876             *p_sys->block.pp_last = b;
877             p_sys->block.pp_last = &b->p_next;
878
879             p_sys->stat.i_read_count++;
880             b = b->p_next;
881         }
882
883         if( p_access->info.b_prebuffered )
884         {
885             /* Access has already prebufferred - update stats and exit */
886             p_sys->stat.i_bytes = p_sys->block.i_size;
887             p_sys->stat.i_read_time = mdate() - i_start;
888             break;
889         }
890
891         if( i_first == 0 )
892         {
893             i_first = mdate();
894             msg_Dbg( s, "received first data for our buffer");
895         }
896
897     }
898
899     p_sys->block.p_current = p_sys->block.p_first;
900 }
901
902 static int AStreamRefillBlock( stream_t *s );
903
904 static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read )
905 {
906     stream_sys_t *p_sys = s->p_sys;
907
908     uint8_t *p_data = p_read;
909     uint8_t *p_record = p_data;
910     unsigned int i_data = 0;
911
912     /* It means EOF */
913     if( p_sys->block.p_current == NULL )
914         return 0;
915
916     if( p_sys->record.b_active && !p_data )
917         p_record = p_data = malloc( i_read );
918
919     if( p_data == NULL )
920     {
921         /* seek within this stream if possible, else use plain old read and discard */
922         stream_sys_t *p_sys = s->p_sys;
923         access_t     *p_access = p_sys->p_access;
924         bool   b_aseek;
925         access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
926         if( b_aseek )
927             return AStreamSeekBlock( s, p_sys->i_pos + i_read ) ? 0 : i_read;
928     }
929
930     while( i_data < i_read )
931     {
932         int i_current =
933             p_sys->block.p_current->i_buffer - p_sys->block.i_offset;
934         unsigned int i_copy = __MIN( (unsigned int)__MAX(i_current,0), i_read - i_data);
935
936         /* Copy data */
937         if( p_data )
938         {
939             memcpy( p_data,
940                     &p_sys->block.p_current->p_buffer[p_sys->block.i_offset],
941                     i_copy );
942             p_data += i_copy;
943         }
944         i_data += i_copy;
945
946         p_sys->block.i_offset += i_copy;
947         if( p_sys->block.i_offset >= p_sys->block.p_current->i_buffer )
948         {
949             /* Current block is now empty, switch to next */
950             if( p_sys->block.p_current )
951             {
952                 p_sys->block.i_offset = 0;
953                 p_sys->block.p_current = p_sys->block.p_current->p_next;
954             }
955             /*Get a new block if needed */
956             if( !p_sys->block.p_current && AStreamRefillBlock( s ) )
957             {
958                 break;
959             }
960         }
961     }
962