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