]> git.sesse.net Git - vlc/blob - src/input/es_out_timeshift.c
Added frame by frame support in es out timeshift.
[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_ 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     //if( !p_sys->b_delayed )
482         return es_out_SetFrameNext( p_sys->p_out );
483
484     /* TODO */
485     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support frame next" );
486     return VLC_EGENERIC;
487 }
488
489 static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
490 {
491     es_out_sys_t *p_sys = p_out->p_sys;
492
493     switch( i_query )
494     {
495     /* Invalid query for this es_out level */
496     case ES_OUT_SET_ES_BY_ID:
497     case ES_OUT_RESTART_ES_BY_ID:
498     case ES_OUT_SET_ES_DEFAULT_BY_ID:
499     case ES_OUT_SET_DELAY:
500     case ES_OUT_SET_RECORD_STATE:
501         assert(0);
502         return VLC_EGENERIC;
503
504     /* TODO ? or to remove ? */
505     case ES_OUT_GET_TS:
506         return VLC_EGENERIC;
507
508     /* Pass-through control */
509     case ES_OUT_SET_ACTIVE:
510     case ES_OUT_SET_MODE:
511     case ES_OUT_SET_GROUP:
512     case ES_OUT_SET_PCR:
513     case ES_OUT_SET_GROUP_PCR:
514     case ES_OUT_RESET_PCR:
515     case ES_OUT_SET_NEXT_DISPLAY_TIME:
516     case ES_OUT_SET_GROUP_META:
517     case ES_OUT_SET_GROUP_EPG:
518     case ES_OUT_DEL_GROUP:
519     case ES_OUT_SET_ES:
520     case ES_OUT_RESTART_ES:
521     case ES_OUT_SET_ES_DEFAULT:
522     case ES_OUT_SET_ES_STATE:
523     case ES_OUT_SET_ES_FMT:
524     {
525         ts_cmd_t cmd;
526         if( CmdInitControl( &cmd, i_query, args, p_sys->b_delayed ) )
527             return VLC_EGENERIC;
528         if( p_sys->b_delayed )
529         {
530             TsPushCmd( p_sys->p_thread, &cmd );
531             return VLC_SUCCESS;
532         }
533         return CmdExecuteControl( p_sys->p_out, &cmd );
534     }
535
536     /* Special control when delayed */
537     case ES_OUT_GET_ES_STATE:
538     {
539         es_out_id_t *p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
540         bool *pb_enabled = (bool*)va_arg( args, bool* );
541
542         if( p_sys->b_delayed )
543         {
544             *pb_enabled = true;
545             return VLC_SUCCESS;
546         }
547         return es_out_Control( p_sys->p_out, ES_OUT_GET_ES_STATE, p_es, pb_enabled );
548     }
549
550     /* Special internal input control */
551     case ES_OUT_GET_EMPTY:
552     {
553         bool *pb_empty = (bool*)va_arg( args, bool* );
554         return ControlLockedGetEmpty( p_out, pb_empty );
555     }
556     case ES_OUT_GET_WAKE_UP: /* TODO ? */
557     {
558         mtime_t *pi_wakeup = (mtime_t*)va_arg( args, mtime_t* );
559         return ControlLockedGetWakeup( p_out, pi_wakeup );
560     }
561     case ES_OUT_GET_BUFFERING:
562     {
563         bool *pb_buffering = (bool *)va_arg( args, bool* );
564         return ControlLockedGetBuffering( p_out, pb_buffering );
565     }
566     case ES_OUT_SET_PAUSE_STATE:
567     {
568         const bool b_source_paused = (bool)va_arg( args, int );
569         const bool b_paused = (bool)va_arg( args, int );
570         const mtime_t i_date = (mtime_t) va_arg( args, mtime_t );
571
572         return ControlLockedSetPauseState( p_out, b_source_paused, b_paused, i_date );
573     }
574     case ES_OUT_SET_RATE:
575     {
576         const int i_src_rate = (int)va_arg( args, int );
577         const int i_rate = (int)va_arg( args, int );
578
579         return ControlLockedSetRate( p_out, i_src_rate, i_rate );
580     }
581     case ES_OUT_SET_TIME:
582     {
583         const mtime_t i_date = (mtime_t)va_arg( args, mtime_t );
584
585         return ControlLockedSetTime( p_out, i_date );
586     }
587     case ES_OUT_SET_FRAME_NEXT:
588     {
589         return ControlLockedSetFrameNext( p_out );
590     }
591
592     default:
593         msg_Err( p_sys->p_input, "Unknown es_out_Control query !" );
594         assert(0);
595         return VLC_EGENERIC;
596     }
597 }
598 static int Control( es_out_t *p_out, int i_query, va_list args )
599 {
600     es_out_sys_t *p_sys = p_out->p_sys;
601     int i_ret;
602
603     vlc_mutex_lock( &p_sys->lock );
604
605     TsAutoStop( p_out );
606
607     i_ret = ControlLocked( p_out, i_query, args );
608
609     vlc_mutex_unlock( &p_sys->lock );
610
611     return i_ret;
612 }
613
614 /*****************************************************************************
615  *
616  *****************************************************************************/
617 static void TsDestructor( vlc_object_t *p_this )
618 {
619     ts_thread_t *p_ts = (ts_thread_t*)p_this;
620
621     vlc_cond_destroy( &p_ts->wait );
622     vlc_mutex_destroy( &p_ts->lock );
623 }
624 static int TsStart( es_out_t *p_out )
625 {
626     es_out_sys_t *p_sys = p_out->p_sys;
627     ts_thread_t *p_ts;
628
629     assert( !p_sys->b_delayed );
630
631     p_sys->p_thread = p_ts = vlc_custom_create( p_sys->p_input, sizeof(ts_thread_t),
632                                                 VLC_OBJECT_GENERIC, "es out timeshift" );
633     if( !p_ts )
634         return VLC_EGENERIC;
635
636     p_ts->p_input = p_sys->p_input;
637     p_ts->p_out = p_sys->p_out;
638     vlc_mutex_init( &p_ts->lock );
639     vlc_cond_init( &p_ts->wait );
640     p_ts->b_paused = p_sys->b_input_paused && !p_sys->b_input_paused_source;
641     p_ts->i_pause_date = p_ts->b_paused ? mdate() : -1;
642     p_ts->i_rate_source = p_sys->i_input_rate_source;
643     p_ts->i_rate        = p_sys->i_input_rate;
644     p_ts->i_rate_date = -1;
645     p_ts->i_rate_delay = 0;
646     p_ts->i_buffering_delay = 0;
647     p_ts->i_cmd_delay = 0;
648     TAB_INIT( p_ts->i_cmd, p_ts->pp_cmd );
649
650     vlc_object_set_destructor( p_ts, TsDestructor );
651
652     p_sys->b_delayed = true;
653     if( vlc_thread_create( p_ts, "es out timeshift",
654                            TsRun, VLC_THREAD_PRIORITY_INPUT, false ) )
655     {
656         msg_Err( p_sys->p_input, "cannot create input thread" );
657
658         vlc_object_release( p_ts );
659
660         p_sys->b_delayed = false;
661         return VLC_EGENERIC;
662     }
663
664     return VLC_SUCCESS;
665 }
666 static void TsAutoStop( es_out_t *p_out )
667 {
668     es_out_sys_t *p_sys = p_out->p_sys;
669
670     if( !p_sys->b_delayed || !TsIsUnused( p_sys->p_thread ) )
671         return;
672
673     msg_Warn( p_sys->p_input, "es out timeshift: auto stop" );
674     TsStop( p_sys->p_thread );
675
676     p_sys->b_delayed = false;
677 }
678 static void TsStop( ts_thread_t *p_ts )
679 {
680     vlc_object_kill( p_ts );
681     vlc_thread_join( p_ts );
682
683     vlc_mutex_lock( &p_ts->lock );
684     for( ;; )
685     {
686         ts_cmd_t cmd;
687
688         if( TsPopCmdLocked( p_ts, &cmd ) )
689             break;
690
691         CmdClean( &cmd );
692     }
693     vlc_mutex_unlock( &p_ts->lock );
694     TAB_CLEAN( p_ts->i_cmd, p_ts->pp_cmd  );
695
696     vlc_object_release( p_ts );
697 }
698 static void TsPushCmd( ts_thread_t *p_ts, const ts_cmd_t *p_cmd )
699 {
700     ts_cmd_t *p_dup = malloc( sizeof(*p_dup) );
701
702     if( p_dup )
703     {
704         *p_dup = *p_cmd;
705
706         vlc_mutex_lock( &p_ts->lock );
707
708         TAB_APPEND( p_ts->i_cmd, p_ts->pp_cmd, p_dup );
709         vlc_cond_signal( &p_ts->wait );
710
711         vlc_mutex_unlock( &p_ts->lock );
712     }
713 }
714 static int TsPopCmdLocked( ts_thread_t *p_ts, ts_cmd_t *p_cmd )
715 {
716     vlc_assert_locked( &p_ts->lock );
717
718     if( p_ts->i_cmd <= 0 )
719         return VLC_EGENERIC;
720
721     *p_cmd = *p_ts->pp_cmd[0];
722
723     free( p_ts->pp_cmd[0] );
724     TAB_REMOVE( p_ts->i_cmd, p_ts->pp_cmd, p_ts->pp_cmd[0] );
725     return VLC_SUCCESS;
726 }
727 static bool TsHasCmd( ts_thread_t *p_ts )
728 {
729     bool b_cmd;
730
731     vlc_mutex_lock( &p_ts->lock );
732     b_cmd =  p_ts->i_cmd > 0;
733     vlc_mutex_unlock( &p_ts->lock );
734
735     return b_cmd;
736 }
737 static bool TsIsUnused( ts_thread_t *p_ts )
738 {
739     bool b_unused;
740
741     vlc_mutex_lock( &p_ts->lock );
742     b_unused = !p_ts->b_paused &&
743                p_ts->i_rate == p_ts->i_rate_source &&
744                p_ts->i_cmd <= 0;
745     vlc_mutex_unlock( &p_ts->lock );
746
747     return b_unused;
748 }
749 static int TsChangePause( ts_thread_t *p_ts, bool b_source_paused, bool b_paused, mtime_t i_date )
750 {
751     vlc_mutex_lock( &p_ts->lock );
752
753     int i_ret;
754     if( b_paused )
755     {
756         assert( !b_source_paused );
757         i_ret = es_out_SetPauseState( p_ts->p_out, true, true, i_date );
758     }
759     else
760     {
761         i_ret = es_out_SetPauseState( p_ts->p_out, false, false, i_date );
762     }
763
764     if( !i_ret )
765     {
766         if( !b_paused )
767         {
768             assert( p_ts->i_pause_date > 0 );
769
770             p_ts->i_cmd_delay += i_date - p_ts->i_pause_date;
771         }
772
773         p_ts->b_paused = b_paused;
774         p_ts->i_pause_date = i_date;
775
776         vlc_cond_signal( &p_ts->wait );
777     }
778     vlc_mutex_unlock( &p_ts->lock );
779     return i_ret;
780 }
781 static int TsChangeRate( ts_thread_t *p_ts, int i_src_rate, int i_rate )
782 {
783     int i_ret;
784
785     vlc_mutex_lock( &p_ts->lock );
786     p_ts->i_cmd_delay += p_ts->i_rate_delay;
787
788     p_ts->i_rate_date = -1;
789     p_ts->i_rate_delay = 0;
790     p_ts->i_rate = i_rate;
791     p_ts->i_rate_source = i_src_rate;
792
793     i_ret = es_out_SetRate( p_ts->p_out, i_rate, i_rate );
794     vlc_mutex_unlock( &p_ts->lock );
795
796     return i_ret;
797 }
798
799 static void *TsRun( vlc_object_t *p_thread )
800 {
801     ts_thread_t *p_ts = (ts_thread_t*)p_thread;
802     mtime_t i_buffering_date = -1;
803
804     for( ;; )
805     {
806         ts_cmd_t cmd;
807         mtime_t  i_deadline;
808         bool b_buffering;
809
810         /* Pop a command to execute */
811         vlc_mutex_lock( &p_ts->lock );
812         mutex_cleanup_push( &p_ts->lock );
813
814         for( ;; )
815         {
816             const int canc = vlc_savecancel();
817             b_buffering = es_out_GetBuffering( p_ts->p_out );
818             vlc_restorecancel( canc );
819
820             if( ( !p_ts->b_paused || b_buffering ) && !TsPopCmdLocked( p_ts, &cmd ) )
821                 break;
822
823             vlc_cond_wait( &p_ts->wait, &p_ts->lock );
824         }
825
826         if( b_buffering && i_buffering_date < 0 )
827         {
828             i_buffering_date = cmd.i_date;
829         }
830         else if( i_buffering_date > 0 )
831         {
832             p_ts->i_buffering_delay += i_buffering_date - cmd.i_date; /* It is < 0 */
833             if( b_buffering )
834                 i_buffering_date = cmd.i_date;
835             else
836                 i_buffering_date = -1;
837         }
838
839         if( p_ts->i_rate_date < 0 )
840             p_ts->i_rate_date = cmd.i_date;
841
842         p_ts->i_rate_delay = 0;
843         if( p_ts->i_rate_source != p_ts->i_rate )
844         {
845             const mtime_t i_duration = cmd.i_date - p_ts->i_rate_date;
846             p_ts->i_rate_delay = i_duration * p_ts->i_rate / p_ts->i_rate_source - i_duration;
847         }
848         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 )
849         {
850             const int canc = vlc_savecancel();
851
852             /* Auto reset to rate 1.0 */
853             msg_Warn( p_ts->p_input, "es out timeshift: auto reset rate to %d", p_ts->i_rate_source );
854
855             p_ts->i_cmd_delay = 0;
856             p_ts->i_buffering_delay = 0;
857
858             p_ts->i_rate_delay = 0;
859             p_ts->i_rate_date = -1;
860             p_ts->i_rate = p_ts->i_rate_source;
861
862             if( !es_out_SetRate( p_ts->p_out, p_ts->i_rate_source, p_ts->i_rate ) )
863             {
864                 vlc_value_t val = { .i_int = p_ts->i_rate };
865                 /* Warn back input
866                  * FIXME it is perfectly safe BUT it is ugly as it may hide a
867                  * rate change requested by user */
868                 input_ControlPush( p_ts->p_input, INPUT_CONTROL_SET_RATE, &val );
869             }
870
871             vlc_restorecancel( canc );
872         }
873         i_deadline = cmd.i_date + p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
874
875         vlc_cleanup_run();
876
877         /* Regulate the speed of command processing to the same one than
878          * reading  */
879         vlc_cleanup_push( cmd_cleanup_routine, &cmd );
880
881         mwait( i_deadline );
882
883         vlc_cleanup_pop();
884
885         /* Execute the command  */
886         const int canc = vlc_savecancel();
887         switch( cmd.i_type )
888         {
889         case C_ADD:
890             CmdExecuteAdd( p_ts->p_out, &cmd );
891             CmdCleanAdd( &cmd );
892             break;
893         case C_SEND:
894             CmdExecuteSend( p_ts->p_out, &cmd );
895             CmdCleanSend( &cmd );
896             break;
897         case C_CONTROL:
898             CmdExecuteControl( p_ts->p_out, &cmd );
899             CmdCleanControl( &cmd );
900             break;
901         case C_DEL:
902             CmdExecuteDel( p_ts->p_out, &cmd );
903             break;
904         default:
905             assert(0);
906             break;
907         }
908         vlc_restorecancel( canc );
909     }
910
911     return NULL;
912 }
913
914 /*****************************************************************************
915  *
916  *****************************************************************************/
917 static void CmdClean( ts_cmd_t *p_cmd )
918 {
919     switch( p_cmd->i_type )
920     {
921     case C_ADD:
922         CmdCleanAdd( p_cmd );
923         break;
924     case C_SEND:
925         CmdCleanSend( p_cmd );
926         break;
927     case C_CONTROL:
928         CmdCleanControl( p_cmd );
929         break;
930     case C_DEL:
931         break;
932     default:
933         assert(0);
934         break;
935     }
936 }
937
938 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
939 {
940     p_cmd->i_type = C_ADD;
941     p_cmd->i_date = mdate();
942     p_cmd->add.p_es = p_es;
943     if( b_copy )
944     {
945         p_cmd->add.p_fmt = malloc( sizeof(*p_fmt) );
946         if( !p_cmd->add.p_fmt )
947             return VLC_EGENERIC;
948         es_format_Copy( p_cmd->add.p_fmt, p_fmt );
949     }
950     else
951     {
952         p_cmd->add.p_fmt = (es_format_t*)p_fmt;
953     }
954     return VLC_SUCCESS;
955 }
956 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
957 {
958     p_cmd->add.p_es->p_es = es_out_Add( p_out, p_cmd->add.p_fmt );
959 }
960 static void CmdCleanAdd( ts_cmd_t *p_cmd )
961 {
962     es_format_Clean( p_cmd->add.p_fmt );
963     free( p_cmd->add.p_fmt );
964 }
965
966 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
967 {
968     p_cmd->i_type = C_SEND;
969     p_cmd->i_date = mdate();
970     p_cmd->send.p_es = p_es;
971     p_cmd->send.p_block = p_block;
972 }
973 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
974 {
975     block_t *p_block = p_cmd->send.p_block;
976
977     p_cmd->send.p_block = NULL;
978
979     if( p_block )
980     {
981         if( p_cmd->send.p_es->p_es )
982             return es_out_Send( p_out, p_cmd->send.p_es->p_es, p_block );
983         block_Release( p_block );
984     }
985     return VLC_EGENERIC;
986 }
987 static void CmdCleanSend( ts_cmd_t *p_cmd )
988 {
989     if( p_cmd->send.p_block )
990         block_Release( p_cmd->send.p_block );
991 }
992
993 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
994 {
995     p_cmd->i_type = C_DEL;
996     p_cmd->i_date = mdate();
997     p_cmd->del.p_es = p_es;
998     return VLC_SUCCESS;
999 }
1000 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
1001 {
1002     if( p_cmd->del.p_es->p_es )
1003         es_out_Del( p_out, p_cmd->del.p_es->p_es );
1004     free( p_cmd->del.p_es );
1005 }
1006
1007 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
1008 {
1009     p_cmd->i_type = C_CONTROL;
1010     p_cmd->i_date = mdate();
1011     p_cmd->control.i_query = i_query;
1012     p_cmd->control.p_meta  = NULL;
1013     p_cmd->control.p_epg = NULL;
1014     p_cmd->control.p_fmt = NULL;
1015
1016     switch( i_query )
1017     {
1018     /* Pass-through control */
1019     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
1020         p_cmd->control.b_bool = (bool)va_arg( args, int );
1021         break;
1022
1023     case ES_OUT_SET_MODE:    /* arg1= int                            */
1024     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1025     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1026         p_cmd->control.i_int = (int)va_arg( args, int );
1027         break;
1028
1029     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1030     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1031         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
1032         break;
1033
1034     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1035         p_cmd->control.i_int = (int)va_arg( args, int );
1036         p_cmd->control.i_i64 = (int64_t)va_arg( args, int64_t );
1037         break;
1038
1039     case ES_OUT_RESET_PCR:           /* no arg */
1040         break;
1041
1042     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
1043     {
1044         p_cmd->control.i_int = (int)va_arg( args, int );
1045         vlc_meta_t *p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t * );
1046
1047         if( b_copy )
1048         {
1049             p_cmd->control.p_meta = vlc_meta_New();
1050             if( !p_cmd->control.p_meta )
1051                 return VLC_EGENERIC;
1052             vlc_meta_Merge( p_cmd->control.p_meta, p_meta );
1053         }
1054         else
1055         {
1056             p_cmd->control.p_meta = p_meta;
1057         }
1058         break;
1059     }
1060
1061     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
1062     {
1063         p_cmd->control.i_int = (int)va_arg( args, int );
1064         vlc_epg_t *p_epg = (vlc_epg_t*)va_arg( args, vlc_epg_t * );
1065
1066         if( b_copy )
1067         {
1068             p_cmd->control.p_epg = vlc_epg_New( p_epg->psz_name );
1069             if( !p_cmd->control.p_epg )
1070                 return VLC_EGENERIC;
1071             for( int i = 0; i < p_epg->i_event; i++ )
1072             {
1073                 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
1074
1075                 vlc_epg_AddEvent( p_cmd->control.p_epg,
1076                                   p_evt->i_start, p_evt->i_duration,
1077                                   p_evt->psz_name,
1078                                   p_evt->psz_short_description, p_evt->psz_description );
1079             }
1080             vlc_epg_SetCurrent( p_cmd->control.p_epg,
1081                                 p_epg->p_current ? p_epg->p_current->i_start : -1 );
1082         }
1083         else
1084         {
1085             p_cmd->control.p_epg = p_epg;
1086         }
1087         break;
1088     }
1089
1090     /* Modified control */
1091     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1092     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1093     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1094         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1095         break;
1096
1097     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1098         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1099         p_cmd->control.b_bool = (bool)va_arg( args, int );
1100         break;
1101
1102     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1103     {
1104         p_cmd->control.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1105         es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
1106
1107         if( b_copy )
1108         {
1109             p_cmd->control.p_fmt = malloc( sizeof(*p_fmt) );
1110             if( !p_cmd->control.p_fmt )
1111                 return VLC_EGENERIC;
1112             es_format_Copy( p_cmd->control.p_fmt, p_fmt );
1113         }
1114         else
1115         {
1116             p_cmd->control.p_fmt = p_fmt;
1117         }
1118         break;
1119     }
1120
1121     default:
1122         assert(0);
1123         return VLC_EGENERIC;
1124     }
1125
1126     return VLC_SUCCESS;
1127 }
1128 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
1129 {
1130     const int i_query = p_cmd->control.i_query;
1131
1132     switch( i_query )
1133     {
1134     /* Pass-through control */
1135     case ES_OUT_SET_ACTIVE:  /* arg1= bool                     */
1136         return es_out_Control( p_out, i_query, p_cmd->control.b_bool );
1137
1138     case ES_OUT_SET_MODE:    /* arg1= int                            */
1139     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1140     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1141         return es_out_Control( p_out, i_query, p_cmd->control.i_int );
1142
1143     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1144     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1145         return es_out_Control( p_out, i_query, p_cmd->control.i_i64 );
1146
1147     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1148         return es_out_Control( p_out, i_query, p_cmd->control.i_int, p_cmd->control.i_i64 );
1149
1150     case ES_OUT_RESET_PCR:           /* no arg */
1151         return es_out_Control( p_out, i_query );
1152
1153     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=vlc_meta_t* */
1154         return es_out_Control( p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_meta );
1155
1156     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=vlc_epg_t* */
1157         return es_out_Control( p_out, i_query, p_cmd->control.i_int, p_cmd->control.p_epg );
1158
1159     /* Modified control */
1160     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1161     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1162     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1163         return es_out_Control( p_out, i_query, p_cmd->control.p_es->p_es );
1164
1165     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1166         return es_out_Control( p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.b_bool );
1167
1168     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1169         return es_out_Control( p_out, i_query, p_cmd->control.p_es->p_es, p_cmd->control.p_fmt );
1170
1171     default:
1172         assert(0);
1173         return VLC_EGENERIC;
1174     }
1175 }
1176 static void CmdCleanControl( ts_cmd_t *p_cmd )
1177 {
1178     if( p_cmd->control.p_meta )
1179         vlc_meta_Delete( p_cmd->control.p_meta );
1180     if( p_cmd->control.p_epg )
1181         vlc_epg_Delete( p_cmd->control.p_epg );
1182     if( p_cmd->control.p_fmt )
1183     {
1184         es_format_Clean( p_cmd->control.p_fmt );
1185         free( p_cmd->control.p_fmt );
1186     }
1187 }
1188
1189
1190 /*****************************************************************************
1191  * GetTmpFile/Path:
1192  *****************************************************************************/
1193 static char *GetTmpPath( char *psz_path )
1194 {
1195     if( psz_path && *psz_path )
1196     {
1197         /* Make sure that the path exists and is a directory */
1198         struct stat s;
1199         const int i_ret = utf8_stat( psz_path, &s );
1200
1201         if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
1202             return psz_path;
1203         else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
1204             return psz_path;
1205     }
1206     free( psz_path );
1207
1208     /* Create a suitable path */
1209 #if defined (WIN32) && !defined (UNDER_CE)
1210     const DWORD dwCount = GetTempPathW( 0, NULL );
1211     wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
1212     if( psw_path )
1213     {
1214         if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
1215         {
1216             free( psw_path );
1217
1218             psw_path = _wgetcwd( NULL, 0 );
1219         }
1220     }
1221
1222     psz_path = NULL;
1223     if( psw_path )
1224     {
1225         psz_path = FromWide( psw_path );
1226         while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
1227             psz_path[strlen( psz_path ) - 1] = '\0';
1228
1229         free( psw_path );
1230     }
1231
1232     if( !psz_path || *psz_path == '\0' )
1233     {
1234         free( psz_path );
1235         return strdup( "C:" );
1236     }
1237 #else
1238     psz_path = strdup( "/tmp" );
1239 #endif
1240
1241     return psz_path;
1242 }
1243
1244 static FILE *GetTmpFile( const char *psz_path )
1245 {
1246     char *psz_name;
1247     int fd;
1248     FILE *f;
1249
1250     /* */
1251     if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
1252         return NULL;
1253
1254     /* */
1255     fd = mkstemp( psz_name );
1256     free( psz_name );
1257
1258     if( fd < 0 )
1259         return NULL;
1260
1261     /* */
1262     f = fdopen( fd, "rw+" );
1263     if( !f )
1264         close( fd );
1265
1266     return f;
1267 }
1268