es_out.c 104 KB
Newer Older
1 2 3
/*****************************************************************************
 * es_out.c: Es Out handler for input.
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2003-2004 VLC authors and VideoLAN
5 6 7
 * $Id$
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8
 *          Jean-Paul Saman <jpsaman #_at_# m2x dot nl>
9
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
10 11 12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
13 14 15 16
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
17 18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
19
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
20 21 22
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <stdio.h>
33 34
#include <assert.h>
#include <vlc_common.h>
35

Clément Stenac's avatar
Clément Stenac committed
36 37 38
#include <vlc_input.h>
#include <vlc_es_out.h>
#include <vlc_block.h>
39
#include <vlc_aout.h>
40
#include <vlc_fourcc.h>
41
#include <vlc_meta.h>
42 43

#include "input_internal.h"
44 45 46
#include "clock.h"
#include "decoder.h"
#include "es_out.h"
47
#include "event.h"
48
#include "info.h"
49
#include "item.h"
50

51 52
#include "../stream_output/stream_output.h"

53
#include <vlc_iso_lang.h>
54
/* FIXME we should find a better way than including that */
55
#include "../text/iso-639_def.h"
56 57 58 59 60 61 62 63 64 65 66 67

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
typedef struct
{
    /* Program ID */
    int i_id;

    /* Number of es for this pgrm */
    int i_es;

68
    bool b_selected;
69
    bool b_scrambled;
70 71

    /* Clock for this program */
72
    input_clock_t *p_clock;
73

74
    vlc_meta_t *p_meta;
75 76 77 78 79 80 81 82
} es_out_pgrm_t;

struct es_out_id_t
{
    /* ES ID */
    int       i_id;
    es_out_pgrm_t *p_pgrm;

83 84 85
    /* */
    bool b_scrambled;

86 87 88 89 90
    /* Channel in the track type */
    int         i_channel;
    es_format_t fmt;
    char        *psz_language;
    char        *psz_language_code;
91

92
    decoder_t   *p_dec;
93
    decoder_t   *p_dec_record;
94 95

    /* Fields for Video with CC */
96 97 98 99 100 101
    struct
    {
        vlc_fourcc_t type;
        uint64_t     i_bitmap;    /* channels bitmap */
        es_out_id_t  *pp_es[64]; /* a max of 64 chans for CEA708 */
    } cc;
102 103 104

    /* Field for CC track from a master video */
    es_out_id_t *p_master;
105 106 107

    /* ID for the meta data */
    int         i_meta_id;
108 109
};

110 111 112 113 114 115 116
typedef struct
{
    int         i_count;    /* es count */
    es_out_id_t *p_main_es; /* current main es */
    enum es_out_policy_e e_policy;

    /* Parameters used for es selection */
117
    bool        b_autoselect; /* if we want to select an es when no user prefs */
118 119 120 121 122 123
    int         i_id;       /* es id as set by es fmt.id */
    int         i_demux_id; /* same as previous, demuxer set default value */
    int         i_channel;  /* es number in creation order */
    char        **ppsz_language;
} es_out_es_props_t;

124 125 126 127
struct es_out_sys_t
{
    input_thread_t *p_input;

128 129 130
    /* */
    vlc_mutex_t   lock;

131 132 133 134 135 136 137 138 139 140 141
    /* all programs */
    int           i_pgrm;
    es_out_pgrm_t **pgrm;
    es_out_pgrm_t *p_pgrm;  /* Master program */

    /* all es */
    int         i_id;
    int         i_es;
    es_out_id_t **es;

    /* mode gestion */
142
    bool  b_active;
143 144
    int         i_mode;

145
    es_out_es_props_t video, audio, sub;
146

Laurent Aimar's avatar
Laurent Aimar committed
147 148
    /* es/group to select */
    int         i_group_id;
149 150 151 152

    /* delay */
    int64_t i_audio_delay;
    int64_t i_spu_delay;
153

154 155
    /* Clock configuration */
    mtime_t     i_pts_delay;
156
    mtime_t     i_pts_jitter;
157
    int         i_cr_average;
158
    int         i_rate;
159

Laurent Aimar's avatar
Laurent Aimar committed
160 161
    /* */
    bool        b_paused;
162
    mtime_t     i_pause_date;
Laurent Aimar's avatar
Laurent Aimar committed
163

164
    /* Current preroll */
Laurent Aimar's avatar
Laurent Aimar committed
165
    mtime_t     i_preroll_end;
166

167 168
    /* Used for buffering */
    bool        b_buffering;
Laurent Aimar's avatar
Laurent Aimar committed
169 170 171
    mtime_t     i_buffering_extra_initial;
    mtime_t     i_buffering_extra_stream;
    mtime_t     i_buffering_extra_system;
172

173 174
    /* Record */
    sout_instance_t *p_sout_record;
175 176 177

    /* Used only to limit debugging output */
    int         i_prev_stream_level;
178 179
};

180
static es_out_id_t *EsOutAdd    ( es_out_t *, const es_format_t * );
181 182 183
static int          EsOutSend   ( es_out_t *, es_out_id_t *, block_t * );
static void         EsOutDel    ( es_out_t *, es_out_id_t * );
static int          EsOutControl( es_out_t *, int i_query, va_list );
184
static void         EsOutDelete ( es_out_t * );
185

186
static void         EsOutTerminate( es_out_t * );
187
static void         EsOutSelect( es_out_t *, es_out_id_t *es, bool b_force );
Laurent Aimar's avatar
Laurent Aimar committed
188
static void         EsOutUpdateInfo( es_out_t *, es_out_id_t *es, const es_format_t *, const vlc_meta_t * );
189
static int          EsOutSetRecord(  es_out_t *, bool b_record );
190

191
static bool EsIsSelected( es_out_id_t *es );
192
static void EsSelect( es_out_t *out, es_out_id_t *es );
193
static void EsDeleteInfo( es_out_t *, es_out_id_t *es );
194
static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update );
195 196 197
static void EsOutDecoderChangeDelay( es_out_t *out, es_out_id_t *p_es );
static void EsOutDecodersChangePause( es_out_t *out, bool b_paused, mtime_t i_date );
static void EsOutProgramChangePause( es_out_t *out, bool b_paused, mtime_t i_date );
Laurent Aimar's avatar
Laurent Aimar committed
198
static void EsOutProgramsChangeRate( es_out_t *out );
199
static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced );
200 201
static void EsOutGlobalMeta( es_out_t *p_out, const vlc_meta_t *p_meta );
static void EsOutMeta( es_out_t *p_out, const vlc_meta_t *p_meta, const vlc_meta_t *p_progmeta );
202

203 204
static char *LanguageGetName( const char *psz_code );
static char *LanguageGetCode( const char *psz_lang );
205
static char **LanguageSplit( const char *psz_langs );
206
static int LanguageArrayIndex( char **ppsz_langs, const char *psz_lang );
207

208
static char *EsOutProgramGetMetaName( es_out_pgrm_t *p_pgrm );
209
static char *EsInfoCategoryName( es_out_id_t* es );
210

211
static inline int EsOutGetClosedCaptionsChannel( const es_format_t *p_fmt )
212
{
213 214 215
    int i_channel;
    if( p_fmt->i_codec == VLC_CODEC_CEA608 && p_fmt->subs.cc.i_channel < 4 )
        i_channel = p_fmt->subs.cc.i_channel;
216 217
    else if( p_fmt->i_codec == VLC_CODEC_CEA708 && p_fmt->subs.cc.i_channel < 64 )
        i_channel = p_fmt->subs.cc.i_channel;
218 219 220
    else
        i_channel = -1;
    return i_channel;
221
}
222 223
static inline bool EsFmtIsTeletext( const es_format_t *p_fmt )
{
224
    return p_fmt->i_cat == SPU_ES && p_fmt->i_codec == VLC_CODEC_TELETEXT;
225
}
226

227 228 229 230
/*****************************************************************************
 * Es category specific structs
 *****************************************************************************/
static es_out_es_props_t * GetPropsByCat( es_out_sys_t *p_sys, int i_cat )
231 232 233 234 235 236 237 238 239 240 241 242 243
{
    switch( i_cat )
    {
    case AUDIO_ES:
        return &p_sys->audio;
    case SPU_ES:
        return &p_sys->sub;
    case VIDEO_ES:
        return &p_sys->video;
    }
    return NULL;
}

244 245 246 247 248 249 250 251 252 253 254
static void EsOutPropsCleanup( es_out_es_props_t *p_props )
{
    if( p_props->ppsz_language )
    {
        for( int i = 0; p_props->ppsz_language[i]; i++ )
            free( p_props->ppsz_language[i] );
        free( p_props->ppsz_language );
    }
}

static void EsOutPropsInit( es_out_es_props_t *p_props,
255
                            bool autoselect,
256 257 258 259 260 261 262 263 264
                            input_thread_t *p_input,
                            enum es_out_policy_e e_default_policy,
                            const char *psz_trackidvar,
                            const char *psz_trackvar,
                            const char *psz_langvar,
                            const char *psz_debug )
{
    p_props->e_policy = e_default_policy;
    p_props->i_count = 0;
265
    p_props->b_autoselect = autoselect;
266 267 268 269 270
    p_props->i_id = (psz_trackidvar) ? var_GetInteger( p_input, psz_trackidvar ): -1;
    p_props->i_channel = (psz_trackvar) ? var_GetInteger( p_input, psz_trackvar ): -1;
    p_props->i_demux_id = -1;
    p_props->p_main_es = NULL;

271
    if( !input_priv(p_input)->b_preparsing && psz_langvar )
272 273
    {
        char *psz_string = var_GetString( p_input, psz_langvar );
274
        p_props->ppsz_language = LanguageSplit( psz_string );
275 276 277 278 279 280 281 282 283 284
        if( p_props->ppsz_language )
        {
            for( int i = 0; p_props->ppsz_language[i]; i++ )
                msg_Dbg( p_input, "selected %s language[%d] %s",
                         psz_debug, i, p_props->ppsz_language[i] );
        }
        free( psz_string );
    }
}

285 286 287
/*****************************************************************************
 * input_EsOutNew:
 *****************************************************************************/
288
es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate )
289
{
290 291 292
    es_out_t     *out = malloc( sizeof( *out ) );
    if( !out )
        return NULL;
293

Rafaël Carré's avatar
Rafaël Carré committed
294
    es_out_sys_t *p_sys = calloc( 1, sizeof( *p_sys ) );
295 296 297 298 299 300
    if( !p_sys )
    {
        free( out );
        return NULL;
    }

301 302 303 304
    out->pf_add     = EsOutAdd;
    out->pf_send    = EsOutSend;
    out->pf_del     = EsOutDel;
    out->pf_control = EsOutControl;
305
    out->pf_destroy = EsOutDelete;
306
    out->p_sys      = p_sys;
307 308

    vlc_mutex_init_recursive( &p_sys->lock );
309 310
    p_sys->p_input = p_input;

311
    p_sys->b_active = false;
Laurent Aimar's avatar
Laurent Aimar committed
312
    p_sys->i_mode   = ES_OUT_MODE_NONE;
313

314 315 316
    TAB_INIT( p_sys->i_pgrm, p_sys->pgrm );

    TAB_INIT( p_sys->i_es, p_sys->es );
317 318

    /* */
319
    EsOutPropsInit( &p_sys->video, true, p_input, ES_OUT_ES_POLICY_SIMULTANEOUS,
320
                    NULL, NULL, NULL, NULL );
321
    EsOutPropsInit( &p_sys->audio, true, p_input, ES_OUT_ES_POLICY_EXCLUSIVE,
322
                    "audio-track-id", "audio-track", "audio-language", "audio" );
323
    EsOutPropsInit( &p_sys->sub,  false, p_input, ES_OUT_ES_POLICY_EXCLUSIVE,
324
                    "sub-track-id", "sub-track", "sub-language", "sub" );
325

Laurent Aimar's avatar
Laurent Aimar committed
326
    p_sys->i_group_id = var_GetInteger( p_input, "program" );
327

328
    p_sys->i_pause_date = -1;
Laurent Aimar's avatar
Laurent Aimar committed
329

330 331
    p_sys->i_rate = i_rate;

332
    p_sys->b_buffering = true;
333
    p_sys->i_preroll_end = -1;
334
    p_sys->i_prev_stream_level = -1;
335

336 337 338 339 340 341
    return out;
}

/*****************************************************************************
 *
 *****************************************************************************/
342 343 344
static void EsOutDelete( es_out_t *out )
{
    es_out_sys_t *p_sys = out->p_sys;
345 346

    assert( !p_sys->i_es && !p_sys->i_pgrm && !p_sys->p_pgrm );
347 348
    EsOutPropsCleanup( &p_sys->audio );
    EsOutPropsCleanup( &p_sys->sub );
349 350 351 352 353 354 355 356 357 358

    vlc_mutex_destroy( &p_sys->lock );

    free( p_sys );
    free( out );
}

static void EsOutTerminate( es_out_t *out )
{
    es_out_sys_t *p_sys = out->p_sys;
359 360

    if( p_sys->p_sout_record )
361
        EsOutSetRecord( out, false );
362

363
    for( int i = 0; i < p_sys->i_es; i++ )
364 365 366 367 368 369 370 371 372 373
    {
        if( p_sys->es[i]->p_dec )
            input_DecoderDelete( p_sys->es[i]->p_dec );

        free( p_sys->es[i]->psz_language );
        free( p_sys->es[i]->psz_language_code );
        es_format_Clean( &p_sys->es[i]->fmt );

        free( p_sys->es[i] );
    }
374
    TAB_CLEAN( p_sys->i_es, p_sys->es );
375 376

    /* FIXME duplicate work EsOutProgramDel (but we cannot use it) add a EsOutProgramClean ? */
377
    for( int i = 0; i < p_sys->i_pgrm; i++ )
378 379 380
    {
        es_out_pgrm_t *p_pgrm = p_sys->pgrm[i];
        input_clock_Delete( p_pgrm->p_clock );
381 382
        if( p_pgrm->p_meta )
            vlc_meta_Delete( p_pgrm->p_meta );
383 384 385 386

        free( p_pgrm );
    }
    TAB_CLEAN( p_sys->i_pgrm, p_sys->pgrm );
Laurent Aimar's avatar
Laurent Aimar committed
387

388 389
    p_sys->p_pgrm = NULL;

390
    input_item_SetEpgOffline( input_priv(p_sys->p_input)->p_item );
391
    input_SendEventMetaEpg( p_sys->p_input );
392 393
}

394 395 396 397 398 399 400 401 402 403 404
static mtime_t EsOutGetWakeup( es_out_t *out )
{
    es_out_sys_t   *p_sys = out->p_sys;
    input_thread_t *p_input = p_sys->p_input;

    if( !p_sys->p_pgrm )
        return 0;

    /* We do not have a wake up date if the input cannot have its speed
     * controlled or sout is imposing its own or while buffering
     *
405
     * FIXME for !input_priv(p_input)->b_can_pace_control a wake-up time is still needed
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
406
     * to avoid too heavy buffering */
407 408
    if( !input_priv(p_input)->b_can_pace_control ||
        input_priv(p_input)->b_out_pace_control ||
409 410 411 412 413 414
        p_sys->b_buffering )
        return 0;

    return input_clock_GetWakeup( p_sys->p_pgrm->p_clock );
}

415 416
static es_out_id_t es_cat[DATA_ES];

417 418 419 420 421
static es_out_id_t *EsOutGetFromID( es_out_t *out, int i_id )
{
    if( i_id < 0 )
    {
        /* Special HACK, -i_id is the cat of the stream */
422
        return es_cat - i_id;
423 424
    }

425 426
    es_out_sys_t *p_sys = out->p_sys;
    for( int i = 0; i < p_sys->i_es; i++ )
427
    {
428 429
        if( p_sys->es[i]->i_id == i_id )
            return p_sys->es[i];
430 431 432
    }
    return NULL;
}
433

434 435 436 437 438 439 440 441 442 443 444
static bool EsOutDecodersIsEmpty( es_out_t *out )
{
    es_out_sys_t      *p_sys = out->p_sys;

    if( p_sys->b_buffering && p_sys->p_pgrm )
    {
        EsOutDecodersStopBuffering( out, true );
        if( p_sys->b_buffering )
            return true;
    }

Rafaël Carré's avatar
Rafaël Carré committed
445
    for( int i = 0; i < p_sys->i_es; i++ )
446 447 448 449 450 451 452 453 454 455 456
    {
        es_out_id_t *es = p_sys->es[i];

        if( es->p_dec && !input_DecoderIsEmpty( es->p_dec ) )
            return false;
        if( es->p_dec_record && !input_DecoderIsEmpty( es->p_dec_record ) )
            return false;
    }
    return true;
}

457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
static void EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay )
{
    es_out_sys_t *p_sys = out->p_sys;

    if( i_cat == AUDIO_ES )
        p_sys->i_audio_delay = i_delay;
    else if( i_cat == SPU_ES )
        p_sys->i_spu_delay = i_delay;

    for( int i = 0; i < p_sys->i_es; i++ )
        EsOutDecoderChangeDelay( out, p_sys->es[i] );
}

static int EsOutSetRecord(  es_out_t *out, bool b_record )
{
    es_out_sys_t   *p_sys = out->p_sys;
    input_thread_t *p_input = p_sys->p_input;

    assert( ( b_record && !p_sys->p_sout_record ) || ( !b_record && p_sys->p_sout_record ) );

    if( b_record )
    {
479 480
        char *psz_path = var_CreateGetNonEmptyString( p_input, "input-record-path" );
        if( !psz_path )
481 482 483 484 485 486 487 488
        {
            if( var_CountChoices( p_input, "video-es" ) )
                psz_path = config_GetUserDir( VLC_VIDEOS_DIR );
            else if( var_CountChoices( p_input, "audio-es" ) )
                psz_path = config_GetUserDir( VLC_MUSIC_DIR );
            else
                psz_path = config_GetUserDir( VLC_DOWNLOAD_DIR );
        }
489 490 491 492 493

        char *psz_sout = NULL;  // TODO conf

        if( !psz_sout && psz_path )
        {
494
            char *psz_file = input_CreateFilename( p_input, psz_path, INPUT_RECORD_PREFIX, NULL );
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
            if( psz_file )
            {
                if( asprintf( &psz_sout, "#record{dst-prefix='%s'}", psz_file ) < 0 )
                    psz_sout = NULL;
                free( psz_file );
            }
        }
        free( psz_path );

        if( !psz_sout )
            return VLC_EGENERIC;

#ifdef ENABLE_SOUT
        p_sys->p_sout_record = sout_NewInstance( p_input, psz_sout );
#endif
        free( psz_sout );

        if( !p_sys->p_sout_record )
            return VLC_EGENERIC;

        for( int i = 0; i < p_sys->i_es; i++ )
        {
            es_out_id_t *p_es = p_sys->es[i];

            if( !p_es->p_dec || p_es->p_master )
                continue;

            p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, p_sys->p_sout_record );
            if( p_es->p_dec_record && p_sys->b_buffering )
524
                input_DecoderStartWait( p_es->p_dec_record );
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
        }
    }
    else
    {
        for( int i = 0; i < p_sys->i_es; i++ )
        {
            es_out_id_t *p_es = p_sys->es[i];

            if( !p_es->p_dec_record )
                continue;

            input_DecoderDelete( p_es->p_dec_record );
            p_es->p_dec_record = NULL;
        }
#ifdef ENABLE_SOUT
        sout_DeleteInstance( p_sys->p_sout_record );
#endif
        p_sys->p_sout_record = NULL;
    }

    return VLC_SUCCESS;
}
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
static void EsOutChangePause( es_out_t *out, bool b_paused, mtime_t i_date )
{
    es_out_sys_t *p_sys = out->p_sys;

    /* XXX the order is important */
    if( b_paused )
    {
        EsOutDecodersChangePause( out, true, i_date );
        EsOutProgramChangePause( out, true, i_date );
    }
    else
    {
        if( p_sys->i_buffering_extra_initial > 0 )
        {
            mtime_t i_stream_start;
            mtime_t i_system_start;
            mtime_t i_stream_duration;
            mtime_t i_system_duration;
            int i_ret;
            i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock,
                                          &i_stream_start, &i_system_start,
                                          &i_stream_duration, &i_system_duration );
            if( !i_ret )
            {
                /* FIXME pcr != exactly what wanted */
572
                const mtime_t i_used = /*(i_stream_duration - input_priv(p_sys->p_input)->i_pts_delay)*/ p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial;
573 574 575 576 577 578 579 580 581 582 583 584
                i_date -= i_used;
            }
            p_sys->i_buffering_extra_initial = 0;
            p_sys->i_buffering_extra_stream = 0;
            p_sys->i_buffering_extra_system = 0;
        }
        EsOutProgramChangePause( out, false, i_date );
        EsOutDecodersChangePause( out, false, i_date );

        EsOutProgramsChangeRate( out );
    }
    p_sys->b_paused = b_paused;
585
    p_sys->i_pause_date = i_date;
586 587 588 589 590 591 592
}

static void EsOutChangeRate( es_out_t *out, int i_rate )
{
    es_out_sys_t      *p_sys = out->p_sys;

    p_sys->i_rate = i_rate;
593
    EsOutProgramsChangeRate( out );
594 595 596 597 598 599
}

static void EsOutChangePosition( es_out_t *out )
{
    es_out_sys_t      *p_sys = out->p_sys;

600 601
    input_SendEventCache( p_sys->p_input, 0.0 );

602 603 604 605
    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *p_es = p_sys->es[i];

606 607 608 609 610 611 612 613 614 615
        if( p_es->p_dec != NULL )
        {
            input_DecoderFlush( p_es->p_dec );
            if( !p_sys->b_buffering )
            {
                input_DecoderStartWait( p_es->p_dec );
                if( p_es->p_dec_record != NULL )
                    input_DecoderStartWait( p_es->p_dec_record );
            }
        }
616 617 618 619 620 621 622 623 624 625
    }

    for( int i = 0; i < p_sys->i_pgrm; i++ )
        input_clock_Reset( p_sys->pgrm[i]->p_clock );

    p_sys->b_buffering = true;
    p_sys->i_buffering_extra_initial = 0;
    p_sys->i_buffering_extra_stream = 0;
    p_sys->i_buffering_extra_system = 0;
    p_sys->i_preroll_end = -1;
626
    p_sys->i_prev_stream_level = -1;
627 628
}

629 630


631 632 633 634 635 636 637 638
static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced )
{
    es_out_sys_t *p_sys = out->p_sys;

    mtime_t i_stream_start;
    mtime_t i_system_start;
    mtime_t i_stream_duration;
    mtime_t i_system_duration;
639
    if (input_clock_GetState( p_sys->p_pgrm->p_clock,
640
                                  &i_stream_start, &i_system_start,
641
                                  &i_stream_duration, &i_system_duration ))
642 643
        return;

644
    mtime_t i_preroll_duration = 0;
645 646
    if( p_sys->i_preroll_end >= 0 )
        i_preroll_duration = __MAX( p_sys->i_preroll_end - i_stream_start, 0 );
647

648
    const mtime_t i_buffering_duration = p_sys->i_pts_delay +
Laurent Aimar's avatar
Laurent Aimar committed
649
                                         i_preroll_duration +
650
                                         p_sys->i_buffering_extra_stream - p_sys->i_buffering_extra_initial;
Laurent Aimar's avatar
Laurent Aimar committed
651 652

    if( i_stream_duration <= i_buffering_duration && !b_forced )
653
    {
654 655 656 657 658
        double f_level;
        if (i_buffering_duration == 0)
            f_level = 0;
        else
            f_level = __MAX( (double)i_stream_duration / i_buffering_duration, 0 );
659 660
        input_SendEventCache( p_sys->p_input, f_level );

661 662 663 664 665 666 667
        int i_level = (int)(100 * f_level);
        if( p_sys->i_prev_stream_level != i_level )
        {
            msg_Dbg( p_sys->p_input, "Buffering %d%%", i_level );
            p_sys->i_prev_stream_level = i_level;
        }

668 669
        return;
    }
670
    input_SendEventCache( p_sys->p_input, 1.0 );
671 672 673 674

    msg_Dbg( p_sys->p_input, "Stream buffering done (%d ms in %d ms)",
              (int)(i_stream_duration/1000), (int)(i_system_duration/1000) );
    p_sys->b_buffering = false;
675
    p_sys->i_preroll_end = -1;
676
    p_sys->i_prev_stream_level = -1;
677

Laurent Aimar's avatar
Laurent Aimar committed
678 679 680 681 682 683
    if( p_sys->i_buffering_extra_initial > 0 )
    {
        /* FIXME wrong ? */
        return;
    }

684 685 686 687 688
    const mtime_t i_decoder_buffering_start = mdate();
    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *p_es = p_sys->es[i];

Laurent Aimar's avatar
Laurent Aimar committed
689
        if( !p_es->p_dec || p_es->fmt.i_cat == SPU_ES )
690
            continue;
691
        input_DecoderWait( p_es->p_dec );
692
        if( p_es->p_dec_record )
693
            input_DecoderWait( p_es->p_dec_record );
694 695
    }

696
    msg_Dbg( p_sys->p_input, "Decoder wait done in %d ms",
697 698
              (int)(mdate() - i_decoder_buffering_start)/1000 );

699
    /* Here is a good place to destroy unused vout with every demuxer */
700
    input_resource_TerminateVout( input_priv(p_sys->p_input)->p_resource );
701 702

    /* */
703 704 705
    const mtime_t i_wakeup_delay = 10*1000; /* FIXME CLEANUP thread wake up time*/
    const mtime_t i_current_date = p_sys->b_paused ? p_sys->i_pause_date : mdate();

706 707
    input_clock_ChangeSystemOrigin( p_sys->p_pgrm->p_clock, true,
                                    i_current_date + i_wakeup_delay - i_buffering_duration );
708 709 710 711 712 713 714 715

    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *p_es = p_sys->es[i];

        if( !p_es->p_dec )
            continue;

716
        input_DecoderStopWait( p_es->p_dec );
717
        if( p_es->p_dec_record )
718
            input_DecoderStopWait( p_es->p_dec_record );
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    }
}
static void EsOutDecodersChangePause( es_out_t *out, bool b_paused, mtime_t i_date )
{
    es_out_sys_t *p_sys = out->p_sys;

    /* Pause decoders first */
    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *es = p_sys->es[i];

        if( es->p_dec )
        {
            input_DecoderChangePause( es->p_dec, b_paused, i_date );
            if( es->p_dec_record )
                input_DecoderChangePause( es->p_dec_record, b_paused, i_date );
        }
    }
}
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752

static bool EsOutIsExtraBufferingAllowed( es_out_t *out )
{
    es_out_sys_t *p_sys = out->p_sys;

    size_t i_size = 0;
    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *p_es = p_sys->es[i];

        if( p_es->p_dec )
            i_size += input_DecoderGetFifoSize( p_es->p_dec );
        if( p_es->p_dec_record )
            i_size += input_DecoderGetFifoSize( p_es->p_dec_record );
    }
753
    //msg_Info( out, "----- EsOutIsExtraBufferingAllowed =% 5d KiB -- ", i_size / 1024 );
754 755 756

    /* TODO maybe we want to be able to tune it ? */
#if defined(OPTIMIZE_MEMORY)
757
    const size_t i_level_high = 512*1024;  /* 0.5 MiB */
758
#else
759
    const size_t i_level_high = 10*1024*1024; /* 10 MiB */
760 761 762 763
#endif
    return i_size < i_level_high;
}

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
static void EsOutProgramChangePause( es_out_t *out, bool b_paused, mtime_t i_date )
{
    es_out_sys_t *p_sys = out->p_sys;

    for( int i = 0; i < p_sys->i_pgrm; i++ )
        input_clock_ChangePause( p_sys->pgrm[i]->p_clock, b_paused, i_date );
}

static void EsOutDecoderChangeDelay( es_out_t *out, es_out_id_t *p_es )
{
    es_out_sys_t *p_sys = out->p_sys;

    mtime_t i_delay = 0;
    if( p_es->fmt.i_cat == AUDIO_ES )
        i_delay = p_sys->i_audio_delay;
    else if( p_es->fmt.i_cat == SPU_ES )
        i_delay = p_sys->i_spu_delay;
781 782
    else
        return;
783

784 785 786 787
    if( p_es->p_dec )
        input_DecoderChangeDelay( p_es->p_dec, i_delay );
    if( p_es->p_dec_record )
        input_DecoderChangeDelay( p_es->p_dec_record, i_delay );
788
}
Laurent Aimar's avatar
Laurent Aimar committed
789 790 791
static void EsOutProgramsChangeRate( es_out_t *out )
{
    es_out_sys_t      *p_sys = out->p_sys;
792

Laurent Aimar's avatar
Laurent Aimar committed
793 794 795
    for( int i = 0; i < p_sys->i_pgrm; i++ )
        input_clock_ChangeRate( p_sys->pgrm[i]->p_clock, p_sys->i_rate );
}
796

797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
static void EsOutFrameNext( es_out_t *out )
{
    es_out_sys_t *p_sys = out->p_sys;
    es_out_id_t *p_es_video = NULL;

    if( p_sys->b_buffering )
    {
        msg_Warn( p_sys->p_input, "buffering, ignoring 'frame next'" );
        return;
    }

    assert( p_sys->b_paused );

    for( int i = 0; i < p_sys->i_es; i++ )
    {
        es_out_id_t *p_es = p_sys->es[i];

        if( p_es->fmt.i_cat == VIDEO_ES && p_es->p_dec )
        {
            p_es_video = p_es;
            break;
        }
    }

    if( !p_es_video )
    {
        msg_Warn( p_sys->p_input, "No video track selected, ignoring 'frame next'" );
        return;
    }

    mtime_t i_duration;
    input_DecoderFrameNext( p_es_video->p_dec, &i_duration );

830
    msg_Dbg( p_sys->p_input, "EsOutFrameNext consummed %d ms", (int)(i_duration/1000) );
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849

    if( i_duration <= 0 )
        i_duration = 40*1000;

    /* FIXME it is not a clean way ? */
    if( p_sys->i_buffering_extra_initial <= 0 )
    {
        mtime_t i_stream_start;
        mtime_t i_system_start;
        mtime_t i_stream_duration;
        mtime_t i_system_duration;
        int i_ret;

        i_ret = input_clock_GetState( p_sys->p_pgrm->p_clock,
                                      &i_stream_start, &i_system_start,
                                      &i_stream_duration, &i_system_duration );
        if( i_ret )
            return;

850
        p_sys->i_buffering_extra_initial = 1 + i_stream_duration - p_sys->i_pts_delay; /* FIXME < 0 ? */
851 852 853 854 855 856 857 858 859 860 861 862 863
        p_sys->i_buffering_extra_system =
        p_sys->i_buffering_extra_stream = p_sys->i_buffering_extra_initial;
    }

    const int i_rate = input_clock_GetRate( p_sys->p_pgrm->p_clock );

    p_sys->b_buffering = true;
    p_sys->i_buffering_extra_system += i_duration;
    p_sys->i_buffering_extra_stream = p_sys->i_buffering_extra_initial +
                                      ( p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial ) *
                                                INPUT_RATE_DEFAULT / i_rate;

    p_sys->i_preroll_end = -1;
864
    p_sys->i_prev_stream_level = -1;
865
}
866 867 868
static mtime_t EsOutGetBuffering( es_out_t *out )
{
    es_out_sys_t *p_sys = out->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
869
    mtime_t i_stream_duration, i_system_start;
870 871 872

    if( !p_sys->p_pgrm )
        return 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
873 874 875
    else
    {
        mtime_t i_stream_start, i_system_duration;
876

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
877
        if( input_clock_GetState( p_sys->p_pgrm->p_clock,
878
                                  &i_stream_start, &i_system_start,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
879 880 881
                                  &i_stream_duration, &i_system_duration ) )
            return 0;
    }
882

883
    mtime_t i_delay;
884

885 886 887 888 889 890 891
    if( p_sys->b_buffering && p_sys->i_buffering_extra_initial <= 0 )
    {
        i_delay = i_stream_duration;
    }
    else
    {
        mtime_t i_system_duration;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
892

893 894 895 896 897 898 899 900 901 902 903 904
        if( p_sys->b_paused )
        {
            i_system_duration = p_sys->i_pause_date  - i_system_start;
            if( p_sys->i_buffering_extra_initial > 0 )
                i_system_duration += p_sys->i_buffering_extra_system - p_sys->i_buffering_extra_initial;
        }
        else
        {
            i_system_duration = mdate() - i_system_start;
        }

        const mtime_t i_consumed = i_system_duration * INPUT_RATE_DEFAULT / p_sys->i_rate - i_stream_duration;
905
        i_delay = p_sys->i_pts_delay - i_consumed;
906 907 908 909 910
    }
    if( i_delay < 0 )
        return 0;
    return i_delay;
}
911

912 913
static void EsOutESVarUpdateGeneric( es_out_t *out, int i_id,
                                     const es_format_t *fmt, const char *psz_language,
914
                                     bool b_delete )
915 916 917 918 919 920 921
{
    es_out_sys_t      *p_sys = out->p_sys;
    input_thread_t    *p_input = p_sys->p_input;
    vlc_value_t       val, text;

    if( b_delete )
    {
922 923
        if( EsFmtIsTeletext( fmt ) )
            input_SendEventTeletextDel( p_sys->p_input, i_id );
924

925
        input_SendEventEsDel( p_input, fmt->i_cat, i_id );
926 927 928 929
        return;
    }

    /* Get the number of ES already added */
930 931 932 933 934 935 936 937
    const char *psz_var;
    if( fmt->i_cat == AUDIO_ES )
        psz_var = "audio-es";
    else if( fmt->i_cat == VIDEO_ES )
        psz_var = "video-es";
    else
        psz_var = "spu-es";

938 939 940 941 942 943 944 945 946 947 948 949
    var_Change( p_input, psz_var, VLC_VAR_CHOICESCOUNT, &val, NULL );
    if( val.i_int == 0 )
    {
        vlc_value_t val2;

        /* First one, we need to add the "Disable" choice */
        val2.i_int = -1; text.psz_string = _("Disable");
        var_Change( p_input, psz_var, VLC_VAR_ADDCHOICE, &val2, &text );
        val.i_int++;
    }

    /* Take care of the ES description */
950
    if( fmt->psz_description && *fmt->psz_description )
951
    {
952
        if( psz_language && *psz_language )
953
        {
Rémi Duraffort's avatar
Rémi Duraffort committed
954 955 956
            if( asprintf( &text.psz_string, "%s - [%s]", fmt->psz_description,
                          psz_language ) == -1 )
                text.psz_string = NULL;
957
        }
958
        else text.psz_string = strdup( fmt->psz_description );
959 960 961
    }
    else
    {
962
        if( psz_language && *psz_language )
963
        {
964
            if( asprintf( &text.psz_string, "%s %"PRId64" - [%s]", _( "Track" ), val.i_int, psz_language ) == -1 )
965
                text.psz_string = NULL;
966 967 968
        }
        else
        {
969
            if( asprintf( &text.psz_string, "%s %"PRId64, _( "Track" ), val.i_int ) == -1 )
970
                text.psz_string = NULL;
971 972 973
        }
    }

974
    input_SendEventEsAdd( p_input, fmt->i_cat, i_id, text.psz_string );
975
    if( EsFmtIsTeletext( fmt ) )
976 977 978 979 980 981 982 983
    {
        char psz_page[3+1];
        snprintf( psz_page, sizeof(psz_page), "%d%2.2x",
                  fmt->subs.teletext.i_magazine,
                  fmt->subs.teletext.i_page );
        input_SendEventTeletextAdd( p_sys->p_input,
                                    i_id, fmt->subs.teletext.i_magazine >= 0 ? psz_page : NULL );
    }
984 985 986 987

    free( text.psz_string );
}

988
static void EsOutESVarUpdate( es_out_t *out, es_out_id_t *es,
989
                              bool b_delete )
990 991 992 993
{
    EsOutESVarUpdateGeneric( out, es->i_id, &es->fmt, es->psz_language, b_delete );
}

Laurent Aimar's avatar
Laurent Aimar committed
994 995
static bool EsOutIsProgramVisible( es_out_t *out, int i_group )
{
996 997
    es_out_sys_t *p_sys = out->p_sys;
    return p_sys->i_group_id == 0 || p_sys->i_group_id == i_group;
Laurent Aimar's avatar
Laurent Aimar committed
998 999
}

1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
/* EsOutProgramSelect:
 *  Select a program and update the object variable
 */
static void EsOutProgramSelect( es_out_t *out, es_out_pgrm_t *p_pgrm )
{
    es_out_sys_t      *p_sys = out->p_sys;
    input_thread_t    *p_input = p_sys->p_input;
    int               i;

    if( p_sys->p_pgrm == p_pgrm )
        return; /* Nothing to do */

    if( p_sys->p_pgrm )
    {
        es_out_pgrm_t *old = p_sys->p_pgrm;
Clément Stenac's avatar
Clément Stenac committed
1015
        msg_Dbg( p_input, "unselecting program id=%d", old->i_id );
1016 1017 1018

        for( i = 0; i < p_sys->i_es; i++ )
        {
1019
            if( p_sys->es[i]->p_pgrm == old && EsIsSelected( p_sys->es[i] ) &&
1020
                p_sys->i_mode != ES_OUT_MODE_ALL )
1021
                EsUnselect( out, p_sys->es[i], true );
1022 1023
        }

1024 1025 1026
        p_sys->audio.p_main_es = NULL;
        p_sys->video.p_main_es = NULL;
        p_sys->sub.p_main_es = NULL;
1027 1028
    }

Clément Stenac's avatar
Clément Stenac committed
1029
    msg_Dbg( p_input, "selecting program id=%d", p_pgrm->i_id );
1030 1031

    /* Mark it selected */
1032
    p_pgrm->b_selected = true;
1033 1034 1035 1036 1037

    /* Switch master stream */
    p_sys->p_pgrm = p_pgrm;

    /* Update "program" */
1038
    input_SendEventProgramSelect( p_input, p_pgrm->i_id );
1039 1040

    /* Update "es-*" */
1041 1042 1043
    input_SendEventEsDel( p_input, AUDIO_ES, -1 );
    input_SendEventEsDel( p_input, VIDEO_ES, -1 );
    input_SendEventEsDel( p_input, SPU_ES, -1 );
1044
    input_SendEventTeletextDel( p_input, -1 );
1045
    input_SendEventProgramScrambled( p_input, p_pgrm->i_id, p_pgrm->b_scrambled );
1046 1047

    /* TODO event */
1048
    var_SetInteger( p_input, "teletext-es", -1 );
1049

1050 1051 1052
    for( i = 0; i < p_sys->i_es; i++ )
    {
        if( p_sys->es[i]->p_pgrm == p_sys->p_pgrm )
1053
        {
1054
            EsOutESVarUpdate( out, p_sys->es[i], false );
1055 1056 1057
            EsOutUpdateInfo( out, p_sys->es[i], &p_sys->es[i]->fmt, NULL );
        }

1058
        EsOutSelect( out, p_sys->es[i], false );
1059 1060
    }

1061 1062 1063
    /* Ensure the correct running EPG table is selected */
    input_item_ChangeEPGSource( input_priv(p_input)->p_item, p_pgrm->i_id );

1064
    /* Update now playing */
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
    if( p_pgrm->p_meta )
    {
        input_item_SetESNowPlaying( input_priv(p_input)->p_item,
                                    vlc_meta_Get( p_pgrm->p_meta, vlc_meta_ESNowPlaying ) );
        input_item_SetPublisher( input_priv(p_input)->p_item,
                                 vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Publisher ) );
        input_item_SetTitle( input_priv(p_input)->p_item,
                             vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Title ) );
        input_SendEventMeta( p_input );
        /* FIXME: we probably want to replace every input meta */
    }
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
}

/* EsOutAddProgram:
 *  Add a program
 */
static es_out_pgrm_t *EsOutProgramAdd( es_out_t *out, int i_group )
{
    es_out_sys_t      *p_sys = out->p_sys;
    input_thread_t    *p_input = p_sys->p_input;

    es_out_pgrm_t *p_pgrm = malloc( sizeof( es_out_pgrm_t ) );
1087 1088
    if( !p_pgrm )
        return NULL;
1089 1090 1091 1092

    /* Init */
    p_pgrm->i_id = i_group;
    p_pgrm->i_es = 0;
1093
    p_pgrm->b_selected = false;
1094
    p_pgrm->b_scrambled = false;
1095
    p_pgrm->p_meta = NULL;
1096
    p_pgrm->p_clock = input_clock_New( p_sys->i_rate );
1097 1098 1099 1100 1101
    if( !p_pgrm->p_clock )
    {
        free( p_pgrm );
        return NULL;
    }
1102 1103
    if( p_sys->b_paused )
        input_clock_ChangePause( p_pgrm->p_clock, p_sys->b_paused, p_sys->i_pause_date );
1104 1105
    input_clock_SetJitter( p_pgrm->p_clock, p_sys->i_pts_delay, p_sys->i_cr_average );

1106 1107 1108 1109
    /* Append it */
    TAB_APPEND( p_sys->i_pgrm, p_sys->pgrm, p_pgrm );

    /* Update "program" variable */
Laurent Aimar's avatar
Laurent Aimar committed
1110 1111
    if( EsOutIsProgramVisible( out, i_group ) )
        input_SendEventProgramAdd( p_input, i_group, NULL );
1112

Laurent Aimar's avatar
Laurent Aimar committed
1113
    if( i_group == p_sys->i_group_id || ( !p_sys->p_pgrm && p_sys->i_group_id == 0 ) )
1114
        EsOutProgramSelect( out, p_pgrm );
1115

1116 1117 1118
    return p_pgrm;
}

1119 1120 1121
/* EsOutDelProgram:
 *  Delete a program
 */
1122
static int EsOutProgramDel( es_out_t *out, int i_group )
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
{
    es_out_sys_t      *p_sys = out->p_sys;
    input_thread_t    *p_input = p_sys->p_input;
    es_out_pgrm_t     *p_pgrm = NULL;
    int               i;

    for( i = 0; i < p_sys->i_pgrm; i++ )
    {
        if( p_sys->pgrm[i]->i_id == i_group )
        {
            p_pgrm = p_sys->pgrm[i];
            break;
        }
    }

1138 1139
    if( p_pgrm == NULL )
        return VLC_EGENERIC;
1140 1141 1142 1143 1144

    if( p_pgrm->i_es )
    {
        msg_Dbg( p_input, "can't delete program %d which still has %i ES",
                 i_group, p_pgrm->i_es );
1145
        return VLC_EGENERIC;
1146 1147 1148 1149 1150
    }

    TAB_REMOVE( p_sys->i_pgrm, p_sys->pgrm, p_pgrm );

    /* If program is selected we need to unselect it */
1151 1152 1153
    if( p_sys->p_pgrm == p_pgrm )
        p_sys->p_pgrm = NULL;

Laurent Aimar's avatar
Laurent Aimar committed
1154
    input_clock_Delete( p_pgrm->p_clock );
1155

1156 1157
    if( p_pgrm->p_meta )
        vlc_meta_Delete( p_pgrm->p_meta );
1158 1159 1160
    free( p_pgrm );

    /* Update "program" variable */
1161
    input_SendEventProgramDel( p_input, i_group );
1162 1163

    return VLC_SUCCESS;
1164 1165
}

Laurent Aimar's avatar
Laurent Aimar committed
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179
/* EsOutProgramFind
 */
static es_out_pgrm_t *EsOutProgramFind( es_out_t *p_out, int i_group )
{
    es_out_sys_t *p_sys = p_out->p_sys;

    for( int i = 0; i < p_sys->i_pgrm; i++ )
    {
        if( p_sys->pgrm[i]->i_id == i_group )
            return p_sys->pgrm[i];
    }
    return EsOutProgramAdd( p_out, i_group );
}

1180 1181
/* EsOutProgramMeta:
 */
1182 1183 1184
static char *EsOutProgramGetMetaName( es_out_pgrm_t *p_pgrm )
{
    char *psz = NULL;
1185
    if( p_pgrm->p_meta && vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Title ) )
1186
    {
1187 1188
        if( asprintf( &psz, _("%s [%s %d]"), vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Title ),
                      _("Program"), p_pgrm->i_id ) == -1 )
Rémi Duraffort's avatar
Rémi Duraffort committed
1189
            return NULL;
1190
    }
1191
    else
1192 1193
    {
        if( asprintf( &psz, "%s %d", _("Program"), p_pgrm->i_id ) == -1 )
Rémi Duraffort's avatar
Rémi Duraffort committed
1194
            return NULL;
1195
    }
1196 1197 1198
    return psz;
}

1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
static char *EsOutProgramGetProgramName( es_out_pgrm_t *p_pgrm )
{
    char *psz = NULL;
    if( p_pgrm->p_meta && vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Title ) )
    {
        return strdup( vlc_meta_Get( p_pgrm->p_meta, vlc_meta_Title ) );
    }
    else
    {
        if( asprintf( &psz, "%s %d", _("Program"), p_pgrm->i_id ) == -1 )
            return NULL;
    }
    return psz;
}

1214 1215 1216 1217 1218 1219 1220 1221 1222 1223
static char *EsInfoCategoryName( es_out_id_t* es )
{
    char *psz_category;

    if( asprintf( &psz_category, _("Stream %d"), es->i_meta_id ) == -1 )
        return NULL;

    return psz_category;
}

1224
static void EsOutProgramMeta( es_out_t *out, int i_group, const vlc_meta_t *p_meta )
1225 1226
{
    es_out_sys_t      *p_sys = out->p_sys;
Laurent Aimar's avatar
Laurent Aimar committed
1227
    es_out_pgrm_t     *p_pgrm;
1228