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