]> git.sesse.net Git - vlc/blob - src/input/es_out_timeshift.c
Auto reset rate to source rate when no more buffered data.
[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         if( p_sys->i_cmd_delay + p_sys->i_rate_delay < 0 )
642         {
643             /* Auto reset to rate 1.0 */
644             msg_Warn( p_sys->p_input, "es out timeshift: auto reset rate to %d", p_sys->i_rate_source );
645
646             p_sys->i_cmd_delay = 0;
647
648             p_sys->i_rate_date = -1;
649             p_sys->i_rate_delay = 0;
650             p_sys->i_rate = p_sys->i_rate_source;
651
652             if( !es_out_SetRate( p_sys->p_out, p_sys->i_rate_source, p_sys->i_rate ) )
653             {
654                 vlc_value_t val = { .i_int = p_sys->i_rate };
655                 /* Warn back input
656                  * FIXME it is perfectly safe BUT it is ugly as it may hide a
657                  * rate change requested by user */
658                 input_ControlPush( p_sys->p_input, INPUT_CONTROL_SET_RATE, &val );
659             }
660         }
661         i_deadline = cmd.i_date + p_sys->i_cmd_delay + p_sys->i_rate_delay;
662
663         vlc_cleanup_run();
664
665         /* Regulate the speed of command processing to the same one than
666          * reading  */
667         //fprintf( stderr, "d=%d - %d\n", (int)(p_sys->i_cmd_delay/1000), (int)( (mdate() - cmd.i_date - p_sys->i_cmd_delay)/1000) );
668         vlc_cleanup_push( cmd_cleanup_routine, &cmd );
669
670         mwait( i_deadline );
671
672         vlc_cleanup_pop();
673
674         /* Execute the command */
675         const int canc = vlc_savecancel();
676
677         vlc_mutex_lock( &p_sys->lock );
678         switch( cmd.i_type )
679         {
680         case C_ADD:
681             CmdExecuteAdd( p_out, &cmd );
682             CmdCleanAdd( &cmd );
683             break;
684         case C_SEND:
685             CmdExecuteSend( p_out, &cmd );
686             CmdCleanSend( &cmd );
687             break;
688         case C_CONTROL:
689             CmdExecuteControl( p_out, &cmd );
690             CmdCleanControl( &cmd );
691             break;
692         case C_DEL:
693             CmdExecuteDel( p_out, &cmd );
694             break;
695         default:
696             assert(0);
697             break;
698         }
699         vlc_mutex_unlock( &p_sys->lock );
700
701         vlc_restorecancel( canc );
702     }
703
704     return NULL;
705 }
706
707 /*****************************************************************************
708  *
709  *****************************************************************************/
710 static void CmdPush( es_out_t *p_out, const ts_cmd_t *p_cmd )
711 {
712     es_out_sys_t *p_sys = p_out->p_sys;
713     ts_cmd_t *p_dup = malloc( sizeof(*p_dup) );
714
715     if( p_dup )
716     {
717         *p_dup = *p_cmd;
718         TAB_APPEND( p_sys->i_cmd, p_sys->pp_cmd, p_dup );
719
720         vlc_cond_signal( &p_sys->wait );
721     }
722 }
723 static int CmdPop( es_out_t *p_out, ts_cmd_t *p_cmd )
724 {
725     es_out_sys_t *p_sys = p_out->p_sys;
726
727     if( p_sys->i_cmd <= 0 )
728         return VLC_EGENERIC;
729
730     *p_cmd = *p_sys->pp_cmd[0];
731
732     free( p_sys->pp_cmd[0] );
733     TAB_REMOVE( p_sys->i_cmd, p_sys->pp_cmd, p_sys->pp_cmd[0] );
734     return VLC_SUCCESS;
735 }
736
737 static void CmdClean( ts_cmd_t *p_cmd )
738 {
739     switch( p_cmd->i_type )
740     {
741     case C_ADD:
742         CmdCleanAdd( p_cmd );
743         break;
744     case C_SEND:
745         CmdCleanSend( p_cmd );
746         break;
747     case C_CONTROL:
748         CmdCleanControl( p_cmd );
749         break;
750     case C_DEL:
751         break;
752     default:
753         assert(0);
754         break;
755     }
756 }
757
758 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
759 {
760     p_cmd->i_type = C_ADD;
761     p_cmd->i_date = mdate();
762     p_cmd->add.p_es = p_es;
763     if( b_copy )
764     {
765         p_cmd->add.p_fmt = malloc( sizeof(*p_fmt) );
766         if( !p_cmd->add.p_fmt )
767             return VLC_EGENERIC;
768         es_format_Copy( p_cmd->add.p_fmt, p_fmt );
769     }
770     else
771     {
772         p_cmd->add.p_fmt = (es_format_t*)p_fmt;
773     }
774     return VLC_SUCCESS;
775 }
776 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
777 {
778     es_out_sys_t *p_sys = p_out->p_sys;
779
780     p_cmd->add.p_es->p_es = es_out_Add( p_sys->p_out, p_cmd->add.p_fmt );
781 }
782 static void CmdCleanAdd( ts_cmd_t *p_cmd )
783 {
784     es_format_Clean( p_cmd->add.p_fmt );
785     free( p_cmd->add.p_fmt );
786 }
787
788 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
789 {
790     p_cmd->i_type = C_SEND;
791     p_cmd->i_date = mdate();
792     p_cmd->send.p_es = p_es;
793     p_cmd->send.p_block = p_block;
794 }
795 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
796 {
797     es_out_sys_t *p_sys = p_out->p_sys;
798     block_t *p_block = p_cmd->send.p_block;
799
800     p_cmd->send.p_block = NULL;
801
802     if( p_block )
803     {
804         if( p_cmd->send.p_es->p_es )
805             return es_out_Send( p_sys->p_out, p_cmd->send.p_es->p_es, p_block );
806         block_Release( p_block );
807     }
808     return VLC_EGENERIC;
809 }
810 static void CmdCleanSend( ts_cmd_t *p_cmd )
811 {
812     if( p_cmd->send.p_block )
813         block_Release( p_cmd->send.p_block );
814 }
815
816 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
817 {
818     p_cmd->i_type = C_DEL;
819     p_cmd->i_date = mdate();
820     p_cmd->del.p_es = p_es;
821     return VLC_SUCCESS;
822 }
823 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
824 {
825     es_out_sys_t *p_sys = p_out->p_sys;
826
827     if( p_cmd->del.p_es->p_es )
828         es_out_Del( p_sys->p_out, p_cmd->del.p_es->p_es );
829     free( p_cmd->del.p_es );
830 }
831
832 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
833 {
834     p_cmd->i_type = C_CONTROL;
835     p_cmd->i_date = mdate();
836     p_cmd->control.i_query = i_query;
837     p_cmd->control.p_meta  = NULL;
838     p_cmd->control.p_epg = NULL;
839     p_cmd->control.p_fmt = NULL;
840
841     switch( i_query )
842     {
843     /* Pass-through control */
844     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
845         p_cmd->control.b_bool = (bool)va_arg( args, int );
846         break;
847
848     case ES_OUT_GET_ACTIVE:  /* arg1= bool*                    */
849         p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
850         break;
851
852     case ES_OUT_SET_MODE:    /* arg1= int                            */
853     case ES_OUT_SET_GROUP:   /* arg1= int                            */
854     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
855         p_cmd->control.i_int = (int)va_arg( args, int );
856         break;
857
858     case ES_OUT_GET_MODE:    /* arg2= int*                           */
859     case ES_OUT_GET_GROUP:   /* arg1= int*                           */
860         p_cmd->control.pi_int = (int*)va_arg( args, int * );
861         break;
862
863     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
864     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
865         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
866         break;
867
868     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
869         p_cmd->control.i_int = (int)va_arg( args, int );
870         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
871         break;
872
873     case ES_OUT_RESET_PCR:           /* no arg */
874         break;
875
876     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
877     {
878         p_cmd->control.i_int = (int)va_arg( args, int );
879         vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * );
880
881         if( b_copy )
882         {
883             p_cmd->control.p_meta = vlc_meta_New();
884             if( !p_cmd->control.p_meta )
885                 return VLC_EGENERIC;
886             vlc_meta_Merge( p_cmd->control.p_meta, p_meta );
887         }
888         else
889         {
890             p_cmd->control.p_meta = p_meta;
891         }
892         break;
893     }
894
895     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
896     {
897         p_cmd->control.i_int = (int)va_arg( args, int );
898         vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * );
899
900         if( b_copy )
901         {
902             p_cmd->control.p_epg = vlc_epg_New( p_epg->psz_name );
903             if( !p_cmd->control.p_epg )
904                 return VLC_EGENERIC;
905             for( int i = 0; i < p_epg->i_event; i++ )
906             {
907                 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
908
909                 vlc_epg_AddEvent( p_cmd->control.p_epg,
910                                   p_evt->i_start, p_evt->i_duration,
911                                   p_evt->psz_name,
912                                   p_evt->psz_short_description, p_evt->psz_description );
913             }
914             vlc_epg_SetCurrent( p_cmd->control.p_epg,
915                                 p_epg->p_current ? p_epg->p_current->i_start : -1 );
916         }
917         else
918         {
919             p_cmd->control.p_epg = p_epg;
920         }
921         break;
922     }
923
924     /* Modified control */
925     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
926     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
927     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
928         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
929         break;
930
931     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
932         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
933         p_cmd->control.b_bool = (bool)va_arg( args, int );
934         break;
935
936     case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool*  */
937         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
938         p_cmd->control.pb_bool = (bool*)va_arg( args, bool * );
939         break;
940
941     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
942     {
943         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
944         es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
945
946         if( b_copy )
947         {
948             p_cmd->control.p_fmt = malloc( sizeof(*p_fmt) );
949             if( !p_cmd->control.p_fmt )
950                 return VLC_EGENERIC;
951             es_format_Copy( p_cmd->control.p_fmt, p_fmt );
952         }
953         else
954         {
955             p_cmd->control.p_fmt = p_fmt;
956         }
957         break;
958     }
959
960     default:
961         assert(0);
962         return VLC_EGENERIC;
963     }
964
965     return VLC_SUCCESS;
966 }
967 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
968 {
969     es_out_sys_t *p_sys = p_out->p_sys;
970     const int i_query = p_cmd->control.i_query;
971
972     switch( i_query )
973     {
974     /* Pass-through control */
975     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
976         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.b_bool );
977
978     case ES_OUT_GET_ACTIVE:  /* arg1= bool*                    */
979         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pb_bool );
980
981     case ES_OUT_SET_MODE:    /* arg1= int                            */
982     case ES_OUT_SET_GROUP:   /* arg1= int                            */
983     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
984         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int );
985
986     case ES_OUT_GET_MODE:    /* arg2= int*                           */
987     case ES_OUT_GET_GROUP:   /* arg1= int*                           */
988         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.pi_int );
989
990     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
991     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
992         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_i64 );
993
994     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
995         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.i_i64 );
996
997     case ES_OUT_RESET_PCR:           /* no arg */
998         return es_out_Control( p_sys->p_out, i_query );
999
1000     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
1001         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_meta );
1002
1003     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
1004         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_epg );
1005
1006     /* Modified control */
1007     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1008     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1009     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1010         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es );
1011
1012     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1013         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.b_bool );
1014
1015     case ES_OUT_GET_ES_STATE:/* arg1= es_out_id_t* arg2=bool*  */
1016         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.pb_bool );
1017
1018     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1019         return es_out_Control( p_sys->p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.p_fmt );
1020
1021     default:
1022         assert(0);
1023         msg_Err( p_sys->p_input, "Unknown es_out_Control query in CmdExecuteControl!" );
1024         return VLC_EGENERIC;
1025     }
1026 }
1027 static void CmdCleanControl( ts_cmd_t *p_cmd )
1028 {
1029     if( p_cmd->control.p_meta )
1030         vlc_meta_Delete( p_cmd->control.p_meta );
1031     if( p_cmd->control.p_epg )
1032         vlc_epg_Delete( p_cmd->control.p_epg );
1033     if( p_cmd->control.p_fmt )
1034     {
1035         es_format_Clean( p_cmd->control.p_fmt );
1036         free( p_cmd->control.p_fmt );
1037     }
1038 }
1039
1040
1041 /*****************************************************************************
1042  * GetTmpFile/Path:
1043  *****************************************************************************/
1044 static char *GetTmpPath( char *psz_path )
1045 {
1046     if( psz_path && *psz_path )
1047     {
1048         /* Make sure that the path exists and is a directory */
1049         struct stat s;
1050         const int i_ret = utf8_stat( psz_path, &s );
1051
1052         if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
1053             return psz_path;
1054         else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
1055             return psz_path;
1056     }
1057     free( psz_path );
1058
1059     /* Create a suitable path */
1060 #if defined (WIN32) && !defined (UNDER_CE)
1061     const DWORD dwCount = GetTempPathW( 0, NULL );
1062     wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
1063     if( psw_path )
1064     {
1065         if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
1066         {
1067             free( psw_path );
1068
1069             psw_path = _wgetcwd( NULL, 0 );
1070         }
1071     }
1072
1073     psz_path = NULL;
1074     if( psw_path )
1075     {
1076         psz_path = FromWide( psw_path );
1077         while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
1078             psz_path[strlen( psz_path ) - 1] = '\0';
1079
1080         free( psw_path );
1081     }
1082
1083     if( !psz_path || *psz_path == '\0' )
1084     {
1085         free( psz_path );
1086         return strdup( "C:" );
1087     }
1088 #else
1089     psz_path = strdup( "/tmp" );
1090 #endif
1091
1092     return psz_path;
1093 }
1094
1095 static FILE *GetTmpFile( const char *psz_path )
1096 {
1097     char *psz_name;
1098     int fd;
1099     FILE *f;
1100
1101     /* */
1102     if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
1103         return NULL;
1104
1105     /* */
1106     fd = mkstemp( psz_name );
1107     free( psz_name );
1108
1109     if( fd < 0 )
1110         return NULL;
1111
1112     /* */
1113     f = fdopen( fd, "rw+" );
1114     if( !f )
1115         close( fd );
1116
1117     return f;
1118 }
1119