f01c5ca9ff36f9735706cbe9554639d5ebb88168
[vlc.git] / modules / access_filter / timeshift.c
1 /*****************************************************************************
2  * timeshift.c
3  *****************************************************************************
4  * Copyright (C) 2005 the VideoLAN team
5  * $Id: demux.c 7546 2004-04-29 13:53:29Z gbazin $
6  *
7  * Author: 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28
29 #include <errno.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/input.h>
33 #include <unistd.h>
34
35 /*****************************************************************************
36  * Module descriptor
37  *****************************************************************************/
38 static int  Open ( vlc_object_t * );
39 static void Close( vlc_object_t * );
40
41 vlc_module_begin();
42     set_shortname( _("Timeshift") );
43     set_description( _("Timeshift") );
44     set_category( CAT_INPUT );
45     set_subcategory( SUBCAT_INPUT_ACCESS_FILTER );
46     set_capability( "access_filter", 0 );
47     add_shortcut( "timeshift" );
48     set_callbacks( Open, Close );
49 vlc_module_end();
50
51 /*****************************************************************************
52  * Local prototypes
53  *****************************************************************************/
54
55 static block_t *Block  ( access_t *p_access );
56 static int      Control( access_t *, int i_query, va_list args );
57 static void     Thread ( access_t * );
58
59 #define TIMESHIFT_FIFO_MAX (4*1024*1024)
60 #define TIMESHIFT_FIFO_MIN (TIMESHIFT_FIFO_MAX/4)
61
62 struct access_sys_t
63 {
64     block_fifo_t *p_fifo;
65
66     vlc_bool_t b_opened;
67
68     FILE *t1, *t2;
69
70     char *psz_tmp1;
71     char *psz_tmp2;
72
73     FILE *w;
74     int i_w;
75
76     FILE *r;
77 };
78
79 /*****************************************************************************
80  * Open:
81  *****************************************************************************/
82 static int Open( vlc_object_t *p_this )
83 {
84     access_t *p_access = (access_t*)p_this;
85     access_t *p_src = p_access->p_source;
86     access_sys_t *p_sys;
87     vlc_bool_t b_bool;
88 #ifdef WIN32
89     char buffer[4096];
90     int i_size;
91 #endif
92
93     /* Only work with not pace controled access */
94     if( access2_Control( p_src, ACCESS_CAN_CONTROL_PACE, &b_bool ) || b_bool )
95     {
96         msg_Dbg( p_src, "ACCESS_CAN_CONTROL_PACE" );
97         return VLC_EGENERIC;
98     }
99     /* Refuse access that can be paused */
100     if( access2_Control( p_src, ACCESS_CAN_PAUSE, &b_bool ) || b_bool )
101     {
102         msg_Dbg( p_src, "ACCESS_CAN_PAUSE: timeshift useless" );
103         return VLC_EGENERIC;
104     }
105
106     /* */
107     p_access->pf_read = NULL;
108     p_access->pf_block = Block;
109     p_access->pf_seek = NULL;
110     p_access->pf_control = Control;
111
112     p_access->info = p_src->info;
113
114     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
115
116     /* */
117     p_sys->p_fifo = block_FifoNew( p_access );
118     p_sys->b_opened = VLC_FALSE;
119     p_sys->t1 = p_sys->t2 = NULL;
120     p_sys->w = p_sys->r = NULL;
121     p_sys->i_w = 0;
122
123 #ifdef WIN32
124     i_size = GetTempPath( 4095, buffer );
125     if( i_size <= 0 || i_size >= 4095 )
126     {
127         if( getcwd( buffer, 4095 ) == NULL )
128             strcpy( buffer, "c:" );
129     }
130     /* remove last \\ if any */
131     if( buffer[strlen(buffer)-1] == '\\' )
132         buffer[strlen(buffer)-1] = '\0';
133
134     asprintf( &p_sys->psz_tmp1, "%s\\vlc-timeshift-%d-%d-1.dat",
135               buffer, GetCurrentProcessId(), p_access->i_object_id );
136     asprintf( &p_sys->psz_tmp2, "%s\\vlc-timeshift-%d-%d-2.dat",
137               buffer, GetCurrentProcessId(), p_access->i_object_id );
138 #else
139     asprintf( &p_sys->psz_tmp1, "/tmp/vlc-timeshift-%d-%d-1.dat",
140               getpid(), p_access->i_object_id );
141     asprintf( &p_sys->psz_tmp2, "/tmp/vlc-timeshift-%d-%d-2.dat",
142               getpid(), p_access->i_object_id );
143 #endif
144
145     if( vlc_thread_create( p_access, "timeshift thread", Thread,
146                            VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
147     {
148         msg_Err( p_access, "cannot spawn timeshift access thread" );
149         return VLC_EGENERIC;
150     }
151
152     return VLC_SUCCESS;
153 }
154
155 /*****************************************************************************
156  * Close:
157  *****************************************************************************/
158 static void Close( vlc_object_t *p_this )
159 {
160     access_t     *p_access = (access_t*)p_this;
161     access_sys_t *p_sys = p_access->p_sys;
162
163     /* */
164     msg_Dbg( p_access, "timeshift close called" );
165     vlc_thread_join( p_access );
166
167     if( p_sys->b_opened )
168     {
169         if( p_sys->t1 ) fclose( p_sys->t1 );
170         if( p_sys->t2 ) fclose( p_sys->t2 );
171         unlink( p_sys->psz_tmp1 );
172         unlink( p_sys->psz_tmp2 );
173     }
174
175     free( p_sys->psz_tmp1 );
176     free( p_sys->psz_tmp2 );
177
178     block_FifoRelease( p_sys->p_fifo );
179     free( p_sys );
180 }
181
182 /*****************************************************************************
183  *
184  *****************************************************************************/
185 static block_t *Block( access_t *p_access )
186 {
187     access_sys_t *p_sys = p_access->p_sys;
188
189     if( p_access->b_die )
190     {
191         p_access->info.b_eof = VLC_TRUE;
192         return NULL;
193     }
194
195     return block_FifoGet( p_sys->p_fifo );
196 }
197
198 /*****************************************************************************
199  *
200  *****************************************************************************/
201 static int Control( access_t *p_access, int i_query, va_list args )
202 {
203     access_t     *p_src = p_access->p_source;
204
205     vlc_bool_t   *pb_bool;
206     int          *pi_int;
207     int64_t      *pi_64;
208
209     switch( i_query )
210     {
211         /* */
212         case ACCESS_CAN_SEEK:
213         case ACCESS_CAN_FASTSEEK:
214             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
215             *pb_bool = VLC_FALSE;
216             break;
217
218         case ACCESS_CAN_CONTROL_PACE:   /* Not really true */
219         case ACCESS_CAN_PAUSE:
220             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
221             *pb_bool = VLC_TRUE;
222             break;
223
224         /* */
225         case ACCESS_GET_MTU:
226             pi_int = (int*)va_arg( args, int * );
227             *pi_int = 0;
228             break;
229
230         case ACCESS_GET_PTS_DELAY:
231             pi_64 = (int64_t*)va_arg( args, int64_t * );
232             return access2_Control( p_src, ACCESS_GET_PTS_DELAY, pi_64 );
233         /* */
234         case ACCESS_SET_PAUSE_STATE:
235             return VLC_SUCCESS;
236
237         case ACCESS_GET_TITLE_INFO:
238         case ACCESS_SET_TITLE:
239         case ACCESS_SET_SEEKPOINT:
240         case ACCESS_GET_META:
241             return VLC_EGENERIC;
242
243         case ACCESS_SET_PRIVATE_ID_STATE:
244         case ACCESS_GET_PRIVATE_ID_STATE:
245         case ACCESS_SET_PRIVATE_ID_CA:
246             return access2_vaControl( p_src, i_query, args );
247
248         default:
249             msg_Warn( p_access, "unimplemented query in control" );
250             return VLC_EGENERIC;
251
252     }
253     return VLC_SUCCESS;
254 }
255
256 /*****************************************************************************
257  *
258  *****************************************************************************/
259 static void Thread( access_t *p_access )
260 {
261     access_sys_t *p_sys = p_access->p_sys;
262     access_t     *p_src = p_access->p_source;
263     int i_loop = 0;
264
265     while( !p_access->b_die )
266     {
267         block_t *p_block;
268
269         /* Get a new block */
270         if( p_src->pf_block )
271         {
272             p_block = p_src->pf_block( p_src );
273
274             if( p_block == NULL )
275             {
276                 if( p_src->info.b_eof )
277                     break;
278
279                 msleep( 1000 );
280                 continue;
281             }
282         }
283         else
284         {
285             if( ( p_block = block_New( p_access, 2048 ) ) == NULL )
286                 break;
287
288             p_block->i_buffer = p_src->pf_read( p_src, p_block->p_buffer, 2048);
289             if( p_block->i_buffer < 0 )
290             {
291                 block_Release( p_block );
292                 if( p_block->i_buffer == 0 )
293                     break;
294                 msleep( 1000 );
295                 continue;
296             }
297         }
298
299         /* Open dump files if we need them */
300         if( p_sys->p_fifo->i_size >= TIMESHIFT_FIFO_MAX && !p_sys->b_opened )
301         {
302             msg_Dbg( p_access, "opening first temporary files (%s)",
303                      p_sys->psz_tmp1 );
304
305             p_sys->b_opened = VLC_TRUE;
306             p_sys->t1 = p_sys->t2 = NULL;
307             p_sys->w = p_sys->r = NULL;
308
309             p_sys->t1 = fopen( p_sys->psz_tmp1, "w+b" );
310             if( p_sys->t1 )
311             {
312                 msg_Dbg( p_access, "opening second temporary files (%s)",
313                          p_sys->psz_tmp2 );
314
315                 p_sys->t2 = fopen( p_sys->psz_tmp2, "w+b" );
316                 if( p_sys->t2 )
317                 {
318                     p_sys->w = p_sys->t1;
319                     p_sys->i_w = 0;
320
321                     msg_Dbg( p_access, "start writing into temporary file" );
322                 }
323                 else
324                 {
325                     msg_Err( p_access, "cannot open temporary file '%s' (%s)",
326                              p_sys->psz_tmp2, strerror(errno) );
327                     fclose( p_sys->t1 );
328                     p_sys->t1 = NULL;
329                 }
330             }
331             else
332             {
333                 msg_Err( p_access, "cannot open temporary file '%s' (%s)",
334                          p_sys->psz_tmp1, strerror(errno) );
335             }
336         }
337
338         if( p_sys->w )
339         {
340             int i_write;
341
342             /* Dump the block */
343             i_write = fwrite( p_block->p_buffer, 1, p_block->i_buffer,
344                               p_sys->w );
345             block_Release( p_block );
346
347             if( i_write > 0 )
348                 p_sys->i_w += i_write;
349             else
350                 msg_Warn( p_access, "cannot write data" );
351
352             /* Start reading from a file if fifo isn't at 25% */
353             if( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN && !p_sys->r )
354             {
355                 msg_Dbg( p_access, "start reading from temporary file (dumped=%d)", p_sys->i_w );
356
357                 p_sys->r = p_sys->w;
358                 fseek( p_sys->r, 0, SEEK_SET );
359
360                 p_sys->w = p_sys->t2;
361                 p_sys->i_w = 0;
362             }
363
364             if( p_sys->r )
365             {
366                 while( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN )
367                 {
368                     p_block = block_New( p_access, 4096 );
369                     p_block->i_buffer = fread( p_block->p_buffer, 1, 4096,
370                                                p_sys->r );
371
372                     if( p_block->i_buffer > 0 )
373                     {
374                         block_FifoPut( p_sys->p_fifo, p_block );
375                     }
376                     else if( p_sys->i_w > 32*1024)
377                     {
378                         FILE *tmp;
379                         block_Release( p_block );
380
381                         msg_Dbg( p_access, "switching temporary files (dumped=%d)", p_sys->i_w );
382
383                         /* Switch read/write */
384                         tmp = p_sys->r;
385
386                         p_sys->r = p_sys->w;
387                         fseek( p_sys->r, 0, SEEK_SET );
388
389                         p_sys->w = tmp;
390                         fseek( p_sys->w, 0, SEEK_SET );
391                         ftruncate( fileno(p_sys->w), 0 );
392                         p_sys->i_w = 0;
393                     }
394                     else
395                     {
396                         msg_Dbg( p_access, "removing temporary files" );
397
398                         /* We will remove the need of tmp files */
399                         if( p_sys->i_w > 0 )
400                         {
401                             msg_Dbg( p_access, "loading temporary file" );
402                             fseek( p_sys->w, 0, SEEK_SET );
403                             for( ;; )
404                             {
405                                 p_block = block_New( p_access, 4096 );
406                                 p_block->i_buffer = fread( p_block->p_buffer,
407                                                            1, 4096,
408                                                            p_sys->w );
409                                 if( p_block->i_buffer <= 0 )
410                                 {
411                                     block_Release( p_block );
412                                     break;
413                                 }
414                                 block_FifoPut( p_sys->p_fifo, p_block );
415                             }
416                         }
417
418                         p_sys->b_opened = VLC_FALSE;
419
420                         fclose( p_sys->t1 );
421                         fclose( p_sys->t2 );
422
423                         p_sys->t1 = p_sys->t2 = NULL;
424                         p_sys->w = p_sys->r = NULL;
425
426                         unlink( p_sys->psz_tmp1 );
427                         unlink( p_sys->psz_tmp2 );
428                         break;
429                     }
430                 }
431             }
432         }
433         else if( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
434         {
435             block_FifoPut( p_sys->p_fifo, p_block );
436         }
437         else
438         {
439             /* We failed to opened files so trash new data */
440             block_Release( p_block );
441         }
442 #if 0
443         if( (i_loop % 400) == 0 )
444             msg_Dbg( p_access, "timeshift: buff=%d", p_sys->p_fifo->i_size );
445 #endif
446         i_loop++;
447     }
448
449     msg_Warn( p_access, "timeshift: EOF" );
450
451     /* Send dummy packet to avoid deadlock in TShiftBlock */
452     for( i_loop = 0; i_loop < 2; i_loop++ )
453     {
454         block_t *p_dummy = block_New( p_access, 128 );
455
456         p_dummy->i_flags |= BLOCK_FLAG_DISCONTINUITY;
457         memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
458
459         block_FifoPut( p_sys->p_fifo, p_dummy );
460     }
461 }
462