]> git.sesse.net Git - vlc/blob - modules/access_filter/timeshift.c
Add option to force timeshift filter even if access can control pace or pause.
[vlc] / modules / access_filter / timeshift.c
1 /*****************************************************************************
2  * timeshift.c: access filter implementing timeshifting capabilities
3  *****************************************************************************
4  * Copyright (C) 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29
30 #include <errno.h>
31
32 #include <vlc/vlc.h>
33 #include <vlc/input.h>
34 #include "charset.h"
35
36 #include <unistd.h>
37
38 /*****************************************************************************
39  * Module descriptor
40  *****************************************************************************/
41 static int  Open ( vlc_object_t * );
42 static void Close( vlc_object_t * );
43
44 #define GRANULARITY_TEXT N_("Timeshift granularity")
45 /// \bug [String] typo
46 #define GRANULARITY_LONGTEXT N_( "This is the size of the temporary files " \
47   "tha will be used to store the timeshifted streams." )
48 #define DIR_TEXT N_("Timeshift directory")
49 #define DIR_LONGTEXT N_( "Directory used to store the timeshift temporary " \
50   "files." )
51 #define FORCE_TEXT N_("Force use of the timeshift module")
52 #define FORCE_LONGTEXT N_("Force use of the timeshift module even if the " \
53   "access declares that it can control pace or pause." )
54
55 vlc_module_begin();
56     set_shortname( _("Timeshift") );
57     set_description( _("Timeshift") );
58     set_category( CAT_INPUT );
59     set_subcategory( SUBCAT_INPUT_ACCESS_FILTER );
60     set_capability( "access_filter", 0 );
61     add_shortcut( "timeshift" );
62     set_callbacks( Open, Close );
63
64     add_integer( "timeshift-granularity", 50, NULL, GRANULARITY_TEXT,
65                  GRANULARITY_LONGTEXT, VLC_TRUE );
66     add_directory( "timeshift-dir", 0, 0, DIR_TEXT, DIR_LONGTEXT, VLC_FALSE );
67     add_bool( "timeshift-force", VLC_FALSE, NULL, FORCE_TEXT, FORCE_LONGTEXT,
68               VLC_FALSE );
69 vlc_module_end();
70
71 /*****************************************************************************
72  * Local prototypes
73  *****************************************************************************/
74
75 static int      Seek( access_t *, int64_t );
76 static block_t *Block  ( access_t *p_access );
77 static int      Control( access_t *, int i_query, va_list args );
78 static void     Thread ( access_t *p_access );
79 static int      WriteBlockToFile( access_t *p_access, block_t *p_block );
80 static block_t *ReadBlockFromFile( access_t *p_access );
81 static char    *GetTmpFilePath( access_t *p_access );
82
83 #define TIMESHIFT_FIFO_MAX (10*1024*1024)
84 #define TIMESHIFT_FIFO_MIN (TIMESHIFT_FIFO_MAX/4)
85 #define TMP_FILE_MAX 256
86
87 typedef struct ts_entry_t
88 {
89     FILE *file;
90     struct ts_entry_t *p_next;
91
92 } ts_entry_t;
93
94 struct access_sys_t
95 {
96     block_fifo_t *p_fifo;
97
98     int  i_files;
99     int  i_file_size;
100     int  i_write_size;
101
102     ts_entry_t *p_read_list;
103     ts_entry_t **pp_read_last;
104     ts_entry_t *p_write_list;
105     ts_entry_t **pp_write_last;
106
107     char *psz_filename_base;
108     char *psz_filename;
109 };
110
111 /*****************************************************************************
112  * Open:
113  *****************************************************************************/
114 static int Open( vlc_object_t *p_this )
115 {
116     access_t *p_access = (access_t*)p_this;
117     access_t *p_src = p_access->p_source;
118     access_sys_t *p_sys;
119     vlc_bool_t b_bool;
120
121     var_Create( p_access, "timeshift-force",
122                 VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
123     if( var_GetBool( p_access, "timeshift-force" ) == VLC_TRUE )
124     {
125         msg_Dbg( p_access, "Forcing use of timeshift even if access can control pace or pause" );
126     }
127     else
128     {
129         /* Only work with not pace controled access */
130         if( access2_Control( p_src, ACCESS_CAN_CONTROL_PACE, &b_bool )
131             || b_bool )
132         {
133             msg_Dbg( p_src, "ACCESS_CAN_CONTROL_PACE: timeshift useless" );
134             return VLC_EGENERIC;
135         }
136         /* Refuse access that can be paused */
137         if( access2_Control( p_src, ACCESS_CAN_PAUSE, &b_bool ) || b_bool )
138         {
139             msg_Dbg( p_src, "ACCESS_CAN_PAUSE: timeshift useless" );
140             return VLC_EGENERIC;
141         }
142     }
143
144     /* */
145     p_access->pf_read = NULL;
146     p_access->pf_block = Block;
147     p_access->pf_seek = Seek;
148     p_access->pf_control = Control;
149     p_access->info = p_src->info;
150
151     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
152
153     /* */
154     p_sys->p_fifo = block_FifoNew( p_access );
155     p_sys->i_write_size = 0;
156     p_sys->i_files = 0;
157
158     p_sys->p_read_list = NULL;
159     p_sys->pp_read_last = &p_sys->p_read_list;
160     p_sys->p_write_list = NULL;
161     p_sys->pp_write_last = &p_sys->p_write_list;
162
163     var_Create( p_access, "timeshift-dir",
164                 VLC_VAR_DIRECTORY | VLC_VAR_DOINHERIT );
165     var_Create( p_access, "timeshift-granularity",
166                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
167     p_sys->i_file_size = var_GetInteger( p_access, "timeshift-granularity" );
168     if( p_sys->i_file_size < 1 ) p_sys->i_file_size = 1;
169     p_sys->i_file_size *= 1024 * 1024; /* In MBytes */
170
171     p_sys->psz_filename_base = GetTmpFilePath( p_access );
172     p_sys->psz_filename = malloc( strlen( p_sys->psz_filename_base ) + 1000 );
173
174     if( vlc_thread_create( p_access, "timeshift thread", Thread,
175                            VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
176     {
177         msg_Err( p_access, "cannot spawn timeshift access thread" );
178         return VLC_EGENERIC;
179     }
180
181     return VLC_SUCCESS;
182 }
183
184 /*****************************************************************************
185  * Close:
186  *****************************************************************************/
187 static void Close( vlc_object_t *p_this )
188 {
189     access_t     *p_access = (access_t*)p_this;
190     access_sys_t *p_sys = p_access->p_sys;
191     ts_entry_t *p_entry;
192     int i;
193
194     msg_Dbg( p_access, "timeshift close called" );
195     vlc_thread_join( p_access );
196
197     for( p_entry = p_sys->p_write_list; p_entry; )
198     {
199         ts_entry_t *p_next = p_entry->p_next;
200         fclose( p_entry->file );
201         free( p_entry );
202         p_entry = p_next;
203     }
204     for( p_entry = p_sys->p_read_list; p_entry; )
205     {
206         ts_entry_t *p_next = p_entry->p_next;
207         fclose( p_entry->file );
208         free( p_entry );
209         p_entry = p_next;
210     }
211     for( i = 0; i < p_sys->i_files; i++ )
212     {
213         sprintf( p_sys->psz_filename, "%s%i.dat",
214                  p_sys->psz_filename_base, i );
215         unlink( p_sys->psz_filename );
216     }
217
218     free( p_sys->psz_filename );
219     free( p_sys->psz_filename_base );
220     block_FifoRelease( p_sys->p_fifo );
221     free( p_sys );
222 }
223
224 /*****************************************************************************
225  *
226  *****************************************************************************/
227 static block_t *Block( access_t *p_access )
228 {
229     access_sys_t *p_sys = p_access->p_sys;
230     block_t *p_block;
231
232     if( p_access->b_die )
233     {
234         p_access->info.b_eof = VLC_TRUE;
235         return NULL;
236     }
237
238     p_block = block_FifoGet( p_sys->p_fifo );
239     //p_access->info.i_size -= p_block->i_buffer;
240     return p_block;
241 }
242
243 /*****************************************************************************
244  *
245  *****************************************************************************/
246 static void Thread( access_t *p_access )
247 {
248     access_sys_t *p_sys = p_access->p_sys;
249     access_t     *p_src = p_access->p_source;
250     int i;
251
252     while( !p_access->b_die )
253     {
254         block_t *p_block;
255
256         /* Get a new block from the source */
257         if( p_src->pf_block )
258         {
259             p_block = p_src->pf_block( p_src );
260
261             if( p_block == NULL )
262             {
263                 if( p_src->info.b_eof ) break;
264                 msleep( 1000 );
265                 continue;
266             }
267         }
268         else
269         {
270             if( ( p_block = block_New( p_access, 2048 ) ) == NULL ) break;
271
272             p_block->i_buffer =
273                 p_src->pf_read( p_src, p_block->p_buffer, 2048 );
274
275             if( p_block->i_buffer < 0 )
276             {
277                 block_Release( p_block );
278                 if( p_block->i_buffer == 0 ) break;
279                 msleep( 1000 );
280                 continue;
281             }
282         }
283
284         /* Write block */
285         if( !p_sys->p_write_list && !p_sys->p_read_list &&
286             p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
287         {
288             /* If there isn't too much timeshifted data,
289              * write directly to FIFO */
290             block_FifoPut( p_sys->p_fifo, p_block );
291
292             //p_access->info.i_size += p_block->i_buffer;
293             //p_access->info.i_update |= INPUT_UPDATE_SIZE;
294
295             /* Nothing else to do */
296             continue;
297         }
298
299         WriteBlockToFile( p_access, p_block );
300         block_Release( p_block );
301
302         /* Read from file to fill up the fifo */
303         while( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN &&
304                !p_access->b_die )
305         {
306             p_block = ReadBlockFromFile( p_access );
307             if( !p_block ) break;
308             block_FifoPut( p_sys->p_fifo, p_block );
309         }
310     }
311
312     msg_Dbg( p_access, "timeshift: EOF" );
313
314     /* Send dummy packet to avoid deadlock in TShiftBlock */
315     for( i = 0; i < 2; i++ )
316     {
317         block_t *p_dummy = block_New( p_access, 128 );
318         p_dummy->i_flags |= BLOCK_FLAG_DISCONTINUITY;
319         memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
320         block_FifoPut( p_sys->p_fifo, p_dummy );
321     }
322 }
323
324 /*****************************************************************************
325  * NextFileWrite:
326  *****************************************************************************/
327 static void NextFileWrite( access_t *p_access )
328 {
329     access_sys_t *p_sys = p_access->p_sys;
330     ts_entry_t   *p_next;
331
332     if( !p_sys->p_write_list )
333     {
334         p_sys->i_write_size = 0;
335         return;
336     }
337
338     p_next = p_sys->p_write_list->p_next;
339
340     /* Put written file in read list */
341     if( p_sys->i_write_size < p_sys->i_file_size )
342         ftruncate( fileno( p_sys->p_write_list->file ), p_sys->i_write_size );
343
344     fseek( p_sys->p_write_list->file, 0, SEEK_SET );
345     *p_sys->pp_read_last = p_sys->p_write_list;
346     p_sys->pp_read_last = &p_sys->p_write_list->p_next;
347     p_sys->p_write_list->p_next = 0;
348
349     /* Switch to next file to write */
350     p_sys->p_write_list = p_next;
351     if( !p_sys->p_write_list ) p_sys->pp_write_last = &p_sys->p_write_list;
352
353     p_sys->i_write_size = 0;
354 }
355
356 /*****************************************************************************
357  * NextFileRead:
358  *****************************************************************************/
359 static void NextFileRead( access_t *p_access )
360 {
361     access_sys_t *p_sys = p_access->p_sys;
362     ts_entry_t   *p_next;
363
364     if( !p_sys->p_read_list ) return;
365
366     p_next = p_sys->p_read_list->p_next;
367
368     /* Put read file in write list */
369     fseek( p_sys->p_read_list->file, 0, SEEK_SET );
370     *p_sys->pp_write_last = p_sys->p_read_list;
371     p_sys->pp_write_last = &p_sys->p_read_list->p_next;
372     p_sys->p_read_list->p_next = 0;
373
374     /* Switch to next file to read */
375     p_sys->p_read_list = p_next;
376     if( !p_sys->p_read_list ) p_sys->pp_read_last = &p_sys->p_read_list;
377 }
378
379 /*****************************************************************************
380  * WriteBlockToFile:
381  *****************************************************************************/
382 static int WriteBlockToFile( access_t *p_access, block_t *p_block )
383 {
384     access_sys_t *p_sys = p_access->p_sys;
385     int i_write, i_buffer;
386
387     if( p_sys->i_write_size == p_sys->i_file_size ) NextFileWrite( p_access );
388
389     /* Open new file if necessary */
390     if( !p_sys->p_write_list )
391     {
392         FILE *file;
393
394         sprintf( p_sys->psz_filename, "%s%i.dat",
395                  p_sys->psz_filename_base, p_sys->i_files );
396         file = utf8_fopen( p_sys->psz_filename, "w+b" );
397
398         if( !file && p_sys->i_files < 2 )
399         {
400             /* We just can't work with less than 2 buffer files */
401             msg_Err( p_access, "cannot open temporary file '%s' (%s)",
402                      p_sys->psz_filename, strerror(errno) );
403             return VLC_EGENERIC;
404         }
405         else if( !file ) return VLC_EGENERIC;
406
407         p_sys->p_write_list = malloc( sizeof(ts_entry_t) );
408         p_sys->p_write_list->p_next = 0;
409         p_sys->p_write_list->file = file;
410         p_sys->pp_write_last = &p_sys->p_write_list->p_next;
411
412         p_sys->i_files++;
413     }
414
415     /* Write to file */
416     i_buffer = __MIN( p_block->i_buffer,
417                       p_sys->i_file_size - p_sys->i_write_size );
418
419     i_write = fwrite( p_block->p_buffer, 1, i_buffer,
420                       p_sys->p_write_list->file );
421
422     if( i_write > 0 ) p_sys->i_write_size += i_write;
423
424     //p_access->info.i_size += i_write;
425     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
426
427     if( i_write < i_buffer )
428     {
429         /* Looks like we're short of space */
430
431         if( !p_sys->p_write_list->p_next )
432         {
433             msg_Warn( p_access, "no more space, overwritting old data" );
434             NextFileRead( p_access );
435             NextFileRead( p_access );
436         }
437
438         /* Make sure we switch to next file in write list */
439         p_sys->i_write_size = p_sys->i_file_size;
440     }
441
442     p_block->p_buffer += i_write;
443     p_block->i_buffer -= i_write;
444
445     /* Check if we have some data left */
446     if( p_block->i_buffer ) return WriteBlockToFile( p_access, p_block );
447
448     return VLC_SUCCESS;
449 }
450
451 /*****************************************************************************
452  * ReadBlockFromFile:
453  *****************************************************************************/
454 static block_t *ReadBlockFromFile( access_t *p_access )
455 {
456     access_sys_t *p_sys = p_access->p_sys;
457     block_t *p_block;
458
459     if( !p_sys->p_read_list && p_sys->p_write_list )
460     {
461         /* Force switching to next write file, that should
462          * give us something to read */
463         NextFileWrite( p_access );
464     }
465
466     if( !p_sys->p_read_list ) return 0;
467
468     p_block = block_New( p_access, 4096 );
469     p_block->i_buffer = fread( p_block->p_buffer, 1, 4096,
470                                p_sys->p_read_list->file );
471
472     if( p_block->i_buffer == 0 ) NextFileRead( p_access );
473
474     //p_access->info.i_size -= p_block->i_buffer;
475     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
476
477     return p_block;
478 }
479
480 /*****************************************************************************
481  * Seek: seek to a specific location in a file
482  *****************************************************************************/
483 static int Seek( access_t *p_access, int64_t i_pos )
484 {
485     //access_sys_t *p_sys = p_access->p_sys;
486     return VLC_SUCCESS;
487 }
488
489 /*****************************************************************************
490  *
491  *****************************************************************************/
492 static int Control( access_t *p_access, int i_query, va_list args )
493 {
494     access_t     *p_src = p_access->p_source;
495
496     vlc_bool_t   *pb_bool;
497     int          *pi_int;
498     int64_t      *pi_64;
499
500     switch( i_query )
501     {
502         case ACCESS_CAN_SEEK:
503         case ACCESS_CAN_FASTSEEK:
504             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
505             *pb_bool = VLC_TRUE;
506             break;
507
508         case ACCESS_CAN_CONTROL_PACE:   /* Not really true */
509         case ACCESS_CAN_PAUSE:
510             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
511             *pb_bool = VLC_TRUE;
512             break;
513
514         case ACCESS_GET_MTU:
515             pi_int = (int*)va_arg( args, int * );
516             *pi_int = 0;
517             break;
518
519         case ACCESS_GET_PTS_DELAY:
520             pi_64 = (int64_t*)va_arg( args, int64_t * );
521             return access2_Control( p_src, ACCESS_GET_PTS_DELAY, pi_64 );
522
523         case ACCESS_SET_PAUSE_STATE:
524             return VLC_SUCCESS;
525
526         case ACCESS_GET_TITLE_INFO:
527         case ACCESS_SET_TITLE:
528         case ACCESS_SET_SEEKPOINT:
529         case ACCESS_GET_META:
530             return VLC_EGENERIC;
531
532         case ACCESS_SET_PRIVATE_ID_STATE:
533         case ACCESS_GET_PRIVATE_ID_STATE:
534         case ACCESS_SET_PRIVATE_ID_CA:
535             return access2_vaControl( p_src, i_query, args );
536
537         default:
538             msg_Warn( p_access, "unimplemented query in control" );
539             return VLC_EGENERIC;
540
541     }
542     return VLC_SUCCESS;
543 }
544
545 /*****************************************************************************
546  * GetTmpFilePath:
547  *****************************************************************************/
548 #ifdef WIN32
549 #define getpid() GetCurrentProcessId()
550 #endif
551 static char *GetTmpFilePath( access_t *p_access )
552 {
553     char *psz_dir = var_GetString( p_access, "timeshift-dir" );
554     char *psz_filename_base;
555
556     if( ( psz_dir != NULL ) && ( psz_dir[0] == '\0' ) )
557     {
558         free( psz_dir );
559         psz_dir = NULL;
560     }
561
562     if( psz_dir == NULL )
563     {
564 #ifdef WIN32
565         char psz_local_dir[MAX_PATH];
566         int i_size;
567
568         i_size = GetTempPath( MAX_PATH, psz_local_dir );
569         if( i_size <= 0 || i_size > MAX_PATH )
570         {
571             if( !getcwd( psz_local_dir, MAX_PATH ) )
572                 strcpy( psz_local_dir, "C:" );
573         }
574
575         psz_dir = FromLocaleDup( psz_local_dir );
576
577         /* remove last \\ if any */
578         if( psz_dir[strlen(psz_dir)-1] == '\\' )
579             psz_dir[strlen(psz_dir)-1] = '\0';
580 #else
581         psz_dir = strdup( "/tmp" );
582 #endif
583     }
584
585     asprintf( &psz_filename_base, "%s/vlc-timeshift-%d-%d-",
586               psz_dir, getpid(), p_access->i_object_id );
587     free( psz_dir );
588
589     return psz_filename_base;
590 }