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