Coverage Report

Created: 2024-09-03 07:05

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