]> git.sesse.net Git - vlc/blob - src/input/es_out_timeshift.c
Added rate change support to es_out_timeshift.
[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_ via _DOT_ ecp _DOT_ fr>
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     int     i_type;
67     mtime_t i_date;
68     union
69     {
70         struct
71         {
72             es_out_id_t *p_es;
73             es_format_t *p_fmt;
74         } add;
75         struct
76         {
77             es_out_id_t *p_es;
78         } del;
79         struct
80         {
81             es_out_id_t *p_es;
82             block_t *p_block;
83         } send;
84         struct
85         {
86             int  i_query;
87
88             bool b_bool;
89             bool *pb_bool;
90             int  i_int;
91             int  *pi_int;
92             int64_t i_i64;
93             vlc_meta_t *p_meta;
94             vlc_epg_t *p_epg;
95             es_out_id_t *p_es;
96             es_format_t *p_fmt;
97         } control;
98     };
99 } ts_cmd_t;
100
101 struct es_out_id_t
102 {
103     es_out_id_t *p_es;
104 };
105
106 struct es_out_sys_t
107 {
108     input_thread_t *p_input;
109         es_out_t       *p_out;
110
111     /* Configuration */
112     int64_t        i_tmp_size_max;    /* Maximal temporary file size in byte */
113     char           *psz_tmp_path;     /* Path for temporary files */
114
115     /* Lock for all following fields */
116     vlc_mutex_t    lock;
117     vlc_cond_t     wait;
118
119     /* */
120     bool           b_delayed;
121     vlc_object_t   *p_thread;
122
123     bool           b_paused;
124     mtime_t        i_pause_date;
125
126     int            i_rate;
127     int            i_rate_source;
128     mtime_t        i_rate_date;
129     mtime_t        i_rate_delay;
130
131     /* */
132     int            i_es;
133     es_out_id_t    **pp_es;
134
135     /* */
136     int            i_cmd;
137     ts_cmd_t       **pp_cmd;
138     mtime_t        i_cmd_delay;
139 };
140
141 static es_out_id_t *Add    ( es_out_t *, const es_format_t * );
142 static int          Send   ( es_out_t *, es_out_id_t *, block_t * );
143 static void         Del    ( es_out_t *, es_out_id_t * );
144 static int          Control( es_out_t *, int i_query, va_list );
145 static void         Destroy( es_out_t * );
146
147 static int          TsStart( es_out_t * );
148 static void         TsStop( es_out_t * );
149 static void         *TsRun( vlc_object_t * );
150
151 static void CmdPush( es_out_t *, const ts_cmd_t * );
152 static int  CmdPop( es_out_t *, ts_cmd_t * );
153 static void CmdClean( ts_cmd_t * );
154 static void cmd_cleanup_routine( void *p ) { CmdClean( p ); }
155
156 static int  CmdInitAdd    ( ts_cmd_t *, es_out_id_t *, const es_format_t *, bool b_copy );
157 static void CmdInitSend   ( ts_cmd_t *, es_out_id_t *, block_t * );
158 static int  CmdInitDel    ( ts_cmd_t *, es_out_id_t * );
159 static int  CmdInitControl( ts_cmd_t *, int i_query, va_list, bool b_copy );
160
161 static void CmdExecuteAdd    ( es_out_t *, ts_cmd_t * );
162 static int  CmdExecuteSend   ( es_out_t *, ts_cmd_t * );
163 static void CmdExecuteDel    ( es_out_t *, ts_cmd_t * );
164 static int  CmdExecuteControl( es_out_t *, ts_cmd_t * );
165
166 static void CmdCleanAdd    ( ts_cmd_t * );
167 static void CmdCleanSend   ( ts_cmd_t * );
168 static void CmdCleanControl( ts_cmd_t *p_cmd );
169
170 static char *GetTmpPath( char *psz_path );
171 static FILE *GetTmpFile( const char *psz_path );
172
173 /*****************************************************************************
174  * input_EsOutTimeshiftNew:
175  *****************************************************************************/
176 es_out_t *input_EsOutTimeshiftNew( input_thread_t *p_input, es_out_t *p_next_out, int i_rate )
177 {
178     es_out_t *p_out = malloc( sizeof(*p_out) );
179     if( !p_out )
180         return NULL;
181
182     es_out_sys_t *p_sys = malloc( sizeof(*p_sys) );
183     if( !p_sys )
184     {
185         free( p_out );
186         return NULL;
187     }
188
189     /* */
190     p_out->pf_add     = Add;
191     p_out->pf_send    = Send;
192     p_out->pf_del     = Del;
193     p_out->pf_control = Control;
194     p_out->pf_destroy = Destroy;
195     p_out->p_sys      = p_sys;
196     p_out->b_sout     = p_input->p->p_sout != NULL;
197
198     /* */
199     p_sys->p_input = p_input;
200     p_sys->p_out = p_next_out;
201     vlc_mutex_init_recursive( &p_sys->lock );
202     vlc_cond_init( &p_sys->wait );
203
204     p_sys->b_delayed = false;
205     p_sys->p_thread = NULL;
206     p_sys->b_paused = false;
207     p_sys->i_pause_date = -1;
208     p_sys->i_rate_source =
209     p_sys->i_rate        = i_rate;
210     p_sys->i_rate_date = -1;
211     p_sys->i_rate_delay = 0;
212     p_sys->i_cmd_delay = 0;
213
214     TAB_INIT( p_sys->i_es, p_sys->pp_es );
215     TAB_INIT( p_sys->i_cmd, p_sys->pp_cmd );
216
217     /* TODO config
218      * timeshift-granularity
219      * timeshift-path
220      */
221     p_sys->i_tmp_size_max = 50 * 1024*1024;
222     p_sys->psz_tmp_path = GetTmpPath( NULL );
223
224     return p_out;
225 }
226
227 /*****************************************************************************
228  * Internal functions
229  *****************************************************************************/
230 static void Destroy( es_out_t *p_out )
231 {
232     es_out_sys_t *p_sys = p_out->p_sys;
233
234     if( p_sys->b_delayed )
235         TsStop( p_out );
236
237     TAB_CLEAN( p_sys->i_cmd, p_sys->pp_cmd  );
238
239     while( p_sys->i_es > 0 )
240         Del( p_out, p_sys->pp_es[0] );
241     TAB_CLEAN( p_sys->i_es, p_sys->pp_es  );
242
243     free( p_sys->psz_tmp_path );
244     vlc_cond_destroy( &p_sys->wait );
245     vlc_mutex_destroy( &p_sys->lock );
246     free( p_sys );
247     free( p_out );
248 }
249
250 static es_out_id_t *Add( es_out_t *p_out, const es_format_t *p_fmt )
251 {
252     es_out_sys_t *p_sys = p_out->p_sys;
253     ts_cmd_t cmd;
254
255     es_out_id_t *p_es = malloc( sizeof( *p_es ) );
256     if( !p_es )
257         return NULL;
258
259     vlc_mutex_lock( &p_sys->lock );
260
261     if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
262     {
263         vlc_mutex_unlock( &p_sys->lock );
264         free( p_es );
265         return NULL;
266     }
267
268     TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );
269
270     if( p_sys->b_delayed )
271         CmdPush( p_out, &cmd );
272     else
273         CmdExecuteAdd( p_out, &cmd );
274
275     vlc_mutex_unlock( &p_sys->lock );
276
277     return p_es;
278 }
279 static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
280 {
281     es_out_sys_t *p_sys = p_out->p_sys;
282     ts_cmd_t cmd;
283     int i_ret = VLC_SUCCESS;
284
285     vlc_mutex_lock( &p_sys->lock );
286
287     CmdInitSend( &cmd, p_es, p_block );
288     if( p_sys->b_delayed )
289         CmdPush( p_out, &cmd );
290     else
291         i_ret = CmdExecuteSend( p_out, &cmd) ;
292
293     vlc_mutex_unlock( &p_sys->lock );
294
295     return i_ret;
296 }
297 static void Del( es_out_t *p_out, es_out_id_t *p_es )
298 {
299     es_out_sys_t *p_sys = p_out->p_sys;
300     ts_cmd_t cmd;
301
302     vlc_mutex_lock( &p_sys->lock );
303
304     CmdInitDel( &cmd, p_es );
305     if( p_sys->b_delayed )
306         CmdPush( p_out, &cmd );
307     else
308         CmdExecuteDel( p_out, &cmd );
309
310     TAB_REMOVE( p_sys->i_es, p_sys->pp_es, p_es );
311
312     vlc_mutex_unlock( &p_sys->lock );
313 }
314
315 static int ControlLockedGetEmpty( es_out_t *p_out, bool *pb_empty )
316 {
317     es_out_sys_t *p_sys = p_out->p_sys;
318
319     if( p_sys->b_delayed && p_sys->i_cmd > 0 )
320         *pb_empty = false;
321     else
322         *pb_empty = es_out_GetEmpty( p_sys->p_out );
323
324     return VLC_SUCCESS;
325 }
326 static int ControlLockedGetWakeup( es_out_t *p_out, mtime_t *pi_wakeup )
327 {
328     es_out_sys_t *p_sys = p_out->p_sys;
329
330     if( p_sys->b_delayed )
331     {
332         assert( !p_sys->p_input->b_can_pace_control );
333         *pi_wakeup = 0;
334     }
335     else
336     {
337         *pi_wakeup = es_out_GetWakeup( p_sys->p_out );
338     }
339
340     return VLC_SUCCESS;
341 }
342 static int ControlLockedGetBuffering( es_out_t *p_out, bool *pb_buffering )
343 {
344     es_out_sys_t *p_sys = p_out->p_sys;
345
346     if( p_sys->b_delayed )
347         *pb_buffering = true;
348     else
349         *pb_buffering = es_out_GetBuffering( p_sys->p_out );
350
351     return VLC_SUCCESS;
352 }
353 static int ControlLockedSetPauseState( es_out_t *p_out, bool b_source_paused, bool b_paused, mtime_t i_date )
354 {
355     es_out_sys_t *p_sys = p_out->p_sys;
356
357     if( !p_sys->b_delayed &&
358         !b_source_paused == !b_paused )
359     {
360         return es_out_SetPauseState( p_sys->p_out, b_source_paused, b_paused, i_date );
361     }
362     if( p_sys->p_input->b_can_pace_control )
363     {
364         /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
365          * and so be able to advertize correctly pace control property in access
366          * module */
367         msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
368         return VLC_EGENERIC;
369     }
370
371     int i_ret;
372     if( b_paused )
373     {
374         assert( !b_source_paused );
375
376         if( !p_sys->b_delayed )
377             TsStart( p_out );
378
379         i_ret = es_out_SetPauseState( p_sys->p_out, true, true, i_date );
380     }
381     else
382     {
383         i_ret = es_out_SetPauseState( p_sys->p_out, false, false, i_date );
384     }
385
386     if( !i_ret )
387     {
388         if( !b_paused )
389         {
390             assert( p_sys->i_pause_date > 0 );
391
392             p_sys->i_cmd_delay += i_date - p_sys->i_pause_date;
393         }
394
395         p_sys->b_paused = b_paused;
396         p_sys->i_pause_date = i_date;
397
398         vlc_cond_signal( &p_sys->wait );
399     }
400     return i_ret;
401 }
402 static int ControlLockedSetRate( es_out_t *p_out, int i_src_rate, int i_rate )
403 {
404     es_out_sys_t *p_sys = p_out->p_sys;
405
406     if( !p_sys->b_delayed &&
407         i_src_rate == i_rate )
408     {
409         return es_out_SetRate( p_sys->p_out, i_src_rate, i_rate );
410     }
411     if( p_sys->p_input->b_can_pace_control )
412     {
413         /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
414          * and so be able to advertize correctly pace control property in access
415          * module */
416         msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
417         return VLC_EGENERIC;
418     }
419
420     p_sys->i_cmd_delay += p_sys->i_rate_delay;
421
422     p_sys->i_rate_date = -1;
423     p_sys->i_rate_delay = 0;
424     p_sys->i_rate = i_rate;
425     p_sys->i_rate_source = i_src_rate;
426
427     if( !p_sys->b_delayed )
428         TsStart( p_out );
429
430     return es_out_SetRate( p_sys->p_out, i_rate, i_rate );
431 }
432 static int ControlLockedSetTime( es_out_t *p_out, mtime_t i_date )
433 {
434     es_out_sys_t *p_sys = p_out->p_sys;
435
436     if( !p_sys->b_delayed )
437         return es_out_SetTime( p_sys->p_out, i_date );
438
439     /* TODO */
440     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support time change" );
441     return VLC_EGENERIC;
442 }
443 static int ControlLockedSetFrameNext( es_out_t *p_out )
444 {
445     es_out_sys_t *p_sys = p_out->p_sys;
446
447     if( !p_sys->b_delayed )
448         return es_out_SetFrameNext( p_sys->p_out );
449
450     /* TODO */
451     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support frame next" );
452     return VLC_EGENERIC;
453 }
454
455 static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
456 {
457     es_out_sys_t *p_sys = p_out->p_sys;
458
459     switch( i_query )
460     {
461     /* Invalid query for this es_out level */
462     case ES_OUT_SET_ES_BY_ID:
463     case ES_OUT_RESTART_ES_BY_ID:
464     case ES_OUT_SET_ES_DEFAULT_BY_ID:
465     case ES_OUT_SET_DELAY:
466     case ES_OUT_SET_RECORD_STATE:
467         assert(0);
468         return VLC_EGENERIC;
469
470     /* TODO ? or to remove ? */
471     case ES_OUT_GET_TS:
472         return VLC_EGENERIC;
473
474     /* Pass-through control */
475     case ES_OUT_SET_ACTIVE:
476     case ES_OUT_GET_ACTIVE:
477     case ES_OUT_SET_MODE:
478     case ES_OUT_GET_MODE:
479     case ES_OUT_SET_GROUP:
480     case ES_OUT_GET_GROUP:
481     case ES_OUT_SET_PCR:
482     case ES_OUT_SET_GROUP_PCR:
483     case ES_OUT_RESET_PCR:
484     case ES_OUT_SET_NEXT_DISPLAY_TIME:
485     case ES_OUT_SET_GROUP_META:
486     case ES_OUT_SET_GROUP_EPG:
487     case ES_OUT_DEL_GROUP:
488     case ES_OUT_SET_ES:
489     case ES_OUT_RESTART_ES:
490     case ES_OUT_SET_ES_DEFAULT:
491     case ES_OUT_SET_ES_STATE:
492     case ES_OUT_GET_ES_STATE:
493     case ES_OUT_SET_ES_FMT:
494     {
495         ts_cmd_t cmd;
496         if( CmdInitControl( &cmd, i_query, args, p_sys->b_delayed ) )
497             return VLC_EGENERIC;
498         if( p_sys->b_delayed )
499         {
500             CmdPush( p_out, &cmd );
501             return VLC_SUCCESS;
502         }
503         return CmdExecuteControl( p_out, &cmd );
504     }
505
506     /* Special control */
507     case ES_OUT_GET_EMPTY:
508     {
509         bool *pb_empty = (bool*)va_arg( args, bool* );
510         return ControlLockedGetEmpty( p_out, pb_empty );
511     }
512     case ES_OUT_GET_WAKE_UP: /* TODO ? */
513     {
514         mtime_t *pi_wakeup = (mtime_t*)va_arg( args, mtime_t* );
515         return ControlLockedGetWakeup( p_out, pi_wakeup );
516     }
517     case ES_OUT_GET_BUFFERING:
518     {
519         bool *pb_buffering = (bool *)va_arg( args, bool* );
520         return ControlLockedGetBuffering( p_out, pb_buffering );
521     }
522     case ES_OUT_SET_PAUSE_STATE:
523     {
524         const bool b_source_paused = (bool)va_arg( args, int );
525         const bool b_paused = (bool)va_arg( args, int );
526         const mtime_t i_date = (mtime_t) va_arg( args, mtime_t );
527
528         return ControlLockedSetPauseState( p_out, b_source_paused, b_paused, i_date );
529     }
530     case ES_OUT_SET_RATE:
531     {
532         const int i_src_rate = (int)va_arg( args, int );
533         const int i_rate = (int)va_arg( args, int );
534
535         return ControlLockedSetRate( p_out, i_src_rate, i_rate );
536     }
537     case ES_OUT_SET_TIME:
538     {
539         const mtime_t i_date = (mtime_t)va_arg( args, mtime_t );
540
541         return ControlLockedSetTime( p_out, i_date );
542     }
543     case ES_OUT_SET_FRAME_NEXT:
544     {
545         return ControlLockedSetFrameNext( p_out );
546     }
547
548     default:
549         msg_Err( p_sys->p_input, "Unknown es_out_Control query !" );
550         assert(0);
551         return VLC_EGENERIC;
552     }
553 }
554 static int Control( es_out_t *p_out, int i_query, va_list args )
555 {
556     es_out_sys_t *p_sys = p_out->p_sys;
557     int i_ret;
558
559     vlc_mutex_lock( &p_sys->lock );
560     i_ret = ControlLocked( p_out, i_query, args );
561     vlc_mutex_unlock( &p_sys->lock );
562
563     return i_ret;
564 }
565
566 /*****************************************************************************
567  *
568  *****************************************************************************/
569 static int TsStart( es_out_t *p_out )
570 {
571     es_out_sys_t *p_sys = p_out->p_sys;
572
573     assert( !p_sys->b_delayed );
574
575     p_sys->p_thread = vlc_custom_create( p_sys->p_input, sizeof(vlc_object_t),
576                                          VLC_OBJECT_GENERIC, "es out timeshift" );
577     if( !p_sys->p_thread )
578         return VLC_EGENERIC;
579
580     p_sys->b_delayed = true;
581     p_sys->p_thread->p_private = p_out;
582     if( vlc_thread_create( p_sys->p_thread, "es out timeshift",
583                            TsRun, VLC_THREAD_PRIORITY_INPUT, false ) )
584     {
585         msg_Err( p_sys->p_input, "cannot create input thread" );
586         vlc_object_release( p_sys->p_thread );
587
588         p_sys->b_delayed = false;
589         return VLC_EGENERIC;
590     }
591
592     return VLC_SUCCESS;
593 }
594 static void TsStop( es_out_t *p_out )
595 {
596     es_out_sys_t *p_sys = p_out->p_sys;
597
598     assert( p_sys->b_delayed );
599
600     vlc_object_kill( p_sys->p_thread );
601     vlc_thread_join( p_sys->p_thread );
602
603     for( ;; )
604     {
605         ts_cmd_t cmd;
606
607         if( CmdPop( p_out, &cmd ) )
608             break;
609
610         CmdClean( &cmd );
611     }
612
613     p_sys->b_delayed = false;
614 }
615 static void *TsRun( vlc_object_t *p_thread )
616 {
617     es_out_t *p_out = p_thread->p_private;
618     es_out_sys_t *p_sys = p_out->p_sys;
619
620     for( ;; )
621     {
622         ts_cmd_t cmd;
623         mtime_t  i_deadline;
624
625         /* Pop a command to execute */
626         vlc_mutex_lock( &p_sys->lock );
627         mutex_cleanup_push( &p_sys->lock );
628
629         while( p_sys->b_paused || CmdPop( p_out, &cmd ) )
630             vlc_cond_wait( &p_sys->wait, &p_sys->lock );
631
632         if( p_sys->i_rate_date < 0 )
633             p_sys->i_rate_date = cmd.i_date;
634
635         p_sys->i_rate_delay = 0;
636         if( p_sys->i_rate_source != p_sys->i_rate )
637         {
638             const mtime_t i_duration = cmd.i_date - p_sys->i_rate_date;
639             p_sys->i_rate_delay = i_duration * p_sys->i_rate / p_sys->i_rate_source - i_duration;
640         }
641
642         i_deadline = cmd.i_date + p_sys->i_cmd_delay + p_sys->i_rate_delay;
643
644         if( p_sys->i_cmd_delay + p_sys->i_rate_delay < 0 )
645         {
646             /* TODO handle when we cannot go faster anymore */
647             msg_Err( p_sys->p_input, "FIXME rate underflow" );
648         }
649
650         vlc_cleanup_run();
651
652         /* Regulate the speed of command processing to the same one than
653          * reading  */
654         //fprintf( stderr, "d=%d - %d\n", (int)(p_sys->i_cmd_delay/1000), (int)( (mdate() - cmd.i_date - p_sys->i_cmd_delay)/1000) );
655         vlc_cleanup_push( cmd_cleanup_routine, &cmd );
656
657         mwait( i_deadline );
658
659         vlc_cleanup_pop();
660
661         /* Execute the command */
662         const int canc = vlc_savecancel();
663
664         vlc_mutex_lock( &p_sys->lock );
665         switch( cmd.i_type )
666         {
667         case C_ADD:
668             CmdExecuteAdd( p_out, &cmd );
669             CmdCleanAdd( &cmd );
670             break;
671         case C_SEND:
672             CmdExecuteSend( p_out, &cmd );
673             CmdCleanSend( &cmd );
674             break;
675         case C_CONTROL:
676             CmdExecuteControl( p_out, &cmd );
677             CmdCleanControl( &cmd );
678             break;
679         case C_DEL:
680             CmdExecuteDel( p_out, &cmd );
681             break;
682         default:
683             assert(0);
684             break;
685         }
686         vlc_mutex_unlock( &p_sys->lock );
687
688         vlc_restorecancel( canc );
689     }
690
691     return NULL;
692 }
693
694 /*****************************************************************************
695  *
696  *****************************************************************************/
697 static void CmdPush( es_out_t *p_out, const ts_cmd_t *p_cmd )
698 {
699     es_out_sys_t *p_sys = p_out->p_sys;
700     ts_cmd_t *p_dup = malloc( sizeof(*p_dup) );
701
702     if( p_dup )
703     {
704         *p_dup = *p_cmd;
705         TAB_APPEND( p_sys->i_cmd, p_sys->pp_cmd, p_dup );
706
707         vlc_cond_signal( &p_sys->wait );
708     }
709 }
710 static int CmdPop( es_out_t *p_out, ts_cmd_t *p_cmd )
711 {
712     es_out_sys_t *p_sys = p_out->p_sys;
713
714     if( p_sys->i_cmd <= 0 )
715         return VLC_EGENERIC;
716
717     *p_cmd = *p_sys->pp_cmd[0];
718
719     free( p_sys->pp_cmd[0] );
720     TAB_REMOVE( p_sys->i_cmd, p_sys->pp_cmd, p_sys->pp_cmd[0] );
721     return VLC_SUCCESS;
722 }
723
724 static void CmdClean( ts_cmd_t *p_cmd )
725 {
726     switch( p_cmd->i_type )
727     {
728     case C_ADD:
729         CmdCleanAdd( p_cmd );
730         break;
731     case C_SEND:
732         CmdCleanSend( p_cmd );
733         break;
734     case C_CONTROL:
735         CmdCleanControl( p_cmd );
736         break;
737     case C_DEL:
738         break;
739     default:
740         assert(0);
741         break;
742     }
743 }
744
745 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
746 {
747     p_cmd->i_type = C_ADD;
748     p_cmd->i_date = mdate();
749     p_cmd->add.p_es = p_es;
750     if( b_copy )
751     {
752         p_cmd->add.p_fmt = malloc( sizeof(*p_fmt) );
753         if( !p_cmd->add.p_fmt )
754             return VLC_EGENERIC;
755         es_format_Copy( p_cmd->add.p_fmt, p_fmt );
756     }
757     else
758     {
759         p_cmd->add.p_fmt = (es_format_t*)p_fmt;
760     }
761     return VLC_SUCCESS;
762 }
763 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
764 {
765     es_out_sys_t *p_sys = p_out->p_sys;
766
767     p_cmd->add.p_es->p_es = es_out_Add( p_sys->p_out, p_cmd->add.p_fmt );
768 }
769 static void CmdCleanAdd( ts_cmd_t *p_cmd )
770 {
771     es_format_Clean( p_cmd->add.p_fmt );
772     free( p_cmd->add.p_fmt );
773 }
774
775 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
776 {
777     p_cmd->i_type = C_SEND;
778     p_cmd->i_date = mdate();
779     p_cmd->send.p_es = p_es;
780     p_cmd->send.p_block = p_block;
781 }
782 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
783 {
784     es_out_sys_t *p_sys = p_out->p_sys;
785     block_t *p_block = p_cmd->send.p_block;
786
787     p_cmd->send.p_block = NULL;
788
789     if( p_block )
790     {
791         if( p_cmd->send.p_es->p_es )
792             return es_out_Send( p_sys->p_out, p_cmd->send.p_es->p_es, p_block );
793         block_Release( p_block );
794     }
795     return VLC_EGENERIC;
796 }
797 static void CmdCleanSend( ts_cmd_t *p_cmd )
798 {
799     if( p_cmd->send.p_block )
800         block_Release( p_cmd->send.p_block );
801 }
802
803 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
804 {
805     p_cmd->i_type = C_DEL;
806     p_cmd->i_date = mdate();
807     p_cmd->del.p_es = p_es;
808     return VLC_SUCCESS;
809 }
810 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
811 {
812     es_out_sys_t *p_sys = p_out->p_sys;
813
814     if( p_cmd->del.p_es->p_es )
815         es_out_Del( p_sys->p_out, p_cmd->del.p_es->p_es );
816     free( p_cmd->del.p_es );
817 }
818
819 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
820 {
821     p_cmd->i_type = C_CONTROL;
822     p_cmd->i_date = mdate();
823     p_cmd->control.i_query = i_query;
824     p_cmd->control.p_meta  = NULL;
825     p_cmd->control.p_epg = NULL;
826     p_cmd->control.p_fmt = NULL;
827
828     switch( i_query )
829     {
830     /* Pass-through control */
831     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
832         p_cmd->control.b_bool = (bool)va_arg( args, int );
833         break;
834
835     case ES_OUT_GET_ACTIVE:  /* arg1= bool*                    */
836         p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
837         break;
838
839     case ES_OUT_SET_MODE:    /* arg1= int                            */
840     case ES_OUT_SET_GROUP:   /* arg1= int                            */
841     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
842         p_cmd->control.i_int = (int)va_arg( args, int );
843         break;
844
845     case ES_OUT_GET_MODE:    /* arg2= int*                           */
846     case ES_OUT_GET_GROUP:   /* arg1= int*                           */
847         p_cmd->control.pi_int = (int*)va_arg( args, int * );
848         break;
849
850     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
851     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
852         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
853         break;
854
855     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
856         p_cmd->control.i_int = (int)va_arg( args, int );
857         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
858         break;
859
860     case ES_OUT_RESET_PCR:           /* no arg */
861         break;
862
863     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
864     {
865         p_cmd->control.i_int = (int)va_arg( args, int );
866         vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * );
867
868         if( b_copy )
869         {
870             p_cmd->control.p_meta = vlc_meta_New();
871             if( !p_cmd->control.p_meta )
872                 return VLC_EGENERIC;
873             vlc_meta_Merge( p_cmd->control.p_meta, p_meta );
874         }
875         else
876         {
877             p_cmd->control.p_meta = p_meta;
878         }
879         break;
880     }
881
882     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
883     {
884         p_cmd->control.i_int = (int)va_arg( args, int );
885         vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * );
886
887         if( b_copy )
888         {
889             p_cmd->control.p_epg = vlc_epg_New( p_epg->psz_name );
890             if( !p_cmd->control.p_epg )
891                 return VLC_EGENERIC;
892             for( int i = 0; i < p_epg->i_event; i++ )
893             {
894                 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
895
896                 vlc_epg_AddEvent( p_cmd->control.p_epg,
897                                   p_evt->i_start, p_evt->i_duration,
898                                   p_evt->psz_name,
899                                   p_evt->psz_short_description, p_evt->psz_description );
900             }
901             vlc_epg_SetCurrent( p_cmd->control.p_epg,
902                                 p_epg->p_current ? p_epg->p_current->i_start : -1 );
903         }
904         else
905         {
906             p_cmd->control.p_epg = p_epg;
907         }
908         break;
909     }
910
911     /* Modified control */
912     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
913     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
914     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
915         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
916         break;
917
918     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
919         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
920         p_cmd->control.b_bool = (bool)va_arg( args, int );
921         break;
922
923     case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool*  */
924         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
925         p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
926         break;
927
928     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
929     {
930         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
931         es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
932
933         if( b_copy )
934         {
935             p_cmd->control.p_fmt = malloc( sizeof(*p_fmt) );
936             if( !p_cmd->control.p_fmt )
937                 return VLC_EGENERIC;
938             es_format_Copy( p_cmd->control.p_fmt, p_fmt );
939         }
940         else
941         {
942             p_cmd->control.p_fmt = p_fmt;
943         }
944         break;
945     }
946
947     default:
948         assert(0);
949         return VLC_EGENERIC;
950     }
951
952     return VLC_SUCCESS;
953 }
954 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
955 {
956     es_out_sys_t *p_sys = p_out->p_sys;
957     const int i_query = p_cmd->control.i_query;
958
959     switch( i_query )
960     {
961     /* Pass-through control */
962     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
963         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.b_bool );
964
965     case ES_OUT_GET_ACTIVE:  /* arg1= bool*                    */
966         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pb_bool );
967
968     case ES_OUT_SET_MODE:    /* arg1= int                            */
969     case ES_OUT_SET_GROUP:   /* arg1= int                            */
970     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
971         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int );
972
973     case ES_OUT_GET_MODE:    /* arg2= int*                           */
974     case ES_OUT_GET_GROUP:   /* arg1= int*                           */
975         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pi_int );
976
977     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
978     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
979         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_i64 );
980
981     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
982         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.i_i64 );
983
984     case ES_OUT_RESET_PCR:           /* no arg */
985         return es_out_Control( p_sys->p_out, i_query );
986
987     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
988         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_meta );
989
990     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
991         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_epg );
992
993     /* Modified control */
994     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
995     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
996     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
997         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es );
998
999     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1000         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.b_bool );
1001
1002     case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool*  */
1003         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.pb_bool );
1004
1005     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1006         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.p_fmt );
1007
1008     default:
1009         assert(0);
1010         msg_Err( p_sys->p_input, "Unknown es_out_Control query in CmdExecuteControl!" );
1011         return VLC_EGENERIC;
1012     }
1013 }
1014 static void CmdCleanControl( ts_cmd_t *p_cmd )
1015 {
1016     if( p_cmd->control.p_meta )
1017         vlc_meta_Delete( p_cmd->control.p_meta );
1018     if( p_cmd->control.p_epg )
1019         vlc_epg_Delete( p_cmd->control.p_epg );
1020     if( p_cmd->control.p_fmt )
1021     {
1022         es_format_Clean( p_cmd->control.p_fmt );
1023         free( p_cmd->control.p_fmt );
1024     }
1025 }
1026
1027
1028 /*****************************************************************************
1029  * GetTmpFile/Path:
1030  *****************************************************************************/
1031 static char *GetTmpPath( char *psz_path )
1032 {
1033     if( psz_path && *psz_path )
1034     {
1035         /* Make sure that the path exists and is a directory */
1036         struct stat s;
1037         const int i_ret = utf8_stat( psz_path, &s );
1038
1039         if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
1040             return psz_path;
1041         else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
1042             return psz_path;
1043     }
1044     free( psz_path );
1045
1046     /* Create a suitable path */
1047 #if defined (WIN32) && !defined (UNDER_CE)
1048     const DWORD dwCount = GetTempPathW( 0, NULL );
1049     wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
1050     if( psw_path )
1051     {
1052         if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
1053         {
1054             free( psw_path );
1055
1056             psw_path = _wgetcwd( NULL, 0 );
1057         }
1058     }
1059
1060     psz_path = NULL;
1061     if( psw_path )
1062     {
1063         psz_path = FromWide( psw_path );
1064         while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
1065             psz_path[strlen( psz_path ) - 1] = '\0';
1066
1067         free( psw_path );
1068     }
1069
1070     if( !psz_path || *psz_path == '\0' )
1071     {
1072         free( psz_path );
1073         return strdup( "C:" );
1074     }
1075 #else
1076     psz_path = strdup( "/tmp" );
1077 #endif
1078
1079     return psz_path;
1080 }
1081
1082 static FILE *GetTmpFile( const char *psz_path )
1083 {
1084     char *psz_name;
1085     int fd;
1086     FILE *f;
1087
1088     /* */
1089     if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
1090         return NULL;
1091
1092     /* */
1093     fd = mkstemp( psz_name );
1094     free( psz_name );
1095
1096     if( fd < 0 )
1097         return NULL;
1098
1099     /* */
1100     f = fdopen( fd, "rw+" );
1101     if( !f )
1102         close( fd );
1103
1104     return f;
1105 }
1106