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