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