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