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