]> git.sesse.net Git - vlc/blob - src/input/es_out_timeshift.c
Removed now useless es_out_t::b_sout field.
[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 <assert.h>
34 #if defined (WIN32) && !defined (UNDER_CE)
35 #  include <direct.h>
36 #endif
37 #ifdef HAVE_SYS_STAT_H
38 #   include <sys/stat.h>
39 #endif
40
41 #include <vlc_common.h>
42 #include <vlc_charset.h>
43
44 #include <vlc_input.h>
45 #include <vlc_es_out.h>
46 #include <vlc_block.h>
47 #include "input_internal.h"
48 #include "es_out.h"
49 #include "es_out_timeshift.h"
50
51 /*****************************************************************************
52  * Local prototypes
53  *****************************************************************************/
54
55 /* XXX attribute_packed is (and MUST be) used ONLY to reduce memory usage */
56 #ifdef HAVE_ATTRIBUTE_PACKED
57 #   define attribute_packed __attribute__((__packed__))
58 #else
59 #   define attribute_packed
60 #endif
61
62 enum
63 {
64     C_ADD,
65     C_SEND,
66     C_DEL,
67     C_CONTROL,
68 };
69
70 typedef struct attribute_packed
71 {
72     es_out_id_t *p_es;
73     es_format_t *p_fmt;
74 } ts_cmd_add_t;
75
76 typedef struct attribute_packed
77 {
78     es_out_id_t *p_es;
79 } ts_cmd_del_t;
80
81 typedef struct attribute_packed
82 {
83     es_out_id_t *p_es;
84     block_t *p_block;
85     int     i_offset;  /* We do not use file > INT_MAX */
86 } ts_cmd_send_t;
87
88 typedef struct attribute_packed
89 {
90     int  i_query;
91
92     union
93     {
94         bool b_bool;
95         int  i_int;
96         int64_t i_i64;
97         es_out_id_t *p_es;
98         struct
99         {
100             int     i_int;
101             int64_t i_i64;
102         } int_i64;
103         struct
104         {
105             int        i_int;
106             vlc_meta_t *p_meta;
107         } int_meta;
108         struct
109         {
110             int       i_int;
111             vlc_epg_t *p_epg;
112         } int_epg;
113         struct
114         {
115             es_out_id_t *p_es;
116             bool        b_bool;
117         } es_bool;
118         struct
119         {
120             es_out_id_t *p_es;
121             es_format_t *p_fmt;
122         } es_fmt;
123         struct
124         {
125             /* FIXME Really too big (double make the whole thing too big) */
126             double  f_position;
127             mtime_t i_time;
128             mtime_t i_length;
129         } times;
130         struct
131         {
132             mtime_t i_pts_delay;
133             int     i_cr_average;
134         } jitter;
135     } u;
136 } ts_cmd_control_t;
137
138 typedef struct attribute_packed
139 {
140     int8_t  i_type;
141     mtime_t i_date;
142     union
143     {
144         ts_cmd_add_t     add;
145         ts_cmd_del_t     del;
146         ts_cmd_send_t    send;
147         ts_cmd_control_t control;
148     } u;
149 } ts_cmd_t;
150
151 typedef struct ts_storage_t ts_storage_t;
152 struct ts_storage_t
153 {
154     ts_storage_t *p_next;
155
156     /* */
157     char    *psz_file;  /* Filename */
158     size_t  i_file_max; /* Max size in bytes */
159     int64_t i_file_size;/* Current size in bytes */
160     FILE    *p_filew;   /* FILE handle for data writing */
161     FILE    *p_filer;   /* FILE handle for data reading */
162
163     /* */
164     int      i_cmd_r;
165     int      i_cmd_w;
166     int      i_cmd_max;
167     ts_cmd_t *p_cmd;
168 };
169
170 typedef struct
171 {
172     VLC_COMMON_MEMBERS
173
174     /* */
175     input_thread_t *p_input;
176     es_out_t       *p_out;
177     int64_t        i_tmp_size_max;
178     const char     *psz_tmp_path;
179
180     /* Lock for all following fields */
181     vlc_mutex_t    lock;
182     vlc_cond_t     wait;
183
184     /* */
185     bool           b_paused;
186     mtime_t        i_pause_date;
187
188     /* */
189     int            i_rate;
190     int            i_rate_source;
191     mtime_t        i_rate_date;
192     mtime_t        i_rate_delay;
193
194     /* */
195     mtime_t        i_buffering_delay;
196
197     /* */
198     ts_storage_t   *p_storage_r;
199     ts_storage_t   *p_storage_w;
200
201     mtime_t        i_cmd_delay;
202
203 } ts_thread_t;
204
205 struct es_out_id_t
206 {
207     es_out_id_t *p_es;
208 };
209
210 struct es_out_sys_t
211 {
212     input_thread_t *p_input;
213         es_out_t       *p_out;
214
215     /* Configuration */
216     int64_t        i_tmp_size_max;    /* Maximal temporary file size in byte */
217     char           *psz_tmp_path;     /* Path for temporary files */
218
219     /* Lock for all following fields */
220     vlc_mutex_t    lock;
221
222     /* */
223     bool           b_delayed;
224     ts_thread_t   *p_thread;
225
226     /* */
227     bool           b_input_paused;
228     bool           b_input_paused_source;
229     int            i_input_rate;
230     int            i_input_rate_source;
231
232     /* */
233     int            i_es;
234     es_out_id_t    **pp_es;
235 };
236
237 static es_out_id_t *Add    ( es_out_t *, const es_format_t * );
238 static int          Send   ( es_out_t *, es_out_id_t *, block_t * );
239 static void         Del    ( es_out_t *, es_out_id_t * );
240 static int          Control( es_out_t *, int i_query, va_list );
241 static void         Destroy( es_out_t * );
242
243 static int          TsStart( es_out_t * );
244 static void         TsAutoStop( es_out_t * );
245
246 static void         TsStop( ts_thread_t * );
247 static void         TsPushCmd( ts_thread_t *, ts_cmd_t * );
248 static int          TsPopCmdLocked( ts_thread_t *, ts_cmd_t *, bool b_flush );
249 static bool         TsHasCmd( ts_thread_t * );
250 static bool         TsIsUnused( ts_thread_t * );
251 static int          TsChangePause( ts_thread_t *, bool b_source_paused, bool b_paused, mtime_t i_date );
252 static int          TsChangeRate( ts_thread_t *, int i_src_rate, int i_rate );
253
254 static void         *TsRun( vlc_object_t * );
255
256 static ts_storage_t *TsStorageNew( const char *psz_path, int64_t i_tmp_size_max );
257 static void         TsStorageDelete( ts_storage_t * );
258 static void         TsStoragePack( ts_storage_t *p_storage );
259 static bool         TsStorageIsFull( ts_storage_t *, const ts_cmd_t *p_cmd );
260 static bool         TsStorageIsEmpty( ts_storage_t * );
261 static void         TsStoragePushCmd( ts_storage_t *, const ts_cmd_t *p_cmd, bool b_flush );
262 static void         TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush );
263
264 static void CmdClean( ts_cmd_t * );
265 static void cmd_cleanup_routine( void *p ) { CmdClean( p ); }
266
267 static int  CmdInitAdd    ( ts_cmd_t *, es_out_id_t *, const es_format_t *, bool b_copy );
268 static void CmdInitSend   ( ts_cmd_t *, es_out_id_t *, block_t * );
269 static int  CmdInitDel    ( ts_cmd_t *, es_out_id_t * );
270 static int  CmdInitControl( ts_cmd_t *, int i_query, va_list, bool b_copy );
271
272 /* */
273 static void CmdCleanAdd    ( ts_cmd_t * );
274 static void CmdCleanSend   ( ts_cmd_t * );
275 static void CmdCleanControl( ts_cmd_t *p_cmd );
276
277 /* XXX these functions will take the destination es_out_t */
278 static void CmdExecuteAdd    ( es_out_t *, ts_cmd_t * );
279 static int  CmdExecuteSend   ( es_out_t *, ts_cmd_t * );
280 static void CmdExecuteDel    ( es_out_t *, ts_cmd_t * );
281 static int  CmdExecuteControl( es_out_t *, ts_cmd_t * );
282
283 /* File helpers */
284 static char *GetTmpPath( char *psz_path );
285 static FILE *GetTmpFile( char **ppsz_file, const char *psz_path );
286
287 /*****************************************************************************
288  * input_EsOutTimeshiftNew:
289  *****************************************************************************/
290 es_out_t *input_EsOutTimeshiftNew( input_thread_t *p_input, es_out_t *p_next_out, int i_rate )
291 {
292     es_out_t *p_out = malloc( sizeof(*p_out) );
293     if( !p_out )
294         return NULL;
295
296     es_out_sys_t *p_sys = malloc( sizeof(*p_sys) );
297     if( !p_sys )
298     {
299         free( p_out );
300         return NULL;
301     }
302
303     /* */
304     p_out->pf_add     = Add;
305     p_out->pf_send    = Send;
306     p_out->pf_del     = Del;
307     p_out->pf_control = Control;
308     p_out->pf_destroy = Destroy;
309     p_out->p_sys      = p_sys;
310
311     /* */
312     p_sys->b_input_paused = false;
313     p_sys->b_input_paused_source = false;
314     p_sys->p_input = p_input;
315     p_sys->i_input_rate = i_rate;
316     p_sys->i_input_rate_source = i_rate;
317
318     p_sys->p_out = p_next_out;
319     vlc_mutex_init_recursive( &p_sys->lock );
320
321     p_sys->b_delayed = false;
322     p_sys->p_thread = NULL;
323
324     TAB_INIT( p_sys->i_es, p_sys->pp_es );
325
326     /* */
327     const int i_tmp_size_max = var_CreateGetInteger( p_input, "input-timeshift-granularity" );
328     if( i_tmp_size_max < 0 )
329         p_sys->i_tmp_size_max = 50*1024*1024;
330     else
331         p_sys->i_tmp_size_max = __MAX( i_tmp_size_max, 1*1024*1024 );
332     msg_Dbg( p_input, "using timeshift granularity of %d MBytes",
333              (int)p_sys->i_tmp_size_max/(1024*1024) );
334
335     char *psz_tmp_path = var_CreateGetNonEmptyString( p_input, "input-timeshift-path" );
336     p_sys->psz_tmp_path = GetTmpPath( psz_tmp_path );
337     msg_Dbg( p_input, "using timeshift path '%s'", p_sys->psz_tmp_path );
338
339 #if 0
340 #define S(t) msg_Err( p_input, "SIZEOF("#t")=%d", sizeof(t) )
341     S(ts_cmd_t);
342     S(ts_cmd_control_t);
343     S(ts_cmd_send_t);
344     S(ts_cmd_del_t);
345     S(ts_cmd_add_t);
346 #undef S
347 #endif
348
349     return p_out;
350 }
351
352 /*****************************************************************************
353  * Internal functions
354  *****************************************************************************/
355 static void Destroy( es_out_t *p_out )
356 {
357     es_out_sys_t *p_sys = p_out->p_sys;
358
359     if( p_sys->b_delayed )
360     {
361         TsStop( p_sys->p_thread );
362         p_sys->b_delayed = false;
363     }
364
365     while( p_sys->i_es > 0 )
366         Del( p_out, p_sys->pp_es[0] );
367     TAB_CLEAN( p_sys->i_es, p_sys->pp_es  );
368
369     free( p_sys->psz_tmp_path );
370     vlc_mutex_destroy( &p_sys->lock );
371     free( p_sys );
372     free( p_out );
373 }
374
375 static es_out_id_t *Add( es_out_t *p_out, const es_format_t *p_fmt )
376 {
377     es_out_sys_t *p_sys = p_out->p_sys;
378     ts_cmd_t cmd;
379
380     es_out_id_t *p_es = malloc( sizeof( *p_es ) );
381     if( !p_es )
382         return NULL;
383
384     vlc_mutex_lock( &p_sys->lock );
385
386     TsAutoStop( p_out );
387
388     if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
389     {
390         vlc_mutex_unlock( &p_sys->lock );
391         free( p_es );
392         return NULL;
393     }
394
395     TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );
396
397     if( p_sys->b_delayed )
398         TsPushCmd( p_sys->p_thread, &cmd );
399     else
400         CmdExecuteAdd( p_sys->p_out, &cmd );
401
402     vlc_mutex_unlock( &p_sys->lock );
403
404     return p_es;
405 }
406 static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
407 {
408     es_out_sys_t *p_sys = p_out->p_sys;
409     ts_cmd_t cmd;
410     int i_ret = VLC_SUCCESS;
411
412     vlc_mutex_lock( &p_sys->lock );
413
414     TsAutoStop( p_out );
415
416     CmdInitSend( &cmd, p_es, p_block );
417     if( p_sys->b_delayed )
418         TsPushCmd( p_sys->p_thread, &cmd );
419     else
420         i_ret = CmdExecuteSend( p_sys->p_out, &cmd) ;
421
422     vlc_mutex_unlock( &p_sys->lock );
423
424     return i_ret;
425 }
426 static void Del( es_out_t *p_out, es_out_id_t *p_es )
427 {
428     es_out_sys_t *p_sys = p_out->p_sys;
429     ts_cmd_t cmd;
430
431     vlc_mutex_lock( &p_sys->lock );
432
433     TsAutoStop( p_out );
434
435     CmdInitDel( &cmd, p_es );
436     if( p_sys->b_delayed )
437         TsPushCmd( p_sys->p_thread, &cmd );
438     else
439         CmdExecuteDel( p_sys->p_out, &cmd );
440
441     TAB_REMOVE( p_sys->i_es, p_sys->pp_es, p_es );
442
443     vlc_mutex_unlock( &p_sys->lock );
444 }
445
446 static int ControlLockedGetEmpty( es_out_t *p_out, bool *pb_empty )
447 {
448     es_out_sys_t *p_sys = p_out->p_sys;
449
450     if( p_sys->b_delayed && TsHasCmd( p_sys->p_thread ) )
451         *pb_empty = false;
452     else
453         *pb_empty = es_out_GetEmpty( p_sys->p_out );
454
455     return VLC_SUCCESS;
456 }
457 static int ControlLockedGetWakeup( es_out_t *p_out, mtime_t *pi_wakeup )
458 {
459     es_out_sys_t *p_sys = p_out->p_sys;
460
461     if( p_sys->b_delayed )
462     {
463         assert( !p_sys->p_input->p->b_can_pace_control );
464         *pi_wakeup = 0;
465     }
466     else
467     {
468         *pi_wakeup = es_out_GetWakeup( p_sys->p_out );
469     }
470
471     return VLC_SUCCESS;
472 }
473 static int ControlLockedGetBuffering( es_out_t *p_out, bool *pb_buffering )
474 {
475     es_out_sys_t *p_sys = p_out->p_sys;
476
477     if( p_sys->b_delayed )
478         *pb_buffering = true;
479     else
480         *pb_buffering = es_out_GetBuffering( p_sys->p_out );
481
482     return VLC_SUCCESS;
483 }
484 static int ControlLockedSetPauseState( es_out_t *p_out, bool b_source_paused, bool b_paused, mtime_t i_date )
485 {
486     es_out_sys_t *p_sys = p_out->p_sys;
487     int i_ret;
488
489     if( !p_sys->b_delayed && !b_source_paused == !b_paused )
490     {
491         i_ret = es_out_SetPauseState( p_sys->p_out, b_source_paused, b_paused, i_date );
492     }
493     else
494     {
495         i_ret = VLC_EGENERIC;
496         if( !p_sys->p_input->p->b_can_pace_control )
497         {
498             if( !p_sys->b_delayed )
499                 TsStart( p_out );
500             if( p_sys->b_delayed )
501                 i_ret = TsChangePause( p_sys->p_thread, b_source_paused, b_paused, i_date );
502         }
503         else
504         {
505             /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
506              * and so be able to advertize correctly pace control property in access
507              * module */
508             msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
509         }
510     }
511
512     if( !i_ret )
513     {
514         p_sys->b_input_paused_source = b_source_paused;
515         p_sys->b_input_paused = b_paused;
516     }
517     return i_ret;
518 }
519 static int ControlLockedSetRate( es_out_t *p_out, int i_src_rate, int i_rate )
520 {
521     es_out_sys_t *p_sys = p_out->p_sys;
522     int i_ret;
523
524     if( !p_sys->b_delayed && i_src_rate == i_rate )
525     {
526         i_ret = es_out_SetRate( p_sys->p_out, i_src_rate, i_rate );
527     }
528     else
529     {
530         i_ret = VLC_EGENERIC;
531         if( !p_sys->p_input->p->b_can_pace_control )
532         {
533             if( !p_sys->b_delayed )
534                 TsStart( p_out );
535             if( p_sys->b_delayed )
536                 i_ret = TsChangeRate( p_sys->p_thread, i_src_rate, i_rate );
537         }
538         else
539         {
540             /* XXX we may do it BUT it would be better to finish the clock clean up+improvments
541              * and so be able to advertize correctly pace control property in access
542              * module */
543             msg_Err( p_sys->p_input, "EsOutTimeshift does not work with streams that have space control" );
544         }
545
546     }
547
548     if( !i_ret )
549     {
550         p_sys->i_input_rate_source = i_src_rate;
551         p_sys->i_input_rate = i_rate;
552     }
553     return i_ret;
554 }
555 static int ControlLockedSetTime( es_out_t *p_out, mtime_t i_date )
556 {
557     es_out_sys_t *p_sys = p_out->p_sys;
558
559     if( !p_sys->b_delayed )
560         return es_out_SetTime( p_sys->p_out, i_date );
561
562     /* TODO */
563     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support time change" );
564     return VLC_EGENERIC;
565 }
566 static int ControlLockedSetFrameNext( es_out_t *p_out )
567 {
568     es_out_sys_t *p_sys = p_out->p_sys;
569
570     return es_out_SetFrameNext( p_sys->p_out );
571 }
572
573 static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
574 {
575     es_out_sys_t *p_sys = p_out->p_sys;
576
577     switch( i_query )
578     {
579     /* Invalid query for this es_out level */
580     case ES_OUT_SET_ES_BY_ID:
581     case ES_OUT_RESTART_ES_BY_ID:
582     case ES_OUT_SET_ES_DEFAULT_BY_ID:
583     case ES_OUT_GET_ES_OBJECTS_BY_ID:
584     case ES_OUT_SET_DELAY:
585     case ES_OUT_SET_RECORD_STATE:
586         assert(0);
587         return VLC_EGENERIC;
588
589     /* Pass-through control */
590     case ES_OUT_SET_MODE:
591     case ES_OUT_SET_GROUP:
592     case ES_OUT_SET_PCR:
593     case ES_OUT_SET_GROUP_PCR:
594     case ES_OUT_RESET_PCR:
595     case ES_OUT_SET_NEXT_DISPLAY_TIME:
596     case ES_OUT_SET_GROUP_META:
597     case ES_OUT_SET_GROUP_EPG:
598     case ES_OUT_SET_ES_SCRAMBLED_STATE:
599     case ES_OUT_DEL_GROUP:
600     case ES_OUT_SET_META:
601     case ES_OUT_SET_ES:
602     case ES_OUT_RESTART_ES:
603     case ES_OUT_SET_ES_DEFAULT:
604     case ES_OUT_SET_ES_STATE:
605     case ES_OUT_SET_ES_FMT:
606     case ES_OUT_SET_TIMES:
607     case ES_OUT_SET_JITTER:
608     {
609         ts_cmd_t cmd;
610         if( CmdInitControl( &cmd, i_query, args, p_sys->b_delayed ) )
611             return VLC_EGENERIC;
612         if( p_sys->b_delayed )
613         {
614             TsPushCmd( p_sys->p_thread, &cmd );
615             return VLC_SUCCESS;
616         }
617         return CmdExecuteControl( p_sys->p_out, &cmd );
618     }
619
620     /* Special control when delayed */
621     case ES_OUT_GET_ES_STATE:
622     {
623         es_out_id_t *p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
624         bool *pb_enabled = (bool*)va_arg( args, bool* );
625
626         if( p_sys->b_delayed )
627         {
628             *pb_enabled = true;
629             return VLC_SUCCESS;
630         }
631         return es_out_Control( p_sys->p_out, ES_OUT_GET_ES_STATE, p_es->p_es, pb_enabled );
632     }
633
634     /* Special internal input control */
635     case ES_OUT_GET_EMPTY:
636     {
637         bool *pb_empty = (bool*)va_arg( args, bool* );
638         return ControlLockedGetEmpty( p_out, pb_empty );
639     }
640     case ES_OUT_GET_WAKE_UP: /* TODO ? */
641     {
642         mtime_t *pi_wakeup = (mtime_t*)va_arg( args, mtime_t* );
643         return ControlLockedGetWakeup( p_out, pi_wakeup );
644     }
645     case ES_OUT_GET_BUFFERING:
646     {
647         bool *pb_buffering = (bool *)va_arg( args, bool* );
648         return ControlLockedGetBuffering( p_out, pb_buffering );
649     }
650     case ES_OUT_SET_PAUSE_STATE:
651     {
652         const bool b_source_paused = (bool)va_arg( args, int );
653         const bool b_paused = (bool)va_arg( args, int );
654         const mtime_t i_date = (mtime_t) va_arg( args, mtime_t );
655
656         return ControlLockedSetPauseState( p_out, b_source_paused, b_paused, i_date );
657     }
658     case ES_OUT_SET_RATE:
659     {
660         const int i_src_rate = (int)va_arg( args, int );
661         const int i_rate = (int)va_arg( args, int );
662
663         return ControlLockedSetRate( p_out, i_src_rate, i_rate );
664     }
665     case ES_OUT_SET_TIME:
666     {
667         const mtime_t i_date = (mtime_t)va_arg( args, mtime_t );
668
669         return ControlLockedSetTime( p_out, i_date );
670     }
671     case ES_OUT_SET_FRAME_NEXT:
672     {
673         return ControlLockedSetFrameNext( p_out );
674     }
675     case ES_OUT_GET_PCR_SYSTEM:
676     {
677         if( p_sys->b_delayed )
678             return VLC_EGENERIC;
679
680         mtime_t *pi_system = (mtime_t*)va_arg( args, mtime_t * );
681         return es_out_ControlGetPcrSystem( p_sys->p_out, pi_system );
682     }
683     case ES_OUT_MODIFY_PCR_SYSTEM:
684     {
685         const bool    b_absolute = va_arg( args, int );
686         const mtime_t i_system   = va_arg( args, mtime_t );
687
688         if( b_absolute && p_sys->b_delayed )
689             return VLC_EGENERIC;
690
691         return es_out_ControlModifyPcrSystem( p_sys->p_out, b_absolute, i_system );
692     }
693     default:
694         msg_Err( p_sys->p_input, "Unknown es_out_Control query !" );
695         assert(0);
696         return VLC_EGENERIC;
697     }
698 }
699 static int Control( es_out_t *p_out, int i_query, va_list args )
700 {
701     es_out_sys_t *p_sys = p_out->p_sys;
702     int i_ret;
703
704     vlc_mutex_lock( &p_sys->lock );
705
706     TsAutoStop( p_out );
707
708     i_ret = ControlLocked( p_out, i_query, args );
709
710     vlc_mutex_unlock( &p_sys->lock );
711
712     return i_ret;
713 }
714
715 /*****************************************************************************
716  *
717  *****************************************************************************/
718 static void TsDestructor( vlc_object_t *p_this )
719 {
720     ts_thread_t *p_ts = (ts_thread_t*)p_this;
721
722     vlc_cond_destroy( &p_ts->wait );
723     vlc_mutex_destroy( &p_ts->lock );
724 }
725 static int TsStart( es_out_t *p_out )
726 {
727     es_out_sys_t *p_sys = p_out->p_sys;
728     ts_thread_t *p_ts;
729
730     assert( !p_sys->b_delayed );
731
732     p_sys->p_thread = p_ts = vlc_custom_create( p_sys->p_input, sizeof(ts_thread_t),
733                                                 VLC_OBJECT_GENERIC, "es out timeshift" );
734     if( !p_ts )
735         return VLC_EGENERIC;
736
737     p_ts->i_tmp_size_max = p_sys->i_tmp_size_max;
738     p_ts->psz_tmp_path = p_sys->psz_tmp_path;
739     p_ts->p_input = p_sys->p_input;
740     p_ts->p_out = p_sys->p_out;
741     vlc_mutex_init( &p_ts->lock );
742     vlc_cond_init( &p_ts->wait );
743     p_ts->b_paused = p_sys->b_input_paused && !p_sys->b_input_paused_source;
744     p_ts->i_pause_date = p_ts->b_paused ? mdate() : -1;
745     p_ts->i_rate_source = p_sys->i_input_rate_source;
746     p_ts->i_rate        = p_sys->i_input_rate;
747     p_ts->i_rate_date = -1;
748     p_ts->i_rate_delay = 0;
749     p_ts->i_buffering_delay = 0;
750     p_ts->i_cmd_delay = 0;
751     p_ts->p_storage_r = NULL;
752     p_ts->p_storage_w = NULL;
753
754     vlc_object_set_destructor( p_ts, TsDestructor );
755
756     p_sys->b_delayed = true;
757     if( vlc_thread_create( p_ts, "es out timeshift",
758                            TsRun, VLC_THREAD_PRIORITY_INPUT ) )
759     {
760         msg_Err( p_sys->p_input, "cannot create input thread" );
761
762         vlc_object_release( p_ts );
763
764         p_sys->b_delayed = false;
765         return VLC_EGENERIC;
766     }
767
768     return VLC_SUCCESS;
769 }
770 static void TsAutoStop( es_out_t *p_out )
771 {
772     es_out_sys_t *p_sys = p_out->p_sys;
773
774     if( !p_sys->b_delayed || !TsIsUnused( p_sys->p_thread ) )
775         return;
776
777     msg_Warn( p_sys->p_input, "es out timeshift: auto stop" );
778     TsStop( p_sys->p_thread );
779
780     p_sys->b_delayed = false;
781 }
782 static void TsStop( ts_thread_t *p_ts )
783 {
784     vlc_object_kill( p_ts );
785     vlc_thread_join( p_ts );
786
787     vlc_mutex_lock( &p_ts->lock );
788     for( ;; )
789     {
790         ts_cmd_t cmd;
791
792         if( TsPopCmdLocked( p_ts, &cmd, true ) )
793             break;
794
795         CmdClean( &cmd );
796     }
797     assert( !p_ts->p_storage_r || !p_ts->p_storage_r->p_next );
798     if( p_ts->p_storage_r )
799         TsStorageDelete( p_ts->p_storage_r );
800     vlc_mutex_unlock( &p_ts->lock );
801
802     vlc_object_release( p_ts );
803 }
804 static void TsPushCmd( ts_thread_t *p_ts, ts_cmd_t *p_cmd )
805 {
806     vlc_mutex_lock( &p_ts->lock );
807
808     if( !p_ts->p_storage_w || TsStorageIsFull( p_ts->p_storage_w, p_cmd ) )
809     {
810         ts_storage_t *p_storage = TsStorageNew( p_ts->psz_tmp_path, p_ts->i_tmp_size_max );
811
812         if( !p_storage )
813         {
814             CmdClean( p_cmd );
815             vlc_mutex_unlock( &p_ts->lock );
816             /* TODO warn the user (but only once) */
817             return;
818         }
819
820         if( !p_ts->p_storage_w )
821         {
822             p_ts->p_storage_r = p_ts->p_storage_w = p_storage;
823         }
824         else
825         {
826             TsStoragePack( p_ts->p_storage_w );
827             p_ts->p_storage_w->p_next = p_storage;
828             p_ts->p_storage_w = p_storage;
829         }
830     }
831
832     /* TODO return error and warn the user (but only once) */
833     TsStoragePushCmd( p_ts->p_storage_w, p_cmd, p_ts->p_storage_r == p_ts->p_storage_w );
834
835     vlc_cond_signal( &p_ts->wait );
836
837     vlc_mutex_unlock( &p_ts->lock );
838 }
839 static int TsPopCmdLocked( ts_thread_t *p_ts, ts_cmd_t *p_cmd, bool b_flush )
840 {
841     vlc_assert_locked( &p_ts->lock );
842
843     if( TsStorageIsEmpty( p_ts->p_storage_r ) )
844         return VLC_EGENERIC;
845
846     TsStoragePopCmd( p_ts->p_storage_r, p_cmd, b_flush );
847
848     while( p_ts->p_storage_r && TsStorageIsEmpty( p_ts->p_storage_r ) )
849     {
850         ts_storage_t *p_next = p_ts->p_storage_r->p_next;
851         if( !p_next )
852             break;
853
854         TsStorageDelete( p_ts->p_storage_r );
855         p_ts->p_storage_r = p_next;
856     }
857
858     return VLC_SUCCESS;
859 }
860 static bool TsHasCmd( ts_thread_t *p_ts )
861 {
862     bool b_cmd;
863
864     vlc_mutex_lock( &p_ts->lock );
865     b_cmd =  TsStorageIsEmpty( p_ts->p_storage_r );
866     vlc_mutex_unlock( &p_ts->lock );
867
868     return b_cmd;
869 }
870 static bool TsIsUnused( ts_thread_t *p_ts )
871 {
872     bool b_unused;
873
874     vlc_mutex_lock( &p_ts->lock );
875     b_unused = !p_ts->b_paused &&
876                p_ts->i_rate == p_ts->i_rate_source &&
877                TsStorageIsEmpty( p_ts->p_storage_r );
878     vlc_mutex_unlock( &p_ts->lock );
879
880     return b_unused;
881 }
882 static int TsChangePause( ts_thread_t *p_ts, bool b_source_paused, bool b_paused, mtime_t i_date )
883 {
884     vlc_mutex_lock( &p_ts->lock );
885
886     int i_ret;
887     if( b_paused )
888     {
889         assert( !b_source_paused );
890         i_ret = es_out_SetPauseState( p_ts->p_out, true, true, i_date );
891     }
892     else
893     {
894         i_ret = es_out_SetPauseState( p_ts->p_out, false, false, i_date );
895     }
896
897     if( !i_ret )
898     {
899         if( !b_paused )
900         {
901             assert( p_ts->i_pause_date > 0 );
902
903             p_ts->i_cmd_delay += i_date - p_ts->i_pause_date;
904         }
905
906         p_ts->b_paused = b_paused;
907         p_ts->i_pause_date = i_date;
908
909         vlc_cond_signal( &p_ts->wait );
910     }
911     vlc_mutex_unlock( &p_ts->lock );
912     return i_ret;
913 }
914 static int TsChangeRate( ts_thread_t *p_ts, int i_src_rate, int i_rate )
915 {
916     int i_ret;
917
918     vlc_mutex_lock( &p_ts->lock );
919     p_ts->i_cmd_delay += p_ts->i_rate_delay;
920
921     p_ts->i_rate_date = -1;
922     p_ts->i_rate_delay = 0;
923     p_ts->i_rate = i_rate;
924     p_ts->i_rate_source = i_src_rate;
925
926     i_ret = es_out_SetRate( p_ts->p_out, i_rate, i_rate );
927     vlc_mutex_unlock( &p_ts->lock );
928
929     return i_ret;
930 }
931
932 static void *TsRun( vlc_object_t *p_thread )
933 {
934     ts_thread_t *p_ts = (ts_thread_t*)p_thread;
935     mtime_t i_buffering_date = -1;
936
937     for( ;; )
938     {
939         ts_cmd_t cmd;
940         mtime_t  i_deadline;
941         bool b_buffering;
942
943         /* Pop a command to execute */
944         vlc_mutex_lock( &p_ts->lock );
945         mutex_cleanup_push( &p_ts->lock );
946
947         for( ;; )
948         {
949             const int canc = vlc_savecancel();
950             b_buffering = es_out_GetBuffering( p_ts->p_out );
951
952             if( ( !p_ts->b_paused || b_buffering ) && !TsPopCmdLocked( p_ts, &cmd, false ) )
953             {
954                 vlc_restorecancel( canc );
955                 break;
956             }
957             vlc_restorecancel( canc );
958
959             vlc_cond_wait( &p_ts->wait, &p_ts->lock );
960         }
961
962         if( b_buffering && i_buffering_date < 0 )
963         {
964             i_buffering_date = cmd.i_date;
965         }
966         else if( i_buffering_date > 0 )
967         {
968             p_ts->i_buffering_delay += i_buffering_date - cmd.i_date; /* It is < 0 */
969             if( b_buffering )
970                 i_buffering_date = cmd.i_date;
971             else
972                 i_buffering_date = -1;
973         }
974
975         if( p_ts->i_rate_date < 0 )
976             p_ts->i_rate_date = cmd.i_date;
977
978         p_ts->i_rate_delay = 0;
979         if( p_ts->i_rate_source != p_ts->i_rate )
980         {
981             const mtime_t i_duration = cmd.i_date - p_ts->i_rate_date;
982             p_ts->i_rate_delay = i_duration * p_ts->i_rate / p_ts->i_rate_source - i_duration;
983         }
984         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 )
985         {
986             const int canc = vlc_savecancel();
987
988             /* Auto reset to rate 1.0 */
989             msg_Warn( p_ts->p_input, "es out timeshift: auto reset rate to %d", p_ts->i_rate_source );
990
991             p_ts->i_cmd_delay = 0;
992             p_ts->i_buffering_delay = 0;
993
994             p_ts->i_rate_delay = 0;
995             p_ts->i_rate_date = -1;
996             p_ts->i_rate = p_ts->i_rate_source;
997
998             if( !es_out_SetRate( p_ts->p_out, p_ts->i_rate_source, p_ts->i_rate ) )
999             {
1000                 vlc_value_t val = { .i_int = p_ts->i_rate };
1001                 /* Warn back input
1002                  * FIXME it is perfectly safe BUT it is ugly as it may hide a
1003                  * rate change requested by user */
1004                 input_ControlPush( p_ts->p_input, INPUT_CONTROL_SET_RATE, &val );
1005             }
1006
1007             vlc_restorecancel( canc );
1008         }
1009         i_deadline = cmd.i_date + p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
1010
1011         vlc_cleanup_run();
1012
1013         /* Regulate the speed of command processing to the same one than
1014          * reading  */
1015         vlc_cleanup_push( cmd_cleanup_routine, &cmd );
1016
1017         mwait( i_deadline );
1018
1019         vlc_cleanup_pop();
1020
1021         /* Execute the command  */
1022         const int canc = vlc_savecancel();
1023         switch( cmd.i_type )
1024         {
1025         case C_ADD:
1026             CmdExecuteAdd( p_ts->p_out, &cmd );
1027             CmdCleanAdd( &cmd );
1028             break;
1029         case C_SEND:
1030             CmdExecuteSend( p_ts->p_out, &cmd );
1031             CmdCleanSend( &cmd );
1032             break;
1033         case C_CONTROL:
1034             CmdExecuteControl( p_ts->p_out, &cmd );
1035             CmdCleanControl( &cmd );
1036             break;
1037         case C_DEL:
1038             CmdExecuteDel( p_ts->p_out, &cmd );
1039             break;
1040         default:
1041             assert(0);
1042             break;
1043         }
1044         vlc_restorecancel( canc );
1045     }
1046
1047     return NULL;
1048 }
1049
1050 /*****************************************************************************
1051  *
1052  *****************************************************************************/
1053 static ts_storage_t *TsStorageNew( const char *psz_tmp_path, int64_t i_tmp_size_max )
1054 {
1055     ts_storage_t *p_storage = calloc( 1, sizeof(ts_storage_t) );
1056     if( !p_storage )
1057         return NULL;
1058
1059     /* */
1060     p_storage->p_next = NULL;
1061
1062     /* */
1063     p_storage->i_file_max = i_tmp_size_max;
1064     p_storage->i_file_size = 0;
1065     p_storage->p_filew = GetTmpFile( &p_storage->psz_file, psz_tmp_path );
1066     if( p_storage->psz_file )
1067         p_storage->p_filer = utf8_fopen( p_storage->psz_file, "rb" );
1068
1069     /* */
1070     p_storage->i_cmd_w = 0;
1071     p_storage->i_cmd_r = 0;
1072     p_storage->i_cmd_max = 30000;
1073     p_storage->p_cmd = malloc( p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) );
1074     //fprintf( stderr, "\nSTORAGE name=%s size=%d kbytes\n", p_storage->psz_file, p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) /1024 );
1075
1076     if( !p_storage->p_cmd || !p_storage->p_filew || !p_storage->p_filer )
1077     {
1078         TsStorageDelete( p_storage );
1079         return NULL;
1080     }
1081     return p_storage;
1082 }
1083 static void TsStorageDelete( ts_storage_t *p_storage )
1084 {
1085     while( p_storage->i_cmd_r < p_storage->i_cmd_w )
1086     {
1087         ts_cmd_t cmd;
1088
1089         TsStoragePopCmd( p_storage, &cmd, true );
1090
1091         CmdClean( &cmd );
1092     }
1093     free( p_storage->p_cmd );
1094
1095     if( p_storage->p_filer )
1096         fclose( p_storage->p_filer );
1097     if( p_storage->p_filew )
1098         fclose( p_storage->p_filew );
1099
1100     if( p_storage->psz_file )
1101     {
1102         utf8_unlink( p_storage->psz_file );
1103         free( p_storage->psz_file );
1104     }
1105
1106     free( p_storage );
1107 }
1108 static void TsStoragePack( ts_storage_t *p_storage )
1109 {
1110     /* Try to release a bit of memory */
1111     if( p_storage->i_cmd_w >= p_storage->i_cmd_max )
1112         return;
1113
1114     p_storage->i_cmd_max = __MAX( p_storage->i_cmd_w, 1 );
1115
1116     ts_cmd_t *p_new = realloc( p_storage->p_cmd, p_storage->i_cmd_max * sizeof(*p_storage->p_cmd) );
1117     if( p_new )
1118         p_storage->p_cmd = p_new;
1119 }
1120 static bool TsStorageIsFull( ts_storage_t *p_storage, const ts_cmd_t *p_cmd )
1121 {
1122     if( p_cmd && p_cmd->i_type == C_SEND && p_storage->i_cmd_w > 0 )
1123     {
1124         size_t i_size = sizeof(*p_cmd->u.send.p_block) + p_cmd->u.send.p_block->i_buffer;
1125
1126         if( p_storage->i_file_size + i_size >= p_storage->i_file_max )
1127             return true;
1128     }
1129     return p_storage->i_cmd_w >= p_storage->i_cmd_max;
1130 }
1131 static bool TsStorageIsEmpty( ts_storage_t *p_storage )
1132 {
1133     return !p_storage || p_storage->i_cmd_r >= p_storage->i_cmd_w;
1134 }
1135 static void TsStoragePushCmd( ts_storage_t *p_storage, const ts_cmd_t *p_cmd, bool b_flush )
1136 {
1137     ts_cmd_t cmd = *p_cmd;
1138
1139     assert( !TsStorageIsFull( p_storage, p_cmd ) );
1140
1141     if( cmd.i_type == C_SEND )
1142     {
1143         block_t *p_block = cmd.u.send.p_block;
1144
1145         cmd.u.send.p_block = NULL;
1146         cmd.u.send.i_offset = ftell( p_storage->p_filew );
1147
1148         if( fwrite( p_block, sizeof(*p_block), 1, p_storage->p_filew ) != 1 )
1149         {
1150             block_Release( p_block );
1151             return;
1152         }
1153         p_storage->i_file_size += sizeof(*p_block);
1154         if( p_block->i_buffer > 0 )
1155         {
1156             if( fwrite( p_block->p_buffer, p_block->i_buffer, 1, p_storage->p_filew ) != 1 )
1157             {
1158                 block_Release( p_block );
1159                 return;
1160             }
1161         }
1162         p_storage->i_file_size += p_block->i_buffer;
1163         block_Release( p_block );
1164
1165         if( b_flush )
1166             fflush( p_storage->p_filew );
1167     }
1168     p_storage->p_cmd[p_storage->i_cmd_w++] = cmd;
1169 }
1170 static void TsStoragePopCmd( ts_storage_t *p_storage, ts_cmd_t *p_cmd, bool b_flush )
1171 {
1172     assert( !TsStorageIsEmpty( p_storage ) );
1173
1174     *p_cmd = p_storage->p_cmd[p_storage->i_cmd_r++];
1175     if( p_cmd->i_type == C_SEND )
1176     {
1177         block_t block;
1178
1179         if( !b_flush &&
1180             !fseek( p_storage->p_filer, p_cmd->u.send.i_offset, SEEK_SET ) &&
1181             fread( &block, sizeof(block), 1, p_storage->p_filer ) == 1 )
1182         {
1183             block_t *p_block = block_Alloc( block.i_buffer );
1184             if( p_block )
1185             {
1186                 p_block->i_dts      = block.i_dts;
1187                 p_block->i_pts      = block.i_pts;
1188                 p_block->i_flags    = block.i_flags;
1189                 p_block->i_length   = block.i_length;
1190                 p_block->i_rate     = block.i_rate;
1191                 p_block->i_nb_samples = block.i_nb_samples;
1192                 p_block->i_buffer = fread( p_block->p_buffer, 1, block.i_buffer, p_storage->p_filer );
1193             }
1194             p_cmd->u.send.p_block = p_block;
1195         }
1196         else
1197         {
1198             //fprintf( stderr, "TsStoragePopCmd: %m\n" );
1199             p_cmd->u.send.p_block = block_Alloc( 1 );
1200         }
1201     }
1202 }
1203
1204 /*****************************************************************************
1205  *
1206  *****************************************************************************/
1207 static void CmdClean( ts_cmd_t *p_cmd )
1208 {
1209     switch( p_cmd->i_type )
1210     {
1211     case C_ADD:
1212         CmdCleanAdd( p_cmd );
1213         break;
1214     case C_SEND:
1215         CmdCleanSend( p_cmd );
1216         break;
1217     case C_CONTROL:
1218         CmdCleanControl( p_cmd );
1219         break;
1220     case C_DEL:
1221         break;
1222     default:
1223         assert(0);
1224         break;
1225     }
1226 }
1227
1228 static int CmdInitAdd( ts_cmd_t *p_cmd, es_out_id_t *p_es, const es_format_t *p_fmt, bool b_copy )
1229 {
1230     p_cmd->i_type = C_ADD;
1231     p_cmd->i_date = mdate();
1232     p_cmd->u.add.p_es = p_es;
1233     if( b_copy )
1234     {
1235         p_cmd->u.add.p_fmt = malloc( sizeof(*p_fmt) );
1236         if( !p_cmd->u.add.p_fmt )
1237             return VLC_EGENERIC;
1238         es_format_Copy( p_cmd->u.add.p_fmt, p_fmt );
1239     }
1240     else
1241     {
1242         p_cmd->u.add.p_fmt = (es_format_t*)p_fmt;
1243     }
1244     return VLC_SUCCESS;
1245 }
1246 static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
1247 {
1248     p_cmd->u.add.p_es->p_es = es_out_Add( p_out, p_cmd->u.add.p_fmt );
1249 }
1250 static void CmdCleanAdd( ts_cmd_t *p_cmd )
1251 {
1252     es_format_Clean( p_cmd->u.add.p_fmt );
1253     free( p_cmd->u.add.p_fmt );
1254 }
1255
1256 static void CmdInitSend( ts_cmd_t *p_cmd, es_out_id_t *p_es, block_t *p_block )
1257 {
1258     p_cmd->i_type = C_SEND;
1259     p_cmd->i_date = mdate();
1260     p_cmd->u.send.p_es = p_es;
1261     p_cmd->u.send.p_block = p_block;
1262 }
1263 static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
1264 {
1265     block_t *p_block = p_cmd->u.send.p_block;
1266
1267     p_cmd->u.send.p_block = NULL;
1268
1269     if( p_block )
1270     {
1271         if( p_cmd->u.send.p_es->p_es )
1272             return es_out_Send( p_out, p_cmd->u.send.p_es->p_es, p_block );
1273         block_Release( p_block );
1274     }
1275     return VLC_EGENERIC;
1276 }
1277 static void CmdCleanSend( ts_cmd_t *p_cmd )
1278 {
1279     if( p_cmd->u.send.p_block )
1280         block_Release( p_cmd->u.send.p_block );
1281 }
1282
1283 static int CmdInitDel( ts_cmd_t *p_cmd, es_out_id_t *p_es )
1284 {
1285     p_cmd->i_type = C_DEL;
1286     p_cmd->i_date = mdate();
1287     p_cmd->u.del.p_es = p_es;
1288     return VLC_SUCCESS;
1289 }
1290 static void CmdExecuteDel( es_out_t *p_out, ts_cmd_t *p_cmd )
1291 {
1292     if( p_cmd->u.del.p_es->p_es )
1293         es_out_Del( p_out, p_cmd->u.del.p_es->p_es );
1294     free( p_cmd->u.del.p_es );
1295 }
1296
1297 static int CmdInitControl( ts_cmd_t *p_cmd, int i_query, va_list args, bool b_copy )
1298 {
1299     p_cmd->i_type = C_CONTROL;
1300     p_cmd->i_date = mdate();
1301     p_cmd->u.control.i_query = i_query;
1302
1303     switch( i_query )
1304     {
1305     /* Pass-through control */
1306     case ES_OUT_SET_MODE:    /* arg1= int                            */
1307     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1308     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1309         p_cmd->u.control.u.i_int = (int)va_arg( args, int );
1310         break;
1311
1312     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1313     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1314         p_cmd->u.control.u.i_i64 = (int64_t)va_arg( args, int64_t );
1315         break;
1316
1317     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1318         p_cmd->u.control.u.int_i64.i_int = (int)va_arg( args, int );
1319         p_cmd->u.control.u.int_i64.i_i64 = (int64_t)va_arg( args, int64_t );
1320         break;
1321
1322     case ES_OUT_SET_ES_SCRAMBLED_STATE:
1323         p_cmd->u.control.u.es_bool.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1324         p_cmd->u.control.u.es_bool.b_bool = (bool)va_arg( args, int );
1325         break;
1326
1327     case ES_OUT_RESET_PCR:           /* no arg */
1328         break;
1329
1330     case ES_OUT_SET_META:        /* arg1=const vlc_meta_t* */
1331     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=const vlc_meta_t* */
1332     {
1333         if( i_query == ES_OUT_SET_GROUP_META )
1334             p_cmd->u.control.u.int_meta.i_int = (int)va_arg( args, int );
1335         const vlc_meta_t *p_meta = va_arg( args, const vlc_meta_t * );
1336
1337         if( b_copy )
1338         {
1339             p_cmd->u.control.u.int_meta.p_meta = vlc_meta_New();
1340             if( !p_cmd->u.control.u.int_meta.p_meta )
1341                 return VLC_EGENERIC;
1342             vlc_meta_Merge( p_cmd->u.control.u.int_meta.p_meta, p_meta );
1343         }
1344         else
1345         {
1346             /* The cast is only needed to avoid warning */
1347             p_cmd->u.control.u.int_meta.p_meta = (vlc_meta_t*)p_meta;
1348         }
1349         break;
1350     }
1351
1352     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=const vlc_epg_t* */
1353     {
1354         p_cmd->u.control.u.int_epg.i_int = (int)va_arg( args, int );
1355         const vlc_epg_t *p_epg = va_arg( args, const vlc_epg_t * );
1356
1357         if( b_copy )
1358         {
1359             p_cmd->u.control.u.int_epg.p_epg = vlc_epg_New( p_epg->psz_name );
1360             if( !p_cmd->u.control.u.int_epg.p_epg )
1361                 return VLC_EGENERIC;
1362             for( int i = 0; i < p_epg->i_event; i++ )
1363             {
1364                 vlc_epg_event_t *p_evt = p_epg->pp_event[i];
1365
1366                 vlc_epg_AddEvent( p_cmd->u.control.u.int_epg.p_epg,
1367                                   p_evt->i_start, p_evt->i_duration,
1368                                   p_evt->psz_name,
1369                                   p_evt->psz_short_description, p_evt->psz_description );
1370             }
1371             vlc_epg_SetCurrent( p_cmd->u.control.u.int_epg.p_epg,
1372                                 p_epg->p_current ? p_epg->p_current->i_start : -1 );
1373         }
1374         else
1375         {
1376             /* The cast is only needed to avoid warning */
1377             p_cmd->u.control.u.int_epg.p_epg = (vlc_epg_t*)p_epg;
1378         }
1379         break;
1380     }
1381
1382     /* Modified control */
1383     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1384     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1385     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1386         p_cmd->u.control.u.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1387         break;
1388
1389     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1390         p_cmd->u.control.u.es_bool.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1391         p_cmd->u.control.u.es_bool.b_bool = (bool)va_arg( args, int );
1392         break;
1393
1394     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1395     {
1396         p_cmd->u.control.u.es_fmt.p_es = (es_out_id_t*)va_arg( args, es_out_id_t * );
1397         es_format_t *p_fmt = (es_format_t*)va_arg( args, es_format_t * );
1398
1399         if( b_copy )
1400         {
1401             p_cmd->u.control.u.es_fmt.p_fmt = malloc( sizeof(*p_fmt) );
1402             if( !p_cmd->u.control.u.es_fmt.p_fmt )
1403                 return VLC_EGENERIC;
1404             es_format_Copy( p_cmd->u.control.u.es_fmt.p_fmt, p_fmt );
1405         }
1406         else
1407         {
1408             p_cmd->u.control.u.es_fmt.p_fmt = p_fmt;
1409         }
1410         break;
1411     }
1412     case ES_OUT_SET_TIMES:
1413     {
1414         double f_position = (double)va_arg( args, double );
1415         mtime_t i_time = (mtime_t)va_arg( args, mtime_t );
1416         mtime_t i_length = (mtime_t)va_arg( args, mtime_t );
1417
1418         p_cmd->u.control.u.times.f_position = f_position;
1419         p_cmd->u.control.u.times.i_time = i_time;
1420         p_cmd->u.control.u.times.i_length = i_length;
1421         break;
1422     }
1423     case ES_OUT_SET_JITTER:
1424     {
1425         mtime_t i_pts_delay = (mtime_t)va_arg( args, mtime_t );
1426         int     i_cr_average = (int)va_arg( args, int );
1427
1428         p_cmd->u.control.u.jitter.i_pts_delay = i_pts_delay;
1429         p_cmd->u.control.u.jitter.i_cr_average = i_cr_average;
1430         break;
1431     }
1432
1433     default:
1434         assert(0);
1435         return VLC_EGENERIC;
1436     }
1437
1438     return VLC_SUCCESS;
1439 }
1440 static int CmdExecuteControl( es_out_t *p_out, ts_cmd_t *p_cmd )
1441 {
1442     const int i_query = p_cmd->u.control.i_query;
1443
1444     switch( i_query )
1445     {
1446     /* Pass-through control */
1447     case ES_OUT_SET_MODE:    /* arg1= int                            */
1448     case ES_OUT_SET_GROUP:   /* arg1= int                            */
1449     case ES_OUT_DEL_GROUP:   /* arg1=int i_group */
1450         return es_out_Control( p_out, i_query, p_cmd->u.control.u.i_int );
1451
1452     case ES_OUT_SET_PCR:                /* arg1=int64_t i_pcr(microsecond!) (using default group 0)*/
1453     case ES_OUT_SET_NEXT_DISPLAY_TIME:  /* arg1=int64_t i_pts(microsecond) */
1454         return es_out_Control( p_out, i_query, p_cmd->u.control.u.i_i64 );
1455
1456     case ES_OUT_SET_GROUP_PCR:          /* arg1= int i_group, arg2=int64_t i_pcr(microsecond!)*/
1457         return es_out_Control( p_out, i_query, p_cmd->u.control.u.int_i64.i_int,
1458                                                p_cmd->u.control.u.int_i64.i_i64 );
1459
1460     case ES_OUT_RESET_PCR:           /* no arg */
1461         return es_out_Control( p_out, i_query );
1462
1463     case ES_OUT_SET_GROUP_META:  /* arg1=int i_group arg2=const vlc_meta_t* */
1464         return es_out_Control( p_out, i_query, p_cmd->u.control.u.int_meta.i_int,
1465                                                p_cmd->u.control.u.int_meta.p_meta );
1466
1467     case ES_OUT_SET_GROUP_EPG:   /* arg1=int i_group arg2=const vlc_epg_t* */
1468         return es_out_Control( p_out, i_query, p_cmd->u.control.u.int_epg.i_int,
1469                                                p_cmd->u.control.u.int_epg.p_epg );
1470
1471     case ES_OUT_SET_ES_SCRAMBLED_STATE: /* arg1=int es_out_id_t* arg2=bool */
1472         return es_out_Control( p_out, i_query, p_cmd->u.control.u.es_bool.p_es->p_es,
1473                                                p_cmd->u.control.u.es_bool.b_bool );
1474
1475     case ES_OUT_SET_META:  /* arg1=const vlc_meta_t* */
1476         return es_out_Control( p_out, i_query, p_cmd->u.control.u.int_meta.p_meta );
1477
1478     /* Modified control */
1479     case ES_OUT_SET_ES:      /* arg1= es_out_id_t*                   */
1480     case ES_OUT_RESTART_ES:  /* arg1= es_out_id_t*                   */
1481     case ES_OUT_SET_ES_DEFAULT: /* arg1= es_out_id_t*                */
1482         return es_out_Control( p_out, i_query, p_cmd->u.control.u.p_es->p_es );
1483
1484     case ES_OUT_SET_ES_STATE:/* arg1= es_out_id_t* arg2=bool   */
1485         return es_out_Control( p_out, i_query, p_cmd->u.control.u.es_bool.p_es->p_es,
1486                                                p_cmd->u.control.u.es_bool.b_bool );
1487
1488     case ES_OUT_SET_ES_FMT:     /* arg1= es_out_id_t* arg2=es_format_t* */
1489         return es_out_Control( p_out, i_query, p_cmd->u.control.u.es_fmt.p_es->p_es,
1490                                                p_cmd->u.control.u.es_fmt.p_fmt );
1491
1492     case ES_OUT_SET_TIMES:
1493         return es_out_Control( p_out, i_query, p_cmd->u.control.u.times.f_position,
1494                                                p_cmd->u.control.u.times.i_time,
1495                                                p_cmd->u.control.u.times.i_length );
1496     case ES_OUT_SET_JITTER:
1497         return es_out_Control( p_out, i_query, p_cmd->u.control.u.jitter.i_pts_delay,
1498                                                p_cmd->u.control.u.jitter.i_cr_average );
1499
1500     default:
1501         assert(0);
1502         return VLC_EGENERIC;
1503     }
1504 }
1505 static void CmdCleanControl( ts_cmd_t *p_cmd )
1506 {
1507     if( ( p_cmd->u.control.i_query == ES_OUT_SET_GROUP_META ||
1508           p_cmd->u.control.i_query == ES_OUT_SET_META ) &&
1509         p_cmd->u.control.u.int_meta.p_meta )
1510     {
1511         vlc_meta_Delete( p_cmd->u.control.u.int_meta.p_meta );
1512     }
1513     else if( p_cmd->u.control.i_query == ES_OUT_SET_GROUP_EPG &&
1514              p_cmd->u.control.u.int_epg.p_epg )
1515     {
1516         vlc_epg_Delete( p_cmd->u.control.u.int_epg.p_epg );
1517     }
1518     else if( p_cmd->u.control.i_query == ES_OUT_SET_ES_FMT &&
1519              p_cmd->u.control.u.es_fmt.p_fmt )
1520     {
1521         es_format_Clean( p_cmd->u.control.u.es_fmt.p_fmt );
1522         free( p_cmd->u.control.u.es_fmt.p_fmt );
1523     }
1524 }
1525
1526
1527 /*****************************************************************************
1528  * GetTmpFile/Path:
1529  *****************************************************************************/
1530 static char *GetTmpPath( char *psz_path )
1531 {
1532     if( psz_path && *psz_path )
1533     {
1534         /* Make sure that the path exists and is a directory */
1535         struct stat s;
1536         const int i_ret = utf8_stat( psz_path, &s );
1537
1538         if( i_ret < 0 && !utf8_mkdir( psz_path, 0600 ) )
1539             return psz_path;
1540         else if( i_ret == 0 && ( s.st_mode & S_IFDIR ) )
1541             return psz_path;
1542     }
1543     free( psz_path );
1544
1545     /* Create a suitable path */
1546 #if defined (WIN32) && !defined (UNDER_CE)
1547     const DWORD dwCount = GetTempPathW( 0, NULL );
1548     wchar_t *psw_path = calloc( dwCount + 1, sizeof(wchar_t) );
1549     if( psw_path )
1550     {
1551         if( GetTempPathW( dwCount + 1, psw_path ) <= 0 )
1552         {
1553             free( psw_path );
1554
1555             psw_path = _wgetcwd( NULL, 0 );
1556         }
1557     }
1558
1559     psz_path = NULL;
1560     if( psw_path )
1561     {
1562         psz_path = FromWide( psw_path );
1563         while( psz_path && *psz_path && psz_path[strlen( psz_path ) - 1] == '\\' )
1564             psz_path[strlen( psz_path ) - 1] = '\0';
1565
1566         free( psw_path );
1567     }
1568
1569     if( !psz_path || *psz_path == '\0' )
1570     {
1571         free( psz_path );
1572         return strdup( "C:" );
1573     }
1574 #else
1575     psz_path = strdup( "/tmp" );
1576 #endif
1577
1578     return psz_path;
1579 }
1580
1581 static FILE *GetTmpFile( char **ppsz_file, const char *psz_path )
1582 {
1583     char *psz_name;
1584     int fd;
1585     FILE *f;
1586
1587     /* */
1588     *ppsz_file = NULL;
1589     if( asprintf( &psz_name, "%s/vlc-timeshift.XXXXXX", psz_path ) < 0 )
1590         return NULL;
1591
1592     /* */
1593     fd = utf8_mkstemp( psz_name );
1594     *ppsz_file = psz_name;
1595
1596     if( fd < 0 )
1597         return NULL;
1598
1599     /* */
1600     f = fdopen( fd, "w+b" );
1601     if( !f )
1602         close( fd );
1603
1604     return f;
1605 }
1606