00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
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
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
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
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
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
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;
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
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
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
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
00267 if( !p_sys->p_write_list && !p_sys->p_read_list &&
00268 p_sys->p_fifo->i_size < TIMESHIFT_FIFO_MAX )
00269 {
00270
00271
00272 block_FifoPut( p_sys->p_fifo, p_block );
00273
00274
00275
00276
00277
00278 continue;
00279 }
00280
00281 WriteBlockToFile( p_access, p_block );
00282 block_Release( p_block );
00283
00284
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
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
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
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
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
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
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
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
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
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
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
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
00407
00408
00409 if( i_write < i_buffer )
00410 {
00411
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
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
00428 if( p_block->i_buffer ) return WriteBlockToFile( p_access, p_block );
00429
00430 return VLC_SUCCESS;
00431 }
00432
00433
00434
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
00444
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
00457
00458
00459 return p_block;
00460 }
00461
00462
00463
00464
00465 static int Seek( access_t *p_access, int64_t i_pos )
00466 {
00467
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:
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
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
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 }