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