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