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