]> git.sesse.net Git - vlc/blob - src/input/es_out_timeshift.c
Hide es out timeshift delay from time display.
[vlc] / src / input / es_out_timeshift.c
1 /*****************************************************************************
2  * es_out_timeshift.c: Es Out timeshift.
3  *****************************************************************************
4  * Copyright (C) 2008 Laurent Aimar
5  * $Id$
6  *
7  * Authors: Laurent Aimar < fenrir _AT_ videolan _DOT_ org>
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <errno.h>
34 #include <assert.h>
35 #if defined (WIN32) && !defined (UNDER_CE)
36 #  include <direct.h>
37 #endif
38 #ifdef HAVE_SYS_STAT_H
39 #   include <sys/stat.h>
40 #endif
41
42 #include <vlc_common.h>
43 #include <vlc_charset.h>
44
45 #include <vlc_input.h>
46 #include <vlc_es_out.h>
47 #include <vlc_block.h>
48 #include "input_internal.h"
49 #include "es_out.h"
50 #include "es_out_timeshift.h"
51
52 /*****************************************************************************
53  * Local prototypes
54  *****************************************************************************/
55
56 /* XXX attribute_packed is (and MUST be) used ONLY to reduce memory usage */
57 #ifdef HAVE_ATTRIBUTE_PACKED
58 #   define attribute_packed __attribute__((__packed__))
59 #else
60 #   define attribute_packed
61 #endif
62
63 enum
64 {
65     C_ADD,
66     C_SEND,
67     C_DEL,
68     C_CONTROL,
69 };
70
71 typedef struct attribute_packed
72 {
73     es_out_id_t *p_es;
74     es_format_t *p_fmt;
75 } ts_cmd_add_t;
76
77 typedef struct attribute_packed
78 {
79     es_out_id_t *p_es;
80 } ts_cmd_del_t;
81
82 typedef struct attribute_packed
83 {
84     es_out_id_t *p_es;
85     block_t *p_block;
86     int     i_offset;  /* We do not use file > INT_MAX */
87 } ts_cmd_send_t;
88
89 typedef struct attribute_packed
90 {
91     int  i_query;
92
93     union
94     {
95         bool b_bool;
96         int  i_int;
97         int64_t i_i64;
98         es_out_id_t *p_es;
99         struct
100         {
101             int     i_int;
102             int64_t i_i64;
103         } int_i64;
104         struct
105         {
106             int        i_int;
107             vlc_meta_t *p_meta;
108         } int_meta;
109         struct
110         {
111             int       i_int;
112             vlc_epg_t *p_epg;
113         } int_epg;
114         struct
115         {
116             es_out_id_t *p_es;
117             bool        b_bool;
118         } es_bool;
119         struct
120         {
121             es_out_id_t *p_es;
122             es_format_t *p_fmt;
123         } es_fmt;
124     };
125 } ts_cmd_control_t;
126
127 typedef struct attribute_packed
128 {
129     int8_t  i_type;
130     mtime_t i_date;
131     union
132     {
133         ts_cmd_add_t     add;
134         ts_cmd_del_t     del;
135         ts_cmd_send_t    send;
136         ts_cmd_control_t control;
137     };
138 } ts_cmd_t;
139
140 typedef struct ts_storage_t ts_storage_t;
141 struct ts_storage_t
142 {
143     ts_storage_t *p_next;
144
145     /* */
146     char    *psz_file;  /* Filename */
147     size_t  i_file_max; /* Max size in bytes */
148     int64_t i_file_size;/* Current size in bytes */
149     FILE    *p_filew;   /* FILE handle for data writing */
150     FILE    *p_filer;   /* FILE handle for data reading */
151
152     /* */
153     int      i_cmd_r;
154     int      i_cmd_w;
155     int      i_cmd_max;
156     ts_cmd_t *p_cmd;
157 };
158
159 typedef struct
160 {
161     VLC_COMMON_MEMBERS
162
163     /* */
164     input_thread_t *p_input;
165     es_out_t       *p_out;
166     int64_t        i_tmp_size_max;
167     const char     *psz_tmp_path;
168
169     /* Lock for all following fields */
170     vlc_mutex_t    lock;
171     vlc_cond_t     wait;
172
173     /* */
174     bool           b_paused;
175     mtime_t        i_pause_date;
176
177     /* */
178     int            i_rate;
179     int            i_rate_source;
180     mtime_t        i_rate_date;
181     mtime_t        i_rate_delay;
182
183     /* */
184     mtime_t        i_buffering_delay;
185
186     /* */
187     ts_storage_t   *p_storage_r;
188     ts_storage_t   *p_storage_w;
189
190     mtime_t        i_cmd_delay;
191
192 } ts_thread_t;
193
194 struct es_out_id_t
195 {
196     es_out_id_t *p_es;
197 };
198
199 struct es_out_sys_t
200 {
201     input_thread_t *p_input;
202         es_out_t       *p_out;
203
204     /* Configuration */
205     int64_t        i_tmp_size_max;    /* Maximal temporary file size in byte */
206     char           *psz_tmp_path;     /* Path for temporary files */
207
208     /* Lock for all following fields */
209     vlc_mutex_t    lock;
210
211     /* */
212     bool           b_delayed;
213     ts_thread_t   *p_thread;
214
215     /* */
216     bool           b_input_paused;
217     bool           b_input_paused_source;
218     int            i_input_rate;
219     int            i_input_rate_source;
220
221     /* */
222     int            i_es;
223     es_out_id_t    **pp_es;
224 };
225
226 static es_out_id_t *Add    ( es_out_t *, const es_format_t * );
227 static int          Send   ( es_out_t *, es_out_id_t *, block_t * );
228 static void         Del    ( es_out_t *, es_out_id_t * );
229 static int          Control( es_out_t *, int i_query, va_list );
230 static void         Destroy( es_out_t * );
231
232 static int          TsStart( es_out_t * );
233 static void         TsAutoStop( es_out_t * );
234
235 static void         TsStop( ts_thread_t * );
236 static void         TsPushCmd( ts_thread_t *, ts_cmd_t * );
237 static int          TsPopCmdLocked( ts_thread_t *, ts_cmd_t *, bool b_flush );
238 static bool         TsHasCmd( ts_thread_t * );
239 static bool         TsIsUnused( ts_thread_t * );
240 static int          TsChangePause( ts_thread_t *, bool b_source_paused, bool b_paused, mtime_t i_date );
241 static int          TsChangeRate( ts_thread_t *, int i_src_rate, int i_rate );
242 static mtime_t      TsGetDelay( ts_thread_t * );
243
244 static void         *TsRun( vlc_object_t * );
245
246 static ts_storage_t *TsStorageNew( const char *psz_path, int64_t i_tmp_size_max );
247 static void         TsStorageDelete( ts_storage_t * );
248 static void         TsStoragePack( ts_storage_t *p_storage );
249 static bool         TsStorageIsFull( ts_storage_t *, const ts_cmd_t *p_cmd );
250 static bool         TsStorageIsEmpty( ts_storage_t * );
251 static void         TsStoragePushCmd( ts_storage_t *, const ts_cmd_t *p_cmd, bool b_flush );
252 static void         TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush );
253
254 static void CmdClean( ts_cmd_t * );
255 static void cmd_cleanup_routine( void *p ) { CmdClean( p ); }
256
257 static int  CmdInitAdd    ( ts_cmd_t *, es_out_id_t *, const es_format_t *, bool b_copy );
258 static void CmdInitSend   ( ts_cmd_t *, es_out_id_t *, block_t * );
259 static int  CmdInitDel    ( ts_cmd_t *, es_out_id_t * );
260 static int  CmdInitControl( ts_cmd_t *, int i_query, va_list, bool b_copy );
261
262 /* */
263 static void CmdCleanAdd    ( ts_cmd_t * );
264 static void CmdCleanSend   ( ts_cmd_t * );
265 static void CmdCleanControl( ts_cmd_t *p_cmd );
266
267 /* XXX these functions will take the destination es_out_t */
268 static void CmdExecuteAdd    ( es_out_t *, ts_cmd_t * );
269 static int  CmdExecuteSend   ( es_out_t *, ts_cmd_t * );
270 static void CmdExecuteDel    ( es_out_t *, ts_cmd_t * );
271 static int  CmdExecuteControl( es_out_t *, ts_cmd_t * );
272
273 /* File helpers */
274 static char *GetTmpPath( char *psz_path );
275 static FILE *GetTmpFile( char **ppsz_file, const char *psz_path );
276
277 /*****************************************************************************
278  * input_EsOutTimeshiftNew:
279  *****************************************************************************/
280 es_out_t *input_EsOutTimeshiftNew( input_thread_t *p_input, es_out_t *p_next_out, int i_rate )
281 {
282     es_out_t *p_out = malloc( sizeof(*p_out) );
283     if( !p_out )
284         return NULL;
285
286     es_out_sys_t *p_sys = malloc( sizeof(*p_sys) );
287     if( !p_sys )
288     {
289         free( p_out );
290         return NULL;
291     }
292
293     /* */
294     p_out->pf_add     = Add;
295     p_out->pf_send    = Send;
296     p_out->pf_del     = Del;
297     p_out->pf_control = Control;
298     p_out->pf_destroy = Destroy;
299     p_out->p_sys      = p_sys;
300     p_out->b_sout     = p_input->p->p_sout != NULL;
301
302     /* */
303     p_sys->b_input_paused = false;
304     p_sys->b_input_paused_source = false;
305     p_sys->p_input = p_input;
306     p_sys->i_input_rate = i_rate;
307     p_sys->i_input_rate_source = i_rate;
308
309     p_sys->p_out = p_next_out;
310     vlc_mutex_init_recursive( &p_sys->lock );
311
312     p_sys->b_delayed = false;
313     p_sys->p_thread = NULL;
314
315     TAB_INIT( p_sys->i_es, p_sys->pp_es );
316
317     /* */
318     const int i_tmp_size_max = var_CreateGetInteger( p_input, "input-timeshift-granularity" );
319     if( i_tmp_size_max < 0 )
320         p_sys->i_tmp_size_max = 50*1024*1024;
321     else
322         p_sys->i_tmp_size_max = __MAX( i_tmp_size_max, 1*1024*1024 );
323     msg_Dbg( p_input, "using timeshift granularity of %d bytes",
324              (int)p_sys->i_tmp_size_max );
325
326     char *psz_tmp_path = var_CreateGetNonEmptyString( p_input, "input-timeshift-path" );
327     p_sys->psz_tmp_path = GetTmpPath( psz_tmp_path );
328     msg_Dbg( p_input, "using timeshift  path '%s'", p_sys->psz_tmp_path );
329
330 #if 0
331 #define S(t) msg_Err( p_input, "SIZEOF("#t")=%d", sizeof(t) )
332     S(ts_cmd_t);
333     S(ts_cmd_control_t);
334     S(ts_cmd_send_t);
335     S(ts_cmd_del_t);
336     S(ts_cmd_add_t);
337 #undef S
338 #endif
339
340     return p_out;
341 }
342
343 /*****************************************************************************
344  * Internal functions
345  *****************************************************************************/
346 static void Destroy( es_out_t *p_out )
347 {
348     es_out_sys_t *p_sys = p_out->p_sys;
349
350     if( p_sys->b_delayed )
351     {
352         TsStop( p_sys->p_thread );
353         p_sys->b_delayed = false;
354     }
355
356     while( p_sys->i_es > 0 )
357         Del( p_out, p_sys->pp_es[0] );
358     TAB_CLEAN( p_sys->i_es, p_sys->pp_es  );
359
360     free( p_sys->psz_tmp_path );
361     vlc_mutex_destroy( &p_sys->lock );
362     free( p_sys );
363     free( p_out );
364 }
365
366 static es_out_id_t *Add( es_out_t *p_out, const es_format_t *p_fmt )
367 {
368     es_out_sys_t *p_sys = p_out->p_sys;
369     ts_cmd_t cmd;
370
371     es_out_id_t *p_es = malloc( sizeof( *p_es ) );
372     if( !p_es )
373         return NULL;
374
375     vlc_mutex_lock( &p_sys->lock );
376
377     TsAutoStop( p_out );
378
379     if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
380     {
381         vlc_mutex_unlock( &p_sys->lock );
382         free( p_es );
383         return NULL;
384     }
385
386     TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );
387
388     if( p_sys->b_delayed )
389         TsPushCmd( p_sys->p_thread, &cmd );
390     else
391         CmdExecuteAdd( p_sys->p_out, &cmd );
392
393     vlc_mutex_unlock( &p_sys->lock );
394
395     return p_es;
396 }
397 static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
398 {
399     es_out_sys_t *p_sys = p_out->p_sys;
400     ts_cmd_t cmd;
401     int i_ret = VLC_SUCCESS;
402
403     vlc_mutex_lock( &p_sys->lock );
404
405     TsAutoStop( p_out );
406
407     CmdInitSend( &cmd, p_es, p_block );
408     if( p_sys->b_delayed )
409         TsPushCmd( p_sys->p_thread, &cmd );
410     else
411         i_ret = CmdExecuteSend( p_sys->p_out, &cmd) ;
412
413     vlc_mutex_unlock( &p_sys->lock );
414
415     return i_ret;
416 }
417 static void Del( es_out_t *p_out, es_out_id_t *p_es )
418 {
419     es_out_sys_t *p_sys = p_out->p_sys;
420     ts_cmd_t cmd;
421
422     vlc_mutex_lock( &p_sys->lock );
423
424     TsAutoStop( p_out );
425
426     CmdInitDel( &cmd, p_es );
427     if( p_sys->b_delayed )
428         TsPushCmd( p_sys->p_thread, &cmd );
429     else
430         CmdExecuteDel( p_sys->p_out, &cmd );
431
432     TAB_REMOVE( p_sys->i_es, p_sys->pp_es, p_es );
433
434     vlc_mutex_unlock( &p_sys->lock );
435 }
436
437 static int ControlLockedGetEmpty( es_out_t *p_out, bool *pb_empty )
438 {
439     es_out_sys_t *p_sys = p_out->p_sys;
440
441     if( p_sys->b_delayed && TsHasCmd( p_sys->p_thread ) )
442         *pb_empty = false;
443     else
444         *pb_empty = es_out_GetEmpty( p_sys->p_out );
445
446     return VLC_SUCCESS;
447 }
448 static int ControlLockedGetWakeup( es_out_t *p_out, mtime_t *pi_wakeup )
449 {
450     es_out_sys_t *p_sys = p_out->p_sys;
451
452     if( p_sys->b_delayed )
453     {
454         assert( !p_sys->p_input->b_can_pace_control );
455         *pi_wakeup = 0;
456     }
457     else
458     {
459         *pi_wakeup = es_out_GetWakeup( p_sys->p_out );
460     }
461
462     return VLC_SUCCESS;
463 }
464 static int ControlLockedGetBuffering( es_out_t *p_out, bool *pb_buffering, mtime_t *pi_delay )
465 {
466     es_out_sys_t *p_sys = p_out->p_sys;
467
468     if( p_sys->b_delayed )
469     {
470         mtime_t i_delay;
471         es_out_GetBuffering( p_sys->p_out, &i_delay );
472
473         *pb_buffering = true;
474         *pi_delay = i_delay + TsGetDelay( p_sys->p_thread );
475     }
476     else
477     {
478         *pb_buffering = es_out_GetBuffering( p_sys->p_out, pi_delay );
479     }
480
481     return VLC_SUCCESS;
482 }
483 static int ControlLockedSetPauseState( es_out_t *p_out, bool b_source_paused, bool b_paused, mtime_t i_date )
484 {
485     es_out_sys_t *p_sys = p_out->p_sys;
486     int i_ret;
487
488     if( !p_sys->b_delayed && !b_source_paused == !b_paused )
489     {
490         i_ret = es_out_SetPauseState( p_sys->p_out, b_source_paused, b_paused, i_date );
491     }
492     else
493     {
494         i_ret = VLC_EGENERIC;
495         if( !p_sys->p_input->b_can_pace_control )
496         {
497             if( !p_sys->b_delayed )
498                 TsStart( p_out );
499             if( p_sys->b_delayed )
500                 i_ret = TsChangePause( p_sys->p_thread, b_source_paused, b_paused, i_date );
501         }
502         else
503         {
504             /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
505              * and so be able to advertize correctly pace control property in access
506              * module */
507             msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
508         }
509     }
510
511     if( !i_ret )
512     {
513         p_sys->b_input_paused_source = b_source_paused;
514         p_sys->b_input_paused = b_paused;
515     }
516     return i_ret;
517 }
518 static int ControlLockedSetRate( es_out_t *p_out, int i_src_rate, int i_rate )
519 {
520     es_out_sys_t *p_sys = p_out->p_sys;
521     int i_ret;
522
523     if( !p_sys->b_delayed && i_src_rate == i_rate )
524     {
525         i_ret = es_out_SetRate( p_sys->p_out, i_src_rate, i_rate );
526     }
527     else
528     {
529         i_ret = VLC_EGENERIC;
530         if( !p_sys->p_input->b_can_pace_control )
531         {
532             if( !p_sys->b_delayed )
533                 TsStart( p_out );
534             if( p_sys->b_delayed )
535                 i_ret = TsChangeRate( p_sys->p_thread, i_src_rate, i_rate );
536         }
537         else
538         {
539             /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
540              * and so be able to advertize correctly pace control property in access
541              * module */
542             msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
543         }
544
545     }
546
547     if( !i_ret )
548     {
549         p_sys->i_input_rate_source = i_src_rate;
550         p_sys->i_input_rate = i_rate;
551     }
552     return i_ret;
553 }
554 static int ControlLockedSetTime( es_out_t *p_out, mtime_t i_date )
555 {
556     es_out_sys_t *p_sys = p_out->p_sys;
557
558     if( !p_sys->b_delayed )
559         return es_out_SetTime( p_sys->p_out, i_date );
560
561     /* TODO */
562     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support time change" );
563     return VLC_EGENERIC;
564 }
565 static int ControlLockedSetFrameNext( es_out_t *p_out )
566 {
567     es_out_sys_t *p_sys = p_out->p_sys;
568
569     return es_out_SetFrameNext( p_sys->p_out );
570 }
571
572 static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
573 {
574     es_out_sys_t *p_sys = p_out->p_sys;
575
576     switch( i_query )
577     {
578     /* Invalid query for this es_out level */
579     case ES_OUT_SET_ES_BY_ID:
580     case ES_OUT_RESTART_ES_BY_ID:
581     case ES_OUT_SET_ES_DEFAULT_BY_ID:
582     case ES_OUT_SET_DELAY:
583     case ES_OUT_SET_RECORD_STATE:
584         assert(0);
585         return VLC_EGENERIC;
586
587     /* TODO ? or to remove ? */
588     case ES_OUT_GET_TS:
589         return VLC_EGENERIC;
590
591     /* Pass-through control */
592     case ES_OUT_SET_ACTIVE:
593     case ES_OUT_SET_MODE:
594     case ES_OUT_SET_GROUP:
595     case ES_OUT_SET_PCR:
596     case ES_OUT_SET_GROUP_PCR:
597     case ES_OUT_RESET_PCR:
598     case ES_OUT_SET_NEXT_DISPLAY_TIME:
599     case ES_OUT_SET_GROUP_META:
600     case ES_OUT_SET_GROUP_EPG:
601     case ES_OUT_DEL_GROUP:
602     case ES_OUT_SET_ES:
603     case ES_OUT_RESTART_ES:
604     case ES_OUT_SET_ES_DEFAULT:
605     case ES_OUT_SET_ES_STATE:
606     case ES_OUT_SET_ES_FMT:
607     {
608         ts_cmd_t cmd;
609         if( CmdInitControl( &cmd, i_query, args, p_sys->b_delayed ) )
610             return VLC_EGENERIC;
611         if( p_sys->b_delayed )
612         {
613             TsPushCmd( p_sys->p_thread, &cmd );
614             return VLC_SUCCESS;
615         }
616         return CmdExecuteControl( p_sys->p_out, &cmd );
617     }
618
619     /* Special control when delayed */
620     case ES_OUT_GET_ES_STATE:
621     {
622         es_out_id_t *p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
623         bool *pb_enabled = (bool*)va_arg( args, bool* );
624
625         if( p_sys->b_delayed )
626         {
627             *pb_enabled = true;
628             return VLC_SUCCESS;
629         }
630         return es_out_Control( p_sys->p_out, ES_OUT_GET_ES_STATE, p_es->p_es, pb_enabled );
631     }
632
633     /* Special internal input control */
634     case ES_OUT_GET_EMPTY:
635     {
636         bool *pb_empty = (bool*)va_arg( args, bool* );
637         return ControlLockedGetEmpty( p_out, pb_empty );
638     }
639     case ES_OUT_GET_WAKE_UP: /* TODO ? */
640     {
641         mtime_t *pi_wakeup = (mtime_t*)va_arg( args, mtime_t* );
642         return ControlLockedGetWakeup( p_out, pi_wakeup );
643     }
644     case ES_OUT_GET_BUFFERING:
645     {
646         bool *pb_buffering = (bool *)va_arg( args, bool* );
647         mtime_t *pi_delay = (mtime_t*)va_arg( args, mtime_t* );
648         return ControlLockedGetBuffering( p_out, pb_buffering, pi_delay );
649     }
650     case ES_OUT_SET_PAUSE_STATE:
651     {
652         const bool b_source_paused = (bool)va_arg( args, int );
653         const bool b_paused = (bool)va_arg( args, int );
654         const mtime_t i_date = (mtime_t) va_arg( args, mtime_t );
655
656         return ControlLockedSetPauseState( p_out, b_source_paused, b_paused, i_date );
657     }
658     case ES_OUT_SET_RATE:
659     {
660         const int i_src_rate = (int)va_arg( args, int );
661         const int i_rate = (int)va_arg( args, int );
662
663         return ControlLockedSetRate( p_out, i_src_rate, i_rate );
664     }
665     case ES_OUT_SET_TIME:
666     {
667         const mtime_t i_date = (mtime_t)va_arg( args, mtime_t );
668
669         return ControlLockedSetTime( p_out, i_date );
670     }
671     case ES_OUT_SET_FRAME_NEXT:
672     {
673         return ControlLockedSetFrameNext( p_out );
674     }
675
676     default:
677         msg_Err( p_sys->p_input, "Unknown es_out_Control query !" );
678         assert(0);
679         return VLC_EGENERIC;
680     }
681 }
682 static int Control( es_out_t *p_out, int i_query, va_list args )
683 {
684     es_out_sys_t *p_sys = p_out->p_sys;
685     int i_ret;
686
687     vlc_mutex_lock( &p_sys->lock );
688
689     TsAutoStop( p_out );
690
691     i_ret = ControlLocked( p_out, i_query, args );
692
693     vlc_mutex_unlock( &p_sys->lock );
694
695     return i_ret;
696 }
697
698 /*****************************************************************************
699  *
700  *****************************************************************************/
701 static void TsDestructor( vlc_object_t *p_this )
702 {
703     ts_thread_t *p_ts = (ts_thread_t*)p_this;
704
705     vlc_cond_destroy( &p_ts->wait );
706     vlc_mutex_destroy( &p_ts->lock );
707 }
708 static int TsStart( es_out_t *p_out )
709 {
710     es_out_sys_t *p_sys = p_out->p_sys;
711     ts_thread_t *p_ts;
712
713     assert( !p_sys->b_delayed );
714
715     p_sys->p_thread = p_ts = vlc_custom_create( p_sys->p_input, sizeof(ts_thread_t),
716                                                 VLC_OBJECT_GENERIC, "es out timeshift" );
717     if( !p_ts )
718         return VLC_EGENERIC;
719
720     p_ts->i_tmp_size_max = p_sys->i_tmp_size_max;
721     p_ts->psz_tmp_path = p_sys->psz_tmp_path;
722     p_ts->p_input = p_sys->p_input;
723     p_ts->p_out = p_sys->p_out;
724     vlc_mutex_init( &p_ts->lock );
725     vlc_cond_init( &p_ts->wait );
726     p_ts->b_paused = p_sys->b_input_paused && !p_sys->b_input_paused_source;
727     p_ts->i_pause_date = p_ts->b_paused ? mdate() : -1;
728     p_ts->i_rate_source = p_sys->i_input_rate_source;
729     p_ts->i_rate        = p_sys->i_input_rate;
730     p_ts->i_rate_date = -1;
731     p_ts->i_rate_delay = 0;
732     p_ts->i_buffering_delay = 0;
733     p_ts->i_cmd_delay = 0;
734     p_ts->p_storage_r = NULL;
735     p_ts->p_storage_w = NULL;
736
737     vlc_object_set_destructor( p_ts, TsDestructor );
738
739     p_sys->b_delayed = true;
740     if( vlc_thread_create( p_ts, "es out timeshift",
741                            TsRun, VLC_THREAD_PRIORITY_INPUT, false ) )
742     {
743         msg_Err( p_sys->p_input, "cannot create input thread" );
744
745         vlc_object_release( p_ts );
746
747         p_sys->b_delayed = false;
748         return VLC_EGENERIC;
749     }
750
751     return VLC_SUCCESS;
752 }
753 static void TsAutoStop( es_out_t *p_out )
754 {
755     es_out_sys_t *p_sys = p_out->p_sys;
756
757     if( !p_sys->b_delayed || !TsIsUnused( p_sys->p_thread ) )
758         return;
759
760     msg_Warn( p_sys->p_input, "es out timeshift: auto stop" );
761     TsStop( p_sys->p_thread );
762
763     p_sys->b_delayed = false;
764 }
765 static void TsStop( ts_thread_t *p_ts )
766 {
767     vlc_object_kill( p_ts );
768     vlc_thread_join( p_ts );
769
770     vlc_mutex_lock( &p_ts->lock );
771     for( ;; )
772     {
773         ts_cmd_t cmd;
774
775         if( TsPopCmdLocked( p_ts, &cmd, true ) )
776             break;
777
778         CmdClean( &cmd );
779     }
780     assert( !p_ts->p_storage_r || !p_ts->p_storage_r->p_next );
781     if( p_ts->p_storage_r )
782         TsStorageDelete( p_ts->p_storage_r );
783     vlc_mutex_unlock( &p_ts->lock );
784
785     vlc_object_release( p_ts );
786 }
787 static void TsPushCmd( ts_thread_t *p_ts, ts_cmd_t *p_cmd )
788 {
789     vlc_mutex_lock( &p_ts->lock );
790
791     if( !p_ts->p_storage_w || TsStorageIsFull( p_ts->p_storage_w, p_cmd ) )
792     {
793         ts_storage_t *p_storage = TsStorageNew( p_ts->psz_tmp_path, p_ts->i_tmp_size_max );
794
795         if( !p_storage )
796         {
797             CmdClean( p_cmd );
798             vlc_mutex_unlock( &p_ts->lock );
799             /* TODO warn the user (but only once) */
800             return;
801         }
802
803         if( !p_ts->p_storage_w )
804         {
805             p_ts->p_storage_r = p_ts->p_storage_w = p_storage;
806         }
807         else
808         {
809             TsStoragePack( p_ts->p_storage_w );
810             p_ts->p_storage_w->p_next = p_storage;
811             p_ts->p_storage_w = p_storage;
812         }
813     }
814
815     /* TODO return error and warn the user (but only once) */
816     TsStoragePushCmd( p_ts->p_storage_w, p_cmd, p_ts->p_storage_r == p_ts->p_storage_w );
817
818     vlc_cond_signal( &p_ts->wait );
819
820     vlc_mutex_unlock( &p_ts->lock );
821 }
822 static int TsPopCmdLocked( ts_thread_t *p_ts, ts_cmd_t *p_cmd, bool b_flush )
823 {
824     vlc_assert_locked( &p_ts->lock );
825
826     if( TsStorageIsEmpty( p_ts->p_storage_r ) )
827         return VLC_EGENERIC;
828
829     TsStoragePopCmd( p_ts->p_storage_r, p_cmd, b_flush );
830
831     while( p_ts->p_storage_r && TsStorageIsEmpty( p_ts->p_storage_r ) )
832     {
833         ts_storage_t *p_next = p_ts->p_storage_r->p_next;
834         if( !p_next )
835             break;
836
837         TsStorageDelete( p_ts->p_storage_r );
838         p_ts->p_storage_r = p_next;
839     }
840
841     return VLC_SUCCESS;
842 }
843 static bool TsHasCmd( ts_thread_t *p_ts )
844 {
845     bool b_cmd;
846
847     vlc_mutex_lock( &p_ts->lock );
848     b_cmd =  TsStorageIsEmpty( p_ts->p_storage_r );
849     vlc_mutex_unlock( &p_ts->lock );
850
851     return b_cmd;
852 }
853 static bool TsIsUnused( ts_thread_t *p_ts )
854 {
855     bool b_unused;
856
857     vlc_mutex_lock( &p_ts->lock );
858     b_unused = !p_ts->b_paused &&
859                p_ts->i_rate == p_ts->i_rate_source &&
860                TsStorageIsEmpty( p_ts->p_storage_r );
861     vlc_mutex_unlock( &p_ts->lock );
862
863     return b_unused;
864 }
865 static int TsChangePause( ts_thread_t *p_ts, bool b_source_paused, bool b_paused, mtime_t i_date )
866 {
867     vlc_mutex_lock( &p_ts->lock );
868
869     int i_ret;
870     if( b_paused )
871     {
872         assert( !b_source_paused );
873         i_ret = es_out_SetPauseState( p_ts->p_out, true, true, i_date );
874     }
875     else
876     {
877         i_ret = es_out_SetPauseState( p_ts->p_out, false, false, i_date );
878     }
879
880     if( !i_ret )
881     {
882         if( !b_paused )
883         {
884             assert( p_ts->i_pause_date > 0 );
885
886             p_ts->i_cmd_delay += i_date - p_ts->i_pause_date;
887         }
888
889         p_ts->b_paused = b_paused;
890         p_ts->i_pause_date = i_date;
891
892         vlc_cond_signal( &p_ts->wait );
893     }
894     vlc_mutex_unlock( &p_ts->lock );
895     return i_ret;
896 }
897 static int TsChangeRate( ts_thread_t *p_ts, int i_src_rate, int i_rate )
898 {
899     int i_ret;
900
901     vlc_mutex_lock( &p_ts->lock );
902     p_ts->i_cmd_delay += p_ts->i_rate_delay;
903
904     p_ts->i_rate_date = -1;
905     p_ts->i_rate_delay = 0;
906     p_ts->i_rate = i_rate;
907     p_ts->i_rate_source = i_src_rate;
908
909     i_ret = es_out_SetRate( p_ts->p_out, i_rate, i_rate );
910     vlc_mutex_unlock( &p_ts->lock );
911
912     return i_ret;
913 }
914 static mtime_t TsGetDelay( ts_thread_t *p_ts )
915 {
916     mtime_t i_delay;
917
918     vlc_mutex_lock( &p_ts->lock );
919     i_delay = p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
920     if( p_ts->b_paused )
921         i_delay += mdate() - p_ts->i_pause_date;
922     vlc_mutex_unlock( &p_ts->lock );
923
924     return i_delay;
925 }
926
927
928 static void *TsRun( vlc_object_t *p_thread )
929 {
930     ts_thread_t *p_ts = (ts_thread_t*)p_thread;
931     mtime_t i_buffering_date = -1;
932
933     for( ;; )
934     {
935         ts_cmd_t cmd;
936         mtime_t  i_deadline;
937         bool b_buffering;
938
939         /* Pop a command to execute */
940         vlc_mutex_lock( &p_ts->lock );
941         mutex_cleanup_push( &p_ts->lock );
942
943         for( ;; )
944         {
945             const int canc = vlc_savecancel();
946             b_buffering = es_out_GetBuffering( p_ts->p_out, NULL );
947
948             if( ( !p_ts->b_paused || b_buffering ) && !TsPopCmdLocked( p_ts, &cmd, false ) )
949             {
950                 vlc_restorecancel( canc );
951                 break;
952             }
953             vlc_restorecancel( canc );
954
955             vlc_cond_wait( &p_ts->wait, &p_ts->lock );
956         }
957
958         if( b_buffering && i_buffering_date < 0 )
959         {
960             i_buffering_date = cmd.i_date;
961         }
962         else if( i_buffering_date > 0 )
963         {
964             p_ts->i_buffering_delay += i_buffering_date - cmd.i_date; /* It is < 0 */
965             if( b_buffering )
966                 i_buffering_date = cmd.i_date;
967             else
968                 i_buffering_date = -1;
969         }
970
971         if( p_ts->i_rate_date < 0 )
972             p_ts->i_rate_date = cmd.i_date;
973
974         p_ts->i_rate_delay = 0;
975         if( p_ts->i_rate_source != p_ts->i_rate )
976         {
977             const mtime_t i_duration = cmd.i_date - p_ts->i_rate_date;
978             p_ts->i_rate_delay = i_duration * p_ts->i_rate / p_ts->i_rate_source - i_duration;
979         }
980         if( p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay < 0 && p_ts->i_rate != p_ts->i_rate_source )
981         {
982             const int canc = vlc_savecancel();
983
984             /* Auto reset to rate 1.0 */
985             msg_Warn( p_ts->p_input, "es out timeshift: auto reset rate to %d", p_ts->i_rate_source );
986
987             p_ts->i_cmd_delay = 0;
988             p_ts->i_buffering_delay = 0;
989
990             p_ts->i_rate_delay = 0;
991             p_ts->i_rate_date = -1;
992             p_ts->i_rate = p_ts->i_rate_source;
993
994             if( !es_out_SetRate( p_ts->p_out, p_ts->i_rate_source, p_ts->i_rate ) )
995             {
996                 vlc_value_t val = { .i_int = p_ts->i_rate };
997                 /* Warn back input
998                  * FIXME it is perfectly safe BUT it is ugly as it may hide a
999                  * rate change requested by user */
1000                 input_ControlPush( p_ts->p_input, INPUT_CONTROL_SET_RATE, &val );
1001             }
1002
1003             vlc_restorecancel( canc );
1004         }
1005         i_deadline = cmd.i_date + p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
1006
1007         vlc_cleanup_run();
1008
1009         /* Regulate the speed of command processing to the same one than
1010          * reading  */
1011         vlc_cleanup_push( cmd_cleanup_routine, &cmd );
1012
1013         mwait( i_deadline );
1014
1015         vlc_cleanup_pop();
1016
1017         /* Execute the command  */
1018         const int canc = vlc_savecancel();
1019         switch( cmd.i_type )
1020         {
1021         case C_ADD:
1022             CmdExecuteAdd( p_ts->p_out, &cmd );
1023             CmdCleanAdd( &cmd );
1024             break;
1025         case C_SEND:
1026             CmdExecuteSend( p_ts->p_out, &cmd );
1027             CmdCleanSend( &cmd );
1028             break;
1029         case C_CONTROL:
1030             CmdExecuteControl( p_ts->p_out, &cmd );
1031             CmdCleanControl( &cmd );
1032             break;
1033         case C_DEL:
1034             CmdExecuteDel( p_ts->p_out, &cmd );
1035             break;
1036         default:
1037             assert(0);
1038             break;
1039         }
1040         vlc_restorecancel( canc );
1041     }
1042
1043     return NULL;
1044 }
1045
1046 /*****************************************************************************
1047  *
1048  *****************************************************************************/
1049 static ts_storage_t *TsStorageNew( const char *psz_tmp_path, int64_t i_tmp_size_max )
1050 {
1051     ts_storage_t *p_storage = calloc( 1, sizeof(ts_storage_t) );
1052     if( !p_storage )
1053         return NULL;
1054
1055     /* */
1056     p_storage->p_next = NULL;
1057
1058     /* */
1059     p_storage->i_file_max = i_tmp_size_max;
1060     p_storage->i_file_size = 0;
1061     p_storage->p_filew = GetTmpFile( &p_storage->psz_file, psz_tmp_path );
1062     if( p_storage->psz_file )
1063         p_storage->p_filer = utf8_fopen( p_storage->psz_file, "rb" );
1064
1065     /* */
1066     p_storage->i_cmd_w = 0;
1067     p_storage->i_cmd_r = 0;
1068     p_storage->i_cmd_max = 30000;
1069     p_storage->p_cmd = malloc( p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) );
1070     //fprintf( stderr, "\nSTORAGE name=%s size=%d kbytes\n", p_storage->psz_file, p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) /1024 );
1071
1072     if( !p_storage->p_cmd || !p_storage->p_filew || !p_storage->p_filer )
1073     {
1074         TsStorageDelete( p_storage );
1075         return NULL;
1076     }
1077     return p_storage;
1078 }
1079 static void TsStorageDelete( ts_storage_t *p_storage )
1080 {
1081     while( p_storage->i_cmd_r < p_storage->i_cmd_w )
1082     {
1083         ts_cmd_t cmd;
1084
1085         TsStoragePopCmd( p_storage, &cmd, true );
1086
1087         CmdClean( &cmd );
1088     }
1089     free( p_storage->p_cmd );
1090
1091     if( p_storage->p_filer )
1092         fclose( p_storage->p_filer );
1093     if( p_storage->p_filew )
1094         fclose( p_storage->p_filew );
1095
1096     if( p_storage->psz_file )
1097     {
1098         utf8_unlink( p_storage->psz_file );
1099         free( p_storage->psz_file );
1100     }
1101
1102     free( p_storage );
1103 }
1104 static void TsStoragePack( ts_storage_t *p_storage )
1105 {
1106     /* Try to release a bit of memory */
1107     if( p_storage->i_cmd_w >= p_storage->i_cmd_max )
1108         return;
1109
1110     p_storage->i_cmd_max = __MAX( p_storage->i_cmd_w, 1 );
1111
1112     ts_cmd_t *p_new = realloc( p_storage->p_cmd, p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) );
1113     if( p_new )
1114         p_storage->p_cmd = p_new;
1115 }
1116 static bool TsStorageIsFull( ts_storage_t *p_storage, const ts_cmd_t *p_cmd )
1117 {
1118     if( p_cmd && p_cmd->i_type == C_SEND && p_storage->i_cmd_w > 0 )
1119     {
1120         size_t i_size = sizeof(*p_cmd->send.p_block) + p_cmd->send.p_block->i_buffer;
1121
1122         if( p_storage->i_file_size + i_size >= p_storage->i_file_max )
1123             return true;
1124     }
1125     return p_storage->i_cmd_w >= p_storage->i_cmd_max;
1126 }
1127 static bool TsStorageIsEmpty( ts_storage_t *p_storage )
1128 {
1129     return !p_storage || p_storage->i_cmd_r >= p_storage->i_cmd_w;
1130 }
1131 static void TsStoragePushCmd( ts_storage_t *p_storage, const ts_cmd_t *p_cmd, bool b_flush )
1132 {
1133     ts_cmd_t cmd = *p_cmd;
1134
1135     assert( !TsStorageIsFull( p_storage, p_cmd ) );
1136
1137     if( cmd.i_type == C_SEND )
1138     {
1139         block_t *p_block = cmd.send.p_block;
1140
1141         cmd.send.p_block = NULL;
1142         cmd.send.i_offset = ftell( p_storage->p_filew );
1143
1144         if( fwrite( p_block, sizeof(*p_block), 1, p_storage->p_filew ) != 1 )
1145         {
1146             block_Release( p_block );
1147             return;
1148         }
1149         p_storage->i_file_size += sizeof(*p_block);
1150         if( p_block->i_buffer > 0 )
1151         {
1152             if( fwrite( p_block->p_buffer, p_block->i_buffer, 1, p_storage->p_filew ) != 1 )
1153             {
1154                 block_Release( p_block );
1155                 return;
1156             }
1157         }
1158         p_storage->i_file_size += p_block->i_buffer;
1159         block_Release( p_block );
1160
1161         if( b_flush )
1162             fflush( p_storage->p_filew );
1163     }
1164     p_storage->p_cmd[p_storage->i_cmd_w++] = cmd;
1165 }
1166 static void TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush )
1167 {
1168     assert( !TsStorageIsEmpty( p_storage ) );
1169
1170     *p_cmd = p_storage->p_cmd[p_storage->i_cmd_r++];
1171     if( p_cmd->i_type == C_SEND )
1172     {
1173         block_t block;
1174
1175         if( !b_flush &&
1176             !fseek( p_storage->p_filer, p_cmd->send.i_offset, SEEK_SET ) &&
1177             fread( &block, sizeof(block), 1, p_storage->p_filer ) == 1 )
1178         {
1179             block_t *p_block = block_Alloc( block.i_buffer );
1180             if( p_block )
1181             {
1182                 p_block->i_dts      = block.i_dts;
1183                 p_block->i_pts      = block.i_pts;
1184                 p_block->i_flags    = block.i_flags;
1185                 p_block->i_length   = block.i_length;
1186                 p_block->i_rate     = block.i_rate;
1187                 p_block->i_samples  = block.i_samples;
1188                 p_block->i_buffer = fread( p_block->p_buffer, 1, block.i_buffer, p_storage->p_filer );
1189             }
1190             p_cmd->send.p_block = p_block;
1191         }
1192         else
1193         {
1194             //fprintf( stderr, "TsStoragePopCmd: %m\n" );
1195             p_cmd->send.p_block = block_Alloc( 1 );
1196         }
1197     }
1198 }
1199
1200 /*****************************************************************************
1201  *
1202  *****************************************************************************/
1203 static void CmdClean( ts_cmd_t *p_cmd )
1204 {
1205     switch( p_cmd->i_type )
1206     {
1207     case C_ADD:
1208         CmdCleanAdd( p_cmd );
1209         break;
1210     case C_SEND:
1211         CmdCleanSend( p_cmd );
1212         break;
1213     case C_CONTROL:
1214         CmdCleanControl( p_cmd );
1215         break;
1216     case C_DEL:
1217         break;
1218     default:
1219         assert(0);
1220         break;
1221     }
1222 }
1223
1224 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
1225 {
1226     p_cmd->i_type = C_ADD;
1227     p_cmd->i_date = mdate();
1228     p_cmd->add.p_es = p_es;
1229     if( b_copy )
1230     {
1231         p_cmd->add.p_fmt = malloc( sizeof(*p_fmt) );
1232         if( !p_cmd->add.p_fmt )
1233             return VLC_EGENERIC;
1234         es_format_Copy( p_cmd->add.p_fmt, p_fmt );
1235     }
1236     else
1237     {
1238         p_cmd->add.p_fmt = (es_format_t*)p_fmt;
1239     }
1240     return VLC_SUCCESS;
1241 }
1242 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
1243 {
1244     p_cmd->add.p_es->p_es = es_out_Add( p_out, p_cmd->add.p_fmt );
1245 }
1246 static void CmdCleanAdd( ts_cmd_t *p_cmd )
1247 {
1248     es_format_Clean( p_cmd->add.p_fmt );
1249     free( p_cmd->add.p_fmt );
1250 }
1251
1252 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
1253 {
1254     p_cmd->i_type = C_SEND;
1255     p_cmd->i_date = mdate();
1256     p_cmd->send.p_es = p_es;
1257     p_cmd->send.p_block = p_block;
1258 }
1259 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
1260 {
1261     block_t *p_block = p_cmd->send.p_block;
1262
1263     p_cmd->send.p_block = NULL;
1264
1265     if( p_block )
1266     {
1267         if( p_cmd->send.p_es->p_es )
1268             return es_out_Send( p_out, p_cmd->send.p_es->p_es, p_block );
1269         block_Release( p_block );
1270     }
1271     return VLC_EGENERIC;
1272 }
1273 static void CmdCleanSend( ts_cmd_t *p_cmd )
1274 {
1275     if( p_cmd->send.p_block )
1276         block_Release( p_cmd->send.p_block );
1277 }
1278
1279 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
1280 {
1281     p_cmd->i_type = C_DEL;
1282     p_cmd->i_date = mdate();
1283     p_cmd->del.p_es = p_es;
1284     return VLC_SUCCESS;
1285 }
1286 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
1287 {
1288     if( p_cmd->del.p_es->p_es )
1289         es_out_Del( p_out, p_cmd->del.p_es->p_es );
1290     free( p_cmd->del.p_es );
1291 }
1292
1293 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
1294 {
1295     p_cmd->i_type = C_CONTROL;
1296     p_cmd->i_date = mdate();
1297     p_cmd->control.i_query = i_query;
1298
1299     switch( i_query )
1300     {
1301     /* Pass-through control */
1302     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
1303         p_cmd->control.b_bool = (bool)va_arg( args, int );
1304         break;
1305
1306     case ES_OUT_SET_MODE:    /* arg1= int                            */
1307     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1308     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1309         p_cmd->control.i_int = (int)va_arg( args, int );
1310         break;
1311
1312     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1313     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1314         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
1315         break;
1316
1317     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1318         p_cmd->control.int_i64.i_int = (int)va_arg( args, int );
1319         p_cmd->control.int_i64.i_i64 = (int64_t)va_arg( args, int64_t );
1320         break;
1321
1322     case ES_OUT_RESET_PCR:           /* no arg */
1323         break;
1324
1325     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
1326     {
1327         p_cmd->control.int_meta.i_int = (int)va_arg( args, int );
1328         vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * );
1329
1330         if( b_copy )
1331         {
1332             p_cmd->control.int_meta.p_meta = vlc_meta_New();
1333             if( !p_cmd->control.int_meta.p_meta )
1334                 return VLC_EGENERIC;
1335             vlc_meta_Merge( p_cmd->control.int_meta.p_meta, p_meta );
1336         }
1337         else
1338         {
1339             p_cmd->control.int_meta.p_meta = p_meta;
1340         }
1341         break;
1342     }
1343
1344     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
1345     {
1346         p_cmd->control.int_epg.i_int = (int)va_arg( args, int );
1347         vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * );
1348
1349         if( b_copy )
1350         {
1351             p_cmd->control.int_epg.p_epg = vlc_epg_New( p_epg->psz_name );
1352             if( !p_cmd->control.int_epg.p_epg )
1353                 return VLC_EGENERIC;
1354             for( int i = 0; i < p_epg->i_event; i++ )
1355             {
1356                 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
1357
1358                 vlc_epg_AddEvent( p_cmd->control.int_epg.p_epg,
1359                                   p_evt->i_start, p_evt->i_duration,
1360                                   p_evt->psz_name,
1361                                   p_evt->psz_short_description, p_evt->psz_description );
1362             }
1363             vlc_epg_SetCurrent( p_cmd->control.int_epg.p_epg,
1364                                 p_epg->p_current ? p_epg->p_current->i_start : -1 );
1365         }
1366         else
1367         {
1368             p_cmd->control.int_epg.p_epg = p_epg;
1369         }
1370         break;
1371     }
1372
1373     /* Modified control */
1374     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1375     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1376     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1377         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1378         break;
1379
1380     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1381         p_cmd->control.es_bool.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1382         p_cmd->control.es_bool.b_bool = (bool)va_arg( args, int );
1383         break;
1384
1385     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1386     {
1387         p_cmd->control.es_fmt.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1388         es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
1389
1390         if( b_copy )
1391         {
1392             p_cmd->control.es_fmt.p_fmt = malloc( sizeof(*p_fmt) );
1393             if( !p_cmd->control.es_fmt.p_fmt )
1394                 return VLC_EGENERIC;
1395             es_format_Copy( p_cmd->control.es_fmt.p_fmt, p_fmt );
1396         }
1397         else
1398         {
1399             p_cmd->control.es_fmt.p_fmt = p_fmt;
1400         }
1401         break;
1402     }
1403
1404     default:
1405         assert(0);
1406         return VLC_EGENERIC;
1407     }
1408
1409     return VLC_SUCCESS;
1410 }
1411 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
1412 {
1413     const int i_query = p_cmd->control.i_query;
1414
1415     switch( i_query )
1416     {
1417     /* Pass-through control */
1418     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
1419         return es_out_Control( p_out, i_query, p_cmd->control.b_bool );
1420
1421     case ES_OUT_SET_MODE:    /* arg1= int                            */
1422     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1423     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1424         return es_out_Control( p_out, i_query, p_cmd->control.i_int );
1425
1426     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1427     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1428         return es_out_Control( p_out, i_query, p_cmd->control.i_i64 );
1429
1430     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1431         return es_out_Control( p_out, i_query, p_cmd->control.int_i64.i_int, p_cmd->control.int_i64.i_i64 );
1432
1433     case ES_OUT_RESET_PCR:           /* no arg */
1434         return es_out_Control( p_out, i_query );
1435
1436     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
1437         return es_out_Control( p_out, i_query, p_cmd->control.int_meta.i_int, p_cmd->control.int_meta.p_meta );
1438
1439     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
1440         return es_out_Control( p_out, i_query, p_cmd->control.int_epg.i_int, p_cmd->control.int_epg.p_epg );
1441
1442     /* Modified control */
1443     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1444     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1445     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1446         return es_out_Control( p_out, i_query, p_cmd->control.p_es->p_es );
1447
1448     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1449         return es_out_Control( p_out, i_query, p_cmd->control.es_bool.p_es->p_es, p_cmd->control.es_bool.b_bool );
1450
1451     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1452         return es_out_Control( p_out, i_query, p_cmd->control.es_fmt.p_es->p_es, p_cmd->control.es_fmt.p_fmt );
1453
1454     default:
1455         assert(0);
1456         return VLC_EGENERIC;
1457     }
1458 }
1459 static void CmdCleanControl( ts_cmd_t *p_cmd )
1460 {
1461     if( p_cmd->control.i_query == ES_OUT_SET_GROUP_META &&
1462         p_cmd->control.int_meta.p_meta )
1463     {
1464         vlc_meta_Delete( p_cmd->control.int_meta.p_meta );
1465     }
1466     else if( p_cmd->control.i_query == ES_OUT_SET_GROUP_EPG &&
1467              p_cmd->control.int_epg.p_epg )
1468     {
1469         vlc_epg_Delete( p_cmd->control.int_epg.p_epg );
1470     }
1471     else if( p_cmd->control.i_query == ES_OUT_SET_ES_FMT &&
1472              p_cmd->control.es_fmt.p_fmt )
1473     {
1474         es_format_Clean( p_cmd->control.es_fmt.p_fmt );
1475         free( p_cmd->control.es_fmt.p_fmt );
1476     }
1477 }
1478
1479
1480 /*****************************************************************************
1481  * GetTmpFile/Path:
1482  *****************************************************************************/
1483 static char *GetTmpPath( char *psz_path )
1484 {
1485     if( psz_path && *psz_path )
1486     {
1487         /* Make sure that the path exists and is a directory */
1488         struct stat s;
1489         const int i_ret = utf8_stat( psz_path, &s );
1490
1491         if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
1492             return psz_path;
1493         else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
1494             return psz_path;
1495     }
1496     free( psz_path );
1497
1498     /* Create a suitable path */
1499 #if defined (WIN32) && !defined (UNDER_CE)
1500     const DWORD dwCount = GetTempPathW( 0, NULL );
1501     wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
1502     if( psw_path )
1503     {
1504         if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
1505         {
1506             free( psw_path );
1507
1508             psw_path = _wgetcwd( NULL, 0 );
1509         }
1510     }
1511
1512     psz_path = NULL;
1513     if( psw_path )
1514     {
1515         psz_path = FromWide( psw_path );
1516         while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
1517             psz_path[strlen( psz_path ) - 1] = '\0';
1518
1519         free( psw_path );
1520     }
1521
1522     if( !psz_path || *psz_path == '\0' )
1523     {
1524         free( psz_path );
1525         return strdup( "C:" );
1526     }
1527 #else
1528     psz_path = strdup( "/tmp" );
1529 #endif
1530
1531     return psz_path;
1532 }
1533
1534 static FILE *GetTmpFile( char **ppsz_file, const char *psz_path )
1535 {
1536     char *psz_name;
1537     int fd;
1538     FILE *f;
1539
1540     /* */
1541     *ppsz_file = NULL;
1542     if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
1543         return NULL;
1544
1545     /* */
1546     fd = utf8_mkstemp( psz_name );
1547     *ppsz_file = psz_name;
1548
1549     if( fd < 0 )
1550         return NULL;
1551
1552     /* */
1553     f = fdopen( fd, "rw+" );
1554     if( !f )
1555         close( fd );
1556
1557     return f;
1558 }
1559