Main Page | Modules | Class Hierarchy | Class List | Directories | File List | Class Members | File Members | Related Pages

timeshift.c

00001 /*****************************************************************************
00002  * timeshift.c: access filter implementing timeshifting capabilities
00003  *****************************************************************************
00004  * Copyright (C) 2005 the VideoLAN team
00005  * $Id: timeshift.c 12924 2005-10-23 09:33:13Z courmisch $
00006  *
00007  * Authors: Laurent Aimar <[email protected]>
00008  *          Gildas Bazin <[email protected]>
00009  *
00010  * This program is free software; you can redistribute it and/or modify
00011  * it under the terms of the GNU General Public License as published by
00012  * the Free Software Foundation; either version 2 of the License, or
00013  * (at your option) any later version.
00014  *
00015  * This program is distributed in the hope that it will be useful,
00016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  * GNU General Public License for more details.
00019  *
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
00023  *****************************************************************************/
00024 
00025 /*****************************************************************************
00026  * Preamble
00027  *****************************************************************************/
00028 #include <stdlib.h>
00029 
00030 #include <errno.h>
00031 
00032 #include <vlc/vlc.h>
00033 #include <vlc/input.h>
00034 #include <unistd.h>
00035 
00036 /*****************************************************************************
00037  * Module descriptor
00038  *****************************************************************************/
00039 static int  Open ( vlc_object_t * );
00040 static void Close( vlc_object_t * );
00041 
00042 #define GRANULARITY_TEXT N_("Timeshift granularity")
00043 #define GRANULARITY_LONGTEXT N_( "Size of the temporary files use to store " \
00044   "the timeshifted stream." )
00045 #define DIR_TEXT N_("Timeshift directory")
00046 #define DIR_LONGTEXT N_( "Directory used to store the timeshift temporary " \
00047   "files." )
00048 
00049 vlc_module_begin();
00050     set_shortname( _("Timeshift") );
00051     set_description( _("Timeshift") );
00052     set_category( CAT_INPUT );
00053     set_subcategory( SUBCAT_INPUT_ACCESS_FILTER );
00054     set_capability( "access_filter", 0 );
00055     add_shortcut( "timeshift" );
00056     set_callbacks( Open, Close );
00057 
00058     add_integer( "timeshift-granularity", 50, NULL, GRANULARITY_TEXT,
00059                  GRANULARITY_LONGTEXT, VLC_TRUE );
00060     add_directory( "timeshift-dir", 0, 0, DIR_TEXT, DIR_LONGTEXT, VLC_FALSE );
00061 vlc_module_end();
00062 
00063 /*****************************************************************************
00064  * Local prototypes
00065  *****************************************************************************/
00066 
00067 static int      Seek( access_t *, int64_t );
00068 static block_t *Block  ( access_t *p_access );
00069 static int      Control( access_t *, int i_query, va_list args );
00070 static void     Thread ( access_t *p_access );
00071 static int      WriteBlockToFile( access_t *p_access, block_t *p_block );
00072 static block_t *ReadBlockFromFile( access_t *p_access );
00073 static char    *GetTmpFilePath( access_t *p_access );
00074 
00075 #define TIMESHIFT_FIFO_MAX (10*1024*1024)
00076 #define TIMESHIFT_FIFO_MIN (TIMESHIFT_FIFO_MAX/4)
00077 #define TMP_FILE_MAX 256
00078 
00079 typedef struct ts_entry_t
00080 {
00081     FILE *file;
00082     struct ts_entry_t *p_next;
00083 
00084 } ts_entry_t;
00085 
00086 struct access_sys_t
00087 {
00088     block_fifo_t *p_fifo;
00089 
00090     int  i_files;
00091     int  i_file_size;
00092     int  i_write_size;
00093 
00094     ts_entry_t *p_read_list;
00095     ts_entry_t **pp_read_last;
00096     ts_entry_t *p_write_list;
00097     ts_entry_t **pp_write_last;
00098 
00099     char *psz_filename_base;
00100     char *psz_filename;
00101 };
00102 
00103 /*****************************************************************************
00104  * Open:
00105  *****************************************************************************/
00106 static int Open( vlc_object_t *p_this )
00107 {
00108     access_t *p_access = (access_t*)p_this;
00109     access_t *p_src = p_access->p_source;
00110     access_sys_t *p_sys;
00111     vlc_bool_t b_bool;
00112 
00113     /* Only work with not pace controled access */
00114     if( access2_Control( p_src, ACCESS_CAN_CONTROL_PACE, &b_bool ) || b_bool )
00115     {
00116         msg_Dbg( p_src, "ACCESS_CAN_CONTROL_PACE" );
00117         return VLC_EGENERIC;
00118     }
00119     /* Refuse access that can be paused */
00120     if( access2_Control( p_src, ACCESS_CAN_PAUSE, &b_bool ) || b_bool )
00121     {
00122         msg_Dbg( p_src, "ACCESS_CAN_PAUSE: timeshift useless" );
00123         return VLC_EGENERIC;
00124     }
00125 
00126     /* */
00127     p_access->pf_read = NULL;
00128     p_access->pf_block = Block;
00129     p_access->pf_seek = Seek;
00130     p_access->pf_control = Control;
00131     p_access->info = p_src->info;
00132 
00133     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
00134 
00135     /* */
00136     p_sys->p_fifo = block_FifoNew( p_access );
00137     p_sys->i_write_size = 0;
00138     p_sys->i_files = 0;
00139 
00140     p_sys->p_read_list = NULL;
00141     p_sys->pp_read_last = &p_sys->p_read_list;
00142     p_sys->p_write_list = NULL;
00143     p_sys->pp_write_last = &p_sys->p_write_list;
00144 
00145     var_Create( p_access, "timeshift-dir",
00146                 VLC_VAR_DIRECTORY | VLC_VAR_DOINHERIT );
00147     var_Create( p_access, "timeshift-granularity",
00148                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
00149     p_sys->i_file_size = var_GetInteger( p_access, "timeshift-granularity" );
00150     if( p_sys->i_file_size < 1 ) p_sys->i_file_size = 1;
00151     p_sys->i_file_size *= 1024 * 1024; /* In MBytes */
00152 
00153     p_sys->psz_filename_base = GetTmpFilePath( p_access );
00154     p_sys->psz_filename = malloc( strlen( p_sys->psz_filename_base ) + 1000 );
00155 
00156     if( vlc_thread_create( p_access, "timeshift thread", Thread,
00157                            VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
00158     {
00159         msg_Err( p_access, "cannot spawn timeshift access thread" );
00160         return VLC_EGENERIC;
00161     }
00162 
00163     return VLC_SUCCESS;
00164 }
00165 
00166 /*****************************************************************************
00167  * Close:
00168  *****************************************************************************/
00169 static void Close( vlc_object_t *p_this )
00170 {
00171     access_t     *p_access = (access_t*)p_this;
00172     access_sys_t *p_sys = p_access->p_sys;
00173     ts_entry_t *p_entry;
00174     int i;
00175 
00176     msg_Dbg( p_access, "timeshift close called" );
00177     vlc_thread_join( p_access );
00178 
00179     for( p_entry = p_sys->p_write_list; p_entry; )
00180     {
00181         ts_entry_t *p_next = p_entry->p_next;
00182         fclose( p_entry->file );
00183         free( p_entry );
00184         p_entry = p_next;
00185     }
00186     for( p_entry = p_sys->p_read_list; p_entry; )
00187     {
00188         ts_entry_t *p_next = p_entry->p_next;
00189         fclose( p_entry->file );
00190         free( p_entry );
00191         p_entry = p_next;
00192     }
00193     for( i = 0; i < p_sys->i_files; i++ )
00194     {
00195         sprintf( p_sys->psz_filename, "%s%i.dat",
00196                  p_sys->psz_filename_base, i );
00197         unlink( p_sys->psz_filename );
00198     }
00199 
00200     free( p_sys->psz_filename );
00201     free( p_sys->psz_filename_base );
00202     block_FifoRelease( p_sys->p_fifo );
00203     free( p_sys );
00204 }
00205 
00206 /*****************************************************************************
00207  *
00208  *****************************************************************************/
00209 static block_t *Block( access_t *p_access )
00210 {
00211     access_sys_t *p_sys = p_access->p_sys;
00212     block_t *p_block;
00213 
00214     if( p_access->b_die )
00215     {
00216         p_access->info.b_eof = VLC_TRUE;
00217         return NULL;
00218     }
00219 
00220     p_block = block_FifoGet( p_sys->p_fifo );
00221     //p_access->info.i_size -= p_block->i_buffer;
00222     return p_block;
00223 }
00224 
00225 /*****************************************************************************
00226  *
00227  *****************************************************************************/
00228 static void Thread( access_t *p_access )
00229 {
00230     access_sys_t *p_sys = p_access->p_sys;
00231     access_t     *p_src = p_access->p_source;
00232     int i;
00233 
00234     while( !p_access->b_die )
00235     {
00236         block_t *p_block;
00237 
00238         /* Get a new block from the source */
00239         if( p_src->pf_block )
00240         {
00241             p_block = p_src->pf_block( p_src );
00242 
00243             if( p_block == NULL )
00244             {
00245                 if( p_src->info.b_eof ) break;
00246                 msleep( 1000 );
00247                 continue;
00248             }
00249         }
00250         else
00251         {
00252             if( ( p_block = block_New( p_access, 2048 ) ) == NULL ) break;
00253 
00254             p_block->i_buffer =
00255                 p_src->pf_read( p_src, p_block->p_buffer, 2048 );
00256 
00257             if( p_block->i_buffer < 0 )
00258             {
00259                 block_Release( p_block );
00260                 if( p_block->i_buffer == 0 ) break;
00261                 msleep( 1000 );
00262                 continue;
00263             }
00264         }
00265 
00266         /* Write block */
00267         if( !p_sys->p_write_list && !p_sys->p_read_list &&
00268             p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
00269         {
00270             /* If there isn't too much timeshifted data,
00271              * write directly to FIFO */
00272             block_FifoPut( p_sys->p_fifo, p_block );
00273 
00274             //p_access->info.i_size += p_block->i_buffer;
00275             //p_access->info.i_update |= INPUT_UPDATE_SIZE;
00276 
00277             /* Nothing else to do */
00278             continue;
00279         }
00280 
00281         WriteBlockToFile( p_access, p_block );
00282         block_Release( p_block );
00283 
00284         /* Read from file to fill up the fifo */
00285         while( p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MIN &&
00286                !p_access->b_die )
00287         {
00288             p_block = ReadBlockFromFile( p_access );
00289             if( !p_block ) break;
00290             block_FifoPut( p_sys->p_fifo, p_block );
00291         }
00292     }
00293 
00294     msg_Dbg( p_access, "timeshift: EOF" );
00295 
00296     /* Send dummy packet to avoid deadlock in TShiftBlock */
00297     for( i = 0; i < 2; i++ )
00298     {
00299         block_t *p_dummy = block_New( p_access, 128 );
00300         p_dummy->i_flags |= BLOCK_FLAG_DISCONTINUITY;
00301         memset( p_dummy->p_buffer, 0, p_dummy->i_buffer );
00302         block_FifoPut( p_sys->p_fifo, p_dummy );
00303     }
00304 }
00305 
00306 /*****************************************************************************
00307  * NextFileWrite:
00308  *****************************************************************************/
00309 static void NextFileWrite( access_t *p_access )
00310 {
00311     access_sys_t *p_sys = p_access->p_sys;
00312     ts_entry_t   *p_next;
00313 
00314     if( !p_sys->p_write_list )
00315     {
00316         p_sys->i_write_size = 0;
00317         return;
00318     }
00319 
00320     p_next = p_sys->p_write_list->p_next;
00321 
00322     /* Put written file in read list */
00323     if( p_sys->i_write_size < p_sys->i_file_size )
00324         ftruncate( fileno( p_sys->p_write_list->file ), p_sys->i_write_size );
00325 
00326     fseek( p_sys->p_write_list->file, 0, SEEK_SET );
00327     *p_sys->pp_read_last = p_sys->p_write_list;
00328     p_sys->pp_read_last = &p_sys->p_write_list->p_next;
00329     p_sys->p_write_list->p_next = 0;
00330 
00331     /* Switch to next file to write */
00332     p_sys->p_write_list = p_next;
00333     if( !p_sys->p_write_list ) p_sys->pp_write_last = &p_sys->p_write_list;
00334 
00335     p_sys->i_write_size = 0;
00336 }
00337 
00338 /*****************************************************************************
00339  * NextFileRead:
00340  *****************************************************************************/
00341 static void NextFileRead( access_t *p_access )
00342 {
00343     access_sys_t *p_sys = p_access->p_sys;
00344     ts_entry_t   *p_next;
00345 
00346     if( !p_sys->p_read_list ) return;
00347 
00348     p_next = p_sys->p_read_list->p_next;
00349 
00350     /* Put read file in write list */
00351     fseek( p_sys->p_read_list->file, 0, SEEK_SET );
00352     *p_sys->pp_write_last = p_sys->p_read_list;
00353     p_sys->pp_write_last = &p_sys->p_read_list->p_next;
00354     p_sys->p_read_list->p_next = 0;
00355 
00356     /* Switch to next file to read */
00357     p_sys->p_read_list = p_next;
00358     if( !p_sys->p_read_list ) p_sys->pp_read_last = &p_sys->p_read_list;
00359 }
00360 
00361 /*****************************************************************************
00362  * WriteBlockToFile:
00363  *****************************************************************************/
00364 static int WriteBlockToFile( access_t *p_access, block_t *p_block )
00365 {
00366     access_sys_t *p_sys = p_access->p_sys;
00367     int i_write, i_buffer;
00368 
00369     if( p_sys->i_write_size == p_sys->i_file_size ) NextFileWrite( p_access );
00370 
00371     /* Open new file if necessary */
00372     if( !p_sys->p_write_list )
00373     {
00374         FILE *file;
00375 
00376         sprintf( p_sys->psz_filename, "%s%i.dat",
00377                  p_sys->psz_filename_base, p_sys->i_files );
00378         file = fopen( p_sys->psz_filename, "w+b" );
00379 
00380         if( !file && p_sys->i_files < 2 )
00381         {
00382             /* We just can't work with less than 2 buffer files */
00383             msg_Err( p_access, "cannot open temporary file '%s' (%s)",
00384                      p_sys->psz_filename, strerror(errno) );
00385             return VLC_EGENERIC;
00386         }
00387         else if( !file ) return VLC_EGENERIC;
00388 
00389         p_sys->p_write_list = malloc( sizeof(ts_entry_t) );
00390         p_sys->p_write_list->p_next = 0;
00391         p_sys->p_write_list->file = file;
00392         p_sys->pp_write_last = &p_sys->p_write_list->p_next;
00393 
00394         p_sys->i_files++;
00395     }
00396 
00397     /* Write to file */
00398     i_buffer = __MIN( p_block->i_buffer,
00399                       p_sys->i_file_size - p_sys->i_write_size );
00400 
00401     i_write = fwrite( p_block->p_buffer, 1, i_buffer,
00402                       p_sys->p_write_list->file );
00403 
00404     if( i_write > 0 ) p_sys->i_write_size += i_write;
00405 
00406     //p_access->info.i_size += i_write;
00407     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
00408 
00409     if( i_write < i_buffer )
00410     {
00411         /* Looks like we're short of space */
00412 
00413         if( !p_sys->p_write_list->p_next )
00414         {
00415             msg_Warn( p_access, "no more space, overwritting old data" );
00416             NextFileRead( p_access );
00417             NextFileRead( p_access );
00418         }
00419 
00420         /* Make sure we switch to next file in write list */
00421         p_sys->i_write_size = p_sys->i_file_size;
00422     }
00423 
00424     p_block->p_buffer += i_write;
00425     p_block->i_buffer -= i_write;
00426 
00427     /* Check if we have some data left */
00428     if( p_block->i_buffer ) return WriteBlockToFile( p_access, p_block );
00429 
00430     return VLC_SUCCESS;
00431 }
00432 
00433 /*****************************************************************************
00434  * ReadBlockFromFile:
00435  *****************************************************************************/
00436 static block_t *ReadBlockFromFile( access_t *p_access )
00437 {
00438     access_sys_t *p_sys = p_access->p_sys;
00439     block_t *p_block;
00440 
00441     if( !p_sys->p_read_list && p_sys->p_write_list )
00442     {
00443         /* Force switching to next write file, that should
00444          * give us something to read */
00445         NextFileWrite( p_access );
00446     }
00447 
00448     if( !p_sys->p_read_list ) return 0;
00449 
00450     p_block = block_New( p_access, 4096 );
00451     p_block->i_buffer = fread( p_block->p_buffer, 1, 4096,
00452                                p_sys->p_read_list->file );
00453 
00454     if( p_block->i_buffer == 0 ) NextFileRead( p_access );
00455 
00456     //p_access->info.i_size -= p_block->i_buffer;
00457     //p_access->info.i_update |= INPUT_UPDATE_SIZE;
00458 
00459     return p_block;
00460 }
00461 
00462 /*****************************************************************************
00463  * Seek: seek to a specific location in a file
00464  *****************************************************************************/
00465 static int Seek( access_t *p_access, int64_t i_pos )
00466 {
00467     //access_sys_t *p_sys = p_access->p_sys;
00468     return VLC_SUCCESS;
00469 }
00470 
00471 /*****************************************************************************
00472  *
00473  *****************************************************************************/
00474 static int Control( access_t *p_access, int i_query, va_list args )
00475 {
00476     access_t     *p_src = p_access->p_source;
00477 
00478     vlc_bool_t   *pb_bool;
00479     int          *pi_int;
00480     int64_t      *pi_64;
00481 
00482     switch( i_query )
00483     {
00484         case ACCESS_CAN_SEEK:
00485         case ACCESS_CAN_FASTSEEK:
00486             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
00487             *pb_bool = VLC_TRUE;
00488             break;
00489 
00490         case ACCESS_CAN_CONTROL_PACE:   /* Not really true */
00491         case ACCESS_CAN_PAUSE:
00492             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
00493             *pb_bool = VLC_TRUE;
00494             break;
00495 
00496         case ACCESS_GET_MTU:
00497             pi_int = (int*)va_arg( args, int * );
00498             *pi_int = 0;
00499             break;
00500 
00501         case ACCESS_GET_PTS_DELAY:
00502             pi_64 = (int64_t*)va_arg( args, int64_t * );
00503             return access2_Control( p_src, ACCESS_GET_PTS_DELAY, pi_64 );
00504 
00505         case ACCESS_SET_PAUSE_STATE:
00506             return VLC_SUCCESS;
00507 
00508         case ACCESS_GET_TITLE_INFO:
00509         case ACCESS_SET_TITLE:
00510         case ACCESS_SET_SEEKPOINT:
00511         case ACCESS_GET_META:
00512             return VLC_EGENERIC;
00513 
00514         case ACCESS_SET_PRIVATE_ID_STATE:
00515         case ACCESS_GET_PRIVATE_ID_STATE:
00516         case ACCESS_SET_PRIVATE_ID_CA:
00517             return access2_vaControl( p_src, i_query, args );
00518 
00519         default:
00520             msg_Warn( p_access, "unimplemented query in control" );
00521             return VLC_EGENERIC;
00522 
00523     }
00524     return VLC_SUCCESS;
00525 }
00526 
00527 /*****************************************************************************
00528  * GetTmpFilePath:
00529  *****************************************************************************/
00530 #ifdef WIN32
00531 #define getpid() GetCurrentProcessId()
00532 #endif
00533 static char *GetTmpFilePath( access_t *p_access )
00534 {
00535     char *psz_dir = var_GetString( p_access, "timeshift-dir" );
00536     char *psz_filename_base;
00537 
00538     if( psz_dir && !*psz_dir )
00539     {
00540         free( psz_dir );
00541         psz_dir = 0;
00542     }
00543 
00544     if( !psz_dir )
00545     {
00546 #ifdef WIN32
00547         int i_size;
00548 
00549         psz_dir = malloc( MAX_PATH + 1 );
00550         i_size = GetTempPath( MAX_PATH, psz_dir );
00551         if( i_size <= 0 || i_size > MAX_PATH )
00552         {
00553             if( !getcwd( psz_dir, MAX_PATH ) ) strcpy( psz_dir, "c:" );
00554         }
00555 
00556         /* remove last \\ if any */
00557         if( psz_dir[strlen(psz_dir)-1] == '\\' )
00558             psz_dir[strlen(psz_dir)-1] = '\0';
00559 #else
00560 
00561         psz_dir = strdup( "/tmp" );
00562 #endif
00563     }
00564 
00565     asprintf( &psz_filename_base, "%s/vlc-timeshift-%d-%d-",
00566               psz_dir, getpid(), p_access->i_object_id );
00567 
00568     return psz_filename_base;
00569 }

Generated on Tue Dec 20 10:14:27 2005 for vlc-0.8.4a by  doxygen 1.4.2