1 /*****************************************************************************
2 * es_out_timeshift.c: Es Out timeshift.
3 *****************************************************************************
4 * Copyright (C) 2008 Laurent Aimar
7 * Authors: Laurent Aimar < fenrir _AT_ via _DOT_ ecp _DOT_ fr>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
35 #if defined (WIN32) && !defined (UNDER_CE)
38 #ifdef HAVE_SYS_STAT_H
39 # include <sys/stat.h>
42 #include <vlc_common.h>
43 #include <vlc_charset.h>
45 #include <vlc_input.h>
46 #include <vlc_es_out.h>
47 #include <vlc_block.h>
48 #include "input_internal.h"
50 #include "es_out_timeshift.h"
52 /*****************************************************************************
54 *****************************************************************************/
107 input_thread_t *p_input;
111 int64_t i_tmp_size_max; /* Maximal temporary file size in byte */
112 char *psz_tmp_path; /* Path for temporary files */
114 /* Lock for all following fields */
129 static es_out_id_t *Add ( es_out_t *, const es_format_t * );
130 static int Send ( es_out_t *, es_out_id_t *, block_t * );
131 static void Del ( es_out_t *, es_out_id_t * );
132 static int Control( es_out_t *, int i_query, va_list );
133 static void Destroy( es_out_t * );
135 static void CmdPush( es_out_t *, const ts_cmd_t * );
136 static int CmdPop( es_out_t *p_out, ts_cmd_t *p_cmd );
138 static int CmdInitAdd ( ts_cmd_t *, es_out_id_t *, const es_format_t *, bool b_copy );
139 static void CmdInitSend ( ts_cmd_t *, es_out_id_t *, block_t * );
140 static int CmdInitDel ( ts_cmd_t *, es_out_id_t * );
141 static int CmdInitControl( ts_cmd_t *, int i_query, va_list, bool b_copy );
143 static void CmdExecuteAdd ( es_out_t *, ts_cmd_t * );
144 static int CmdExecuteSend ( es_out_t *, ts_cmd_t * );
145 static void CmdExecuteDel ( es_out_t *, ts_cmd_t * );
146 static int CmdExecuteControl( es_out_t *, ts_cmd_t * );
148 static void CmdCleanAdd ( ts_cmd_t * );
149 static void CmdCleanSend ( ts_cmd_t * );
150 static void CmdCleanControl( ts_cmd_t *p_cmd );
152 static char *GetTmpPath( char *psz_path );
153 static FILE *GetTmpFile( const char *psz_path );
155 /*****************************************************************************
156 * input_EsOutTimeshiftNew:
157 *****************************************************************************/
158 es_out_t *input_EsOutTimeshiftNew( input_thread_t *p_input, es_out_t *p_next_out )
160 es_out_t *p_out = malloc( sizeof(*p_out) );
164 es_out_sys_t *p_sys = malloc( sizeof(*p_sys) );
173 p_out->pf_send = Send;
175 p_out->pf_control = Control;
176 p_out->pf_destroy = Destroy;
177 p_out->p_sys = p_sys;
178 p_out->b_sout = p_input->p->p_sout != NULL;
181 p_sys->p_input = p_input;
182 p_sys->p_out = p_next_out;
183 vlc_mutex_init_recursive( &p_sys->lock );
184 p_sys->b_delayed = false;
186 TAB_INIT( p_sys->i_es, p_sys->pp_es );
187 TAB_INIT( p_sys->i_cmd, p_sys->pp_cmd );
190 * timeshift-granularity
193 p_sys->i_tmp_size_max = 50 * 1024*1024;
194 p_sys->psz_tmp_path = GetTmpPath( NULL );
199 /*****************************************************************************
201 *****************************************************************************/
202 static void Destroy( es_out_t *p_out )
204 es_out_sys_t *p_sys = p_out->p_sys;
206 while( p_sys->i_cmd > 0 )
210 if( CmdPop( p_out, &cmd ) )
219 CmdCleanSend( &cmd );
222 CmdCleanControl( &cmd );
231 TAB_CLEAN( p_sys->i_cmd, p_sys->pp_cmd );
233 while( p_sys->i_es > 0 )
234 Del( p_out, p_sys->pp_es[0] );
235 TAB_CLEAN( p_sys->i_es, p_sys->pp_es );
237 free( p_sys->psz_tmp_path );
238 vlc_mutex_destroy( &p_sys->lock );
243 static es_out_id_t *Add( es_out_t *p_out, const es_format_t *p_fmt )
245 es_out_sys_t *p_sys = p_out->p_sys;
248 es_out_id_t *p_es = malloc( sizeof( *p_es ) );
252 vlc_mutex_lock( &p_sys->lock );
254 if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
256 vlc_mutex_unlock( &p_sys->lock );
261 TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );
263 if( p_sys->b_delayed )
264 CmdPush( p_out, &cmd );
266 CmdExecuteAdd( p_out, &cmd );
268 vlc_mutex_unlock( &p_sys->lock );
272 static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
274 es_out_sys_t *p_sys = p_out->p_sys;
276 int i_ret = VLC_SUCCESS;
278 vlc_mutex_lock( &p_sys->lock );
280 CmdInitSend( &cmd, p_es, p_block );
281 if( p_sys->b_delayed )
282 CmdPush( p_out, &cmd );
284 i_ret = CmdExecuteSend( p_out, &cmd) ;
286 vlc_mutex_unlock( &p_sys->lock );
290 static void Del( es_out_t *p_out, es_out_id_t *p_es )
292 es_out_sys_t *p_sys = p_out->p_sys;
295 vlc_mutex_lock( &p_sys->lock );
297 CmdInitDel( &cmd, p_es );
298 if( p_sys->b_delayed )
299 CmdPush( p_out, &cmd );
301 CmdExecuteDel( p_out, &cmd );
303 TAB_REMOVE( p_sys->i_es, p_sys->pp_es, p_es );
305 vlc_mutex_unlock( &p_sys->lock );
307 static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
309 es_out_sys_t *p_sys = p_out->p_sys;
313 /* Invalid query for this es_out level */
314 case ES_OUT_SET_ES_BY_ID:
315 case ES_OUT_RESTART_ES_BY_ID:
316 case ES_OUT_SET_ES_DEFAULT_BY_ID:
317 case ES_OUT_SET_DELAY:
318 case ES_OUT_SET_RECORD_STATE:
322 /* TODO ? or to remove ? */
326 /* Pass-through control */
327 case ES_OUT_SET_ACTIVE:
328 case ES_OUT_GET_ACTIVE:
329 case ES_OUT_SET_MODE:
330 case ES_OUT_GET_MODE:
331 case ES_OUT_SET_GROUP:
332 case ES_OUT_GET_GROUP:
334 case ES_OUT_SET_GROUP_PCR:
335 case ES_OUT_RESET_PCR:
336 case ES_OUT_SET_NEXT_DISPLAY_TIME:
337 case ES_OUT_SET_GROUP_META:
338 case ES_OUT_SET_GROUP_EPG:
339 case ES_OUT_DEL_GROUP:
341 case ES_OUT_RESTART_ES:
342 case ES_OUT_SET_ES_DEFAULT:
343 case ES_OUT_SET_ES_STATE:
344 case ES_OUT_GET_ES_STATE:
345 case ES_OUT_SET_ES_FMT:
348 if( CmdInitControl( &cmd, i_query, args, p_sys->b_delayed ) )
350 if( p_sys->b_delayed )
352 CmdPush( p_out, &cmd );
355 return CmdExecuteControl( p_out, &cmd );
358 /* Special control */
359 case ES_OUT_GET_WAKE_UP: /* TODO ? */
360 case ES_OUT_GET_BUFFERING:
361 case ES_OUT_GET_EMPTY:
362 case ES_OUT_SET_PAUSE_STATE:
363 case ES_OUT_SET_RATE:
364 case ES_OUT_SET_TIME:
365 case ES_OUT_SET_FRAME_NEXT:
366 if( p_sys->b_delayed )
370 return es_out_vaControl( p_sys->p_out, i_query, args );
373 msg_Err( p_sys->p_input, "Unknown es_out_Control query !" );
378 static int Control( es_out_t *p_out, int i_query, va_list args )
380 es_out_sys_t *p_sys = p_out->p_sys;
383 vlc_mutex_lock( &p_sys->lock );
384 i_ret = ControlLocked( p_out, i_query, args );
385 vlc_mutex_unlock( &p_sys->lock );
390 /*****************************************************************************
392 *****************************************************************************/
393 static void CmdPush( es_out_t *p_out, const ts_cmd_t *p_cmd )
395 es_out_sys_t *p_sys = p_out->p_sys;
396 ts_cmd_t *p_dup = malloc( sizeof(*p_dup) );
401 TAB_APPEND( p_sys->i_cmd, p_sys->pp_cmd, p_dup );
404 static int CmdPop( es_out_t *p_out, ts_cmd_t *p_cmd )
406 es_out_sys_t *p_sys = p_out->p_sys;
408 if( p_sys->i_cmd <= 0 )
411 *p_cmd = *p_sys->pp_cmd[0];
413 free( p_sys->pp_cmd[0] );
414 TAB_REMOVE( p_sys->i_cmd, p_sys->pp_cmd, p_sys->pp_cmd[0] );
418 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
420 p_cmd->i_type = C_ADD;
421 p_cmd->add.p_es = p_es;
424 p_cmd->add.p_fmt = malloc( sizeof(*p_fmt) );
425 if( !p_cmd->add.p_fmt )
427 es_format_Copy( p_cmd->add.p_fmt, p_fmt );
431 p_cmd->add.p_fmt = (es_format_t*)p_fmt;
435 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
437 es_out_sys_t *p_sys = p_out->p_sys;
439 p_cmd->add.p_es->p_es = es_out_Add( p_sys->p_out, p_cmd->add.p_fmt );
441 static void CmdCleanAdd( ts_cmd_t *p_cmd )
443 es_format_Clean( p_cmd->add.p_fmt );
444 free( p_cmd->add.p_fmt );
447 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
449 p_cmd->i_type = C_SEND;
450 p_cmd->send.p_es = p_es;
451 p_cmd->send.p_block = p_block;
453 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
455 es_out_sys_t *p_sys = p_out->p_sys;
456 block_t *p_block = p_cmd->send.p_block;
458 p_cmd->send.p_block = NULL;
462 if( p_cmd->send.p_es->p_es )
463 return es_out_Send( p_sys->p_out, p_cmd->send.p_es->p_es, p_block );
464 block_Release( p_block );
468 static void CmdCleanSend( ts_cmd_t *p_cmd )
470 if( p_cmd->send.p_block )
471 block_Release( p_cmd->send.p_block );
474 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
476 p_cmd->i_type = C_DEL;
477 p_cmd->del.p_es = p_es;
480 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
482 es_out_sys_t *p_sys = p_out->p_sys;
484 if( p_cmd->del.p_es->p_es )
485 es_out_Del( p_sys->p_out, p_cmd->del.p_es->p_es );
486 free( p_cmd->del.p_es );
489 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
491 p_cmd->i_type = C_CONTROL;
492 p_cmd->control.i_query = i_query;
496 /* Pass-through control */
497 case ES_OUT_SET_ACTIVE: /* arg1= bool */
498 p_cmd->control.b_bool = (bool)va_arg( args, int );
501 case ES_OUT_GET_ACTIVE: /* arg1= bool* */
502 p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
505 case ES_OUT_SET_MODE: /* arg1= int */
506 case ES_OUT_SET_GROUP: /* arg1= int */
507 case ES_OUT_DEL_GROUP: /* arg1=int i_group */
508 p_cmd->control.i_int = (int)va_arg( args, int );
511 case ES_OUT_GET_MODE: /* arg2= int* */
512 case ES_OUT_GET_GROUP: /* arg1= int* */
513 p_cmd->control.pi_int = (int*)va_arg( args, int * );
516 case ES_OUT_SET_PCR: /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
517 case ES_OUT_SET_NEXT_DISPLAY_TIME: /* arg1=int64_t i_pts(microsecond) */
518 p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
521 case ES_OUT_SET_GROUP_PCR: /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
522 p_cmd->control.i_int = (int)va_arg( args, int );
523 p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
526 case ES_OUT_RESET_PCR: /* no arg */
529 case ES_OUT_SET_GROUP_META: /* arg1=int i_group arg2=vlc_meta_t* */
531 p_cmd->control.i_int = (int)va_arg( args, int );
532 vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * );
536 p_cmd->control.p_meta = vlc_meta_New();
537 if( !p_cmd->control.p_meta )
539 vlc_meta_Merge( p_cmd->control.p_meta, p_meta );
543 p_cmd->control.p_meta = p_meta;
548 case ES_OUT_SET_GROUP_EPG: /* arg1=int i_group arg2=vlc_epg_t* */
550 p_cmd->control.i_int = (int)va_arg( args, int );
551 vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * );
555 p_cmd->control.p_epg = vlc_epg_New( p_epg->psz_name );
556 if( !p_cmd->control.p_epg )
558 for( int i = 0; i < p_epg->i_event; i++ )
560 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
562 vlc_epg_AddEvent( p_cmd->control.p_epg,
563 p_evt->i_start, p_evt->i_duration,
565 p_evt->psz_short_description, p_evt->psz_description );
567 vlc_epg_SetCurrent( p_cmd->control.p_epg,
568 p_epg->p_current ? p_epg->p_current->i_start : -1 );
572 p_cmd->control.p_epg = p_epg;
577 /* Modified control */
578 case ES_OUT_SET_ES: /* arg1= es_out_id_t* */
579 case ES_OUT_RESTART_ES: /* arg1= es_out_id_t* */
580 case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t* */
581 p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
584 case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool */
585 p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
586 p_cmd->control.b_bool = (bool)va_arg( args, int );
589 case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool* */
590 p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
591 p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
594 case ES_OUT_SET_ES_FMT: /* arg1= es_out_id_t* arg2=es_format_t* */
596 p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
597 es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
601 p_cmd->control.p_fmt = malloc( sizeof(*p_fmt) );
602 if( !p_cmd->control.p_fmt )
604 es_format_Copy( p_cmd->control.p_fmt, p_fmt );
608 p_cmd->control.p_fmt = p_fmt;
620 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
622 es_out_sys_t *p_sys = p_out->p_sys;
623 const int i_query = p_cmd->control.i_query;
627 /* Pass-through control */
628 case ES_OUT_SET_ACTIVE: /* arg1= bool */
629 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.b_bool );
631 case ES_OUT_GET_ACTIVE: /* arg1= bool* */
632 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pb_bool );
634 case ES_OUT_SET_MODE: /* arg1= int */
635 case ES_OUT_SET_GROUP: /* arg1= int */
636 case ES_OUT_DEL_GROUP: /* arg1=int i_group */
637 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int );
639 case ES_OUT_GET_MODE: /* arg2= int* */
640 case ES_OUT_GET_GROUP: /* arg1= int* */
641 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pi_int );
643 case ES_OUT_SET_PCR: /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
644 case ES_OUT_SET_NEXT_DISPLAY_TIME: /* arg1=int64_t i_pts(microsecond) */
645 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_i64 );
647 case ES_OUT_SET_GROUP_PCR: /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
648 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.i_i64 );
650 case ES_OUT_RESET_PCR: /* no arg */
651 return es_out_Control( p_sys->p_out, i_query );
653 case ES_OUT_SET_GROUP_META: /* arg1=int i_group arg2=vlc_meta_t* */
654 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_meta );
656 case ES_OUT_SET_GROUP_EPG: /* arg1=int i_group arg2=vlc_epg_t* */
657 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_epg );
659 /* Modified control */
660 case ES_OUT_SET_ES: /* arg1= es_out_id_t* */
661 case ES_OUT_RESTART_ES: /* arg1= es_out_id_t* */
662 case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t* */
663 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es );
665 case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool */
666 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.b_bool );
668 case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool* */
669 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.pb_bool );
671 case ES_OUT_SET_ES_FMT: /* arg1= es_out_id_t* arg2=es_format_t* */
672 return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.p_fmt );
676 msg_Err( p_sys->p_input, "Unknown es_out_Control query in CmdExecuteControl!" );
680 static void CmdCleanControl( ts_cmd_t *p_cmd )
682 if( p_cmd->control.p_meta )
683 vlc_meta_Delete( p_cmd->control.p_meta );
684 if( p_cmd->control.p_epg )
685 vlc_epg_Delete( p_cmd->control.p_epg );
686 if( p_cmd->control.p_fmt )
688 es_format_Clean( p_cmd->control.p_fmt );
689 free( p_cmd->control.p_fmt );
694 /*****************************************************************************
696 *****************************************************************************/
697 static char *GetTmpPath( char *psz_path )
699 if( psz_path && *psz_path )
701 /* Make sure that the path exists and is a directory */
703 const int i_ret = utf8_stat( psz_path, &s );
705 if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
707 else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
712 /* Create a suitable path */
713 #if defined (WIN32) && !defined (UNDER_CE)
714 const DWORD dwCount = GetTempPathW( 0, NULL );
715 wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
718 if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
722 psw_path = _wgetcwd( NULL, 0 );
729 psz_path = FromWide( psw_path );
730 while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
731 psz_path[strlen( psz_path ) - 1] = '\0';
736 if( !psz_path || *psz_path == '\0' )
739 return strdup( "C:" );
742 psz_path = strdup( "/tmp" );
748 static FILE *GetTmpFile( const char *psz_path )
755 if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
759 fd = mkstemp( psz_name );
766 f = fdopen( fd, "rw+" );