/*****************************************************************************
 * ts_psi.c: Transport Stream input module for VLC.
 *****************************************************************************
 * Copyright (C) 2004-2016 VLC authors and VideoLAN
 *
 * 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
 * (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
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdbit.h>
#include <vlc_common.h>

#ifndef _DVBPSI_DVBPSI_H_
 # include <dvbpsi/dvbpsi.h>
#endif

#include <dvbpsi/descriptor.h>
#include <dvbpsi/pat.h>
#include <dvbpsi/pmt.h>
#include <dvbpsi/dr.h>
#include "../../mux/mpeg/dvbpsi_compat.h" /* dvbpsi_messages */

#include <vlc_demux.h>
#include <vlc_bits.h>

#include "ts_streams.h"
#include "ts_psi.h"
#include "ts_pid.h"
#include "ts_streams_private.h"
#include "ts.h"

#include "ts_strings.h"

#include "timestamps.h"

#include "../../codec/jpeg2000.h"
#include "../../codec/opus_header.h"
#include "../../packetizer/dts_header.h"

#include "sections.h"
#include "ts_sl.h"
#include "ts_scte.h"
#include "ts_psip.h"
#include "ts_si.h"
#include "ts_metadata.h"
#include "ts_descriptions.h"

#include "../../access/dtv/en50221_capmt.h"
#include "ts_streamwrapper.h"

#include <assert.h>

struct ts_psi_context_t
{
    dvbpsi_t *p_handle;
    void (*pf_detach)(dvbpsi_t *);
    en50221_capmt_info_t *p_capmt;
};

static void PIDFillFormat( demux_t *, ts_stream_t *p_pes, int i_stream_type, ts_transport_type_t * );
static void PMTCallBack( void *data, dvbpsi_pmt_t *p_dvbpsipmt );
static ts_standards_e ProbePMTStandard( const dvbpsi_pmt_t *p_dvbpsipmt );

static int PATCheck( demux_t *p_demux, dvbpsi_pat_t *p_pat )
{
    /* Some Dreambox streams have all PMT set to same pid */
    int i_prev_pid = -1;
    for( dvbpsi_pat_program_t * p_program = p_pat->p_first_program;
         p_program != NULL;
         p_program = p_program->p_next )
    {
        if( p_program->i_pid == i_prev_pid )
        {
            msg_Warn( p_demux, "PAT check failed: duplicate program pid %d", i_prev_pid );
            return VLC_EGENERIC;
        }
        i_prev_pid = p_program->i_pid;
    }
    return VLC_SUCCESS;
}

static void PATCallBack( void *data, dvbpsi_pat_t *p_dvbpsipat )
{
    demux_t              *p_demux = data;
    demux_sys_t          *p_sys = p_demux->p_sys;
    dvbpsi_pat_program_t *p_program;
    ts_pid_t             *patpid = GetPID(p_sys, 0);
    ts_pat_t             *p_pat = GetPID(p_sys, 0)->u.p_pat;

    patpid->i_flags |= FLAG_SEEN;

    msg_Dbg( p_demux, "PATCallBack called" );

    if(unlikely( GetPID(p_sys, 0)->type != TYPE_PAT ))
    {
        msg_Warn( p_demux, "PATCallBack called on invalid pid" );
        return;
    }

    /* override hotfixes */
    if( p_pat->b_generated )
    {
        p_pat->b_generated = false;
        p_pat->i_version = -1;
        p_pat->i_ts_id = -1;
        msg_Warn( p_demux, "Replacing generated PAT with one received from stream" );
    }

    /* check versioning changes */
    if( ( p_pat->i_version != -1 && p_dvbpsipat->i_version == p_pat->i_version ) ||
        ( p_pat->i_ts_id != -1 && p_dvbpsipat->i_ts_id != p_pat->i_ts_id ) )
    {
        dvbpsi_pat_delete( p_dvbpsipat );
        return;
    }

    /* check content */
    if( !p_dvbpsipat->b_current_next || p_sys->b_user_pmt ||
        PATCheck( p_demux, p_dvbpsipat ) )
    {
        dvbpsi_pat_delete( p_dvbpsipat );
        return;
    }

    msg_Dbg( p_demux, "new PAT ts_id=%d version=%d current_next=%d",
             p_dvbpsipat->i_ts_id, p_dvbpsipat->i_version, p_dvbpsipat->b_current_next );

    /* Save old programs array */
    DECL_ARRAY(ts_pid_t *) old_pmt_rm;
    old_pmt_rm.i_alloc = p_pat->programs.i_alloc;
    old_pmt_rm.i_size = p_pat->programs.i_size;
    old_pmt_rm.p_elems = p_pat->programs.p_elems;
    ARRAY_INIT(p_pat->programs);

    bool b_force_reselect = false;
    if( p_sys->programs.i_size && p_sys->seltype == PROGRAM_AUTO_DEFAULT )
    {
        /* If the program was set by default selection, we'll need to repick */
        b_force_reselect = true;
        for( p_program = p_dvbpsipat->p_first_program; p_program != NULL;
             p_program = p_program->p_next )
        {
            if( p_sys->programs.p_elems[0] == p_program->i_number )
            {
                b_force_reselect = false;
                break;
            }
        }
        if( b_force_reselect )
            ARRAY_RESET( p_sys->programs );
    }

    /* now create programs */
    for( p_program = p_dvbpsipat->p_first_program; p_program != NULL;
         p_program = p_program->p_next )
    {
        msg_Dbg( p_demux, "  * number=%d pid=%d", p_program->i_number,
                 p_program->i_pid );
        if( p_program->i_number == 0 )
            continue;

        ts_pid_t *pmtpid = GetPID(p_sys, p_program->i_pid);

        bool b_existing = (pmtpid->type == TYPE_PMT);
        /* create or temporary incref pid */
        if( !PIDSetup( p_demux, TYPE_PMT, pmtpid, patpid ) )
        {
            msg_Warn( p_demux, "  * number=%d pid=%d (ignored)", p_program->i_number,
                     p_program->i_pid );
            continue;
        }

        if( !b_existing || pmtpid->u.p_pmt->i_number != p_program->i_number )
        {
            if( b_existing && pmtpid->u.p_pmt->i_number != p_program->i_number )
                dvbpsi_pmt_detach(pmtpid->u.p_pmt->p_ctx->p_handle);

            if( !dvbpsi_pmt_attach( pmtpid->u.p_pmt->p_ctx->p_handle, p_program->i_number, PMTCallBack, p_demux ) )
                msg_Err( p_demux, "PATCallback failed attaching PMTCallback to program %d",
                         p_program->i_number );
            pmtpid->u.p_pmt->p_ctx->pf_detach = dvbpsi_pmt_detach;
        }

        pmtpid->u.p_pmt->i_number = p_program->i_number;
        if( p_pat->b_generated )
            pmtpid->u.p_pmt->pcr.b_disable = p_sys->patfix.b_pcrhasnopcrfield;

        ARRAY_APPEND( p_pat->programs, pmtpid );

        /* Now select PID at access level */
        if( p_sys->programs.i_size == 0 ||
            ProgramIsSelected( p_sys, p_program->i_number ) )
        {
            if( p_sys->programs.i_size == 0 )
            {
                msg_Dbg( p_demux, "temporary receiving program %d", p_program->i_number );
                p_sys->b_default_selection = true;
                ARRAY_APPEND( p_sys->programs, p_program->i_number );
            }

            SetPIDFilter( p_sys, pmtpid, true );

            if ( p_sys->es_creation == DELAY_ES )
                p_sys->es_creation = CREATE_ES;
        }
    }
    p_pat->i_version = p_dvbpsipat->i_version;
    p_pat->i_ts_id = p_dvbpsipat->i_ts_id;

    for(int i=0; i<old_pmt_rm.i_size; i++)
    {
        /* decref current or release now unreferenced */
        PIDRelease( p_demux, old_pmt_rm.p_elems[i] );
    }
    ARRAY_RESET(old_pmt_rm);

    if( b_force_reselect && p_sys->programs.i_size )
    {
        es_out_Control( p_demux->out, ES_OUT_SET_GROUP, p_sys->programs.p_elems[0] );
    }

    dvbpsi_pat_delete( p_dvbpsipat );
}

#define PMT_DESC_PREFIX " * PMT descriptor: "
#define PMT_DESC_INDENT "                 : "
static void ParsePMTRegistrations( demux_t *p_demux, const dvbpsi_descriptor_t  *p_firstdr,
                                   ts_pmt_t *p_pmt, ts_pmt_registration_type_t *p_registration_type )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    ts_pmt_registration_type_t registration_type = *p_registration_type;
    int i_arib_score_flags = 0; /* Descriptors can be repeated */

    for( const dvbpsi_descriptor_t *p_dr = p_firstdr; p_dr != NULL; p_dr = p_dr->p_next )
    {
        /* general descriptors handling < 0x40 and scoring */
        if( p_dr->i_tag < 0x40 )
        {
            msg_Dbg( p_demux, PMT_DESC_PREFIX "%s (0x%x)",
                     ISO13818_1_Get_Descriptor_Description(p_dr->i_tag), p_dr->i_tag );
        }

        switch(p_dr->i_tag)
        {
            case 0x05: /* Registration Descriptor */
            {
                if( p_dr->i_length != 4 )
                {
                    msg_Warn( p_demux, PMT_DESC_INDENT "invalid registration descriptor" );
                    break;
                }

                static const struct
                {
                    const char rgs[4];
                    const ts_pmt_registration_type_t reg;
                } regs[] = {
                    { { 'H', 'D', 'M', 'V' }, TS_PMT_REGISTRATION_BLURAY },
                    { { 'H', 'D', 'P', 'R' }, TS_PMT_REGISTRATION_BLURAY },
                    { { 'G', 'A', '9', '4' }, TS_PMT_REGISTRATION_ATSC },
                };

                for( unsigned i=0; i<ARRAY_SIZE(regs); i++ )
                {
                    if( !memcmp( regs[i].rgs, p_dr->p_data, 4 ) )
                    {
                        registration_type = regs[i].reg;
                        msg_Dbg( p_demux, PMT_DESC_INDENT "%4.4s registration", p_dr->p_data );
                        break;
                    }
                }
            }
            break;

            case 0x09:
            {
                dvbpsi_ca_dr_t *p_cadr = dvbpsi_DecodeCADr( (dvbpsi_descriptor_t *) p_dr );
                if( p_cadr )
                {
                    msg_Dbg( p_demux, PMT_DESC_INDENT "CA System ID 0x%x", p_cadr->i_ca_system_id );
                    i_arib_score_flags |= (p_cadr->i_ca_system_id == 0x05);
                }
            }
            break;

            case 0x1d: /* We have found an IOD descriptor */
                p_pmt->iod = IODNew( VLC_OBJECT(p_demux), p_dr->i_length, p_dr->p_data );
                break;

            case 0xC1:
                i_arib_score_flags |= 1 << 2;
                break;

            case 0xF6:
                i_arib_score_flags |= 1 << 1;
                break;

            default:
                break;
        }
    }

    if ( p_sys->standard == TS_STANDARD_AUTO &&
         registration_type == TS_PMT_REGISTRATION_NONE &&
         i_arib_score_flags == 0x07 ) //0b111
    {
        registration_type = TS_PMT_REGISTRATION_ARIB;
    }

    *p_registration_type = registration_type;
}

static void ParsePMTPrivateRegistrations( demux_t *p_demux, const dvbpsi_descriptor_t *p_firstdr,
                                          ts_pmt_t *p_pmt, ts_standards_e i_standard )
{
    VLC_UNUSED(p_pmt);
     /* Now process private descriptors >= 0x40 */
    for( const dvbpsi_descriptor_t *p_dr = p_firstdr; p_dr != NULL; p_dr = p_dr->p_next )
    {
        if( p_dr->i_tag < 0x40 )
            continue;

        switch( i_standard )
        {
            case TS_STANDARD_ARIB:
            {
                const char *psz_desc = ARIB_B10_Get_PMT_Descriptor_Description( p_dr->i_tag );
                if( psz_desc )
                    msg_Dbg( p_demux, PMT_DESC_PREFIX "%s (0x%x)", psz_desc, p_dr->i_tag );
                else
                    msg_Dbg( p_demux, PMT_DESC_PREFIX "Unknown Private (0x%x)", p_dr->i_tag );
            }
            break;

            case TS_STANDARD_DVB:
            case TS_STANDARD_AUTO:
            {
                if( p_dr->i_tag == 0x88 )
                {
                    /* EACEM Simulcast HD Logical channels ordering */
                    /* TODO: apply visibility flags */
                    msg_Dbg( p_demux, PMT_DESC_PREFIX "EACEM Simulcast HD" );
                    break;
                }
            }
            /* fallthrough */
            default:
                msg_Dbg( p_demux, PMT_DESC_PREFIX "Unknown Private (0x%x)", p_dr->i_tag );
                break;
        }
    }
}

/*****************************************************************************
 * PMT callback and helpers
 *****************************************************************************/
static dvbpsi_descriptor_t *PMTEsFindDescriptor( const dvbpsi_pmt_es_t *p_es,
                                                 int i_tag )
{
    dvbpsi_descriptor_t *p_dr = p_es->p_first_descriptor;
    while( p_dr && ( p_dr->i_tag != i_tag ) )
        p_dr = p_dr->p_next;
    return p_dr;
}
static bool PMTEsHasRegistration( demux_t *p_demux,
                                  const dvbpsi_pmt_es_t *p_es,
                                  const char *psz_tag )
{
    dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_es, 0x05 );
    if( !p_dr )
        return false;

    if( p_dr->i_length < 4 )
    {
        msg_Warn( p_demux, "invalid Registration Descriptor" );
        return false;
    }

    assert( strlen(psz_tag) == 4 );
    return !memcmp( p_dr->p_data, psz_tag, 4 );
}

static bool PMTEsHasComponentTagBetween( const dvbpsi_pmt_es_t *p_es,
                                         uint8_t i_low, uint8_t i_high )
{
    dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_es, 0x52 );
    if( !p_dr )
        return false;
    dvbpsi_stream_identifier_dr_t *p_si = dvbpsi_DecodeStreamIdentifierDr( p_dr );
    if( !p_si )
        return false;

    return p_si->i_component_tag >= i_low && p_si->i_component_tag <= i_high;
}

static ts_standards_e ProbePMTStandard( const dvbpsi_pmt_t *p_dvbpsipmt )
{
    dvbpsi_pmt_es_t *p_dvbpsies;
    for( p_dvbpsies = p_dvbpsipmt->p_first_es; p_dvbpsies; p_dvbpsies = p_dvbpsies->p_next )
    {
        if( p_dvbpsies->i_type == 0x06 )
        {
            /* Probe for ARIB subtitles */
            dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0xFD );
            if( p_dr && p_dr->i_length >= 2 )
            {
                const uint16_t i_data_component_id = GetWBE(p_dr->p_data);
                if( ( i_data_component_id == 0x0008 &&
                      PMTEsHasComponentTagBetween( p_dvbpsies, 0x30, 0x37 ) ) ||
                    ( i_data_component_id == 0x0012 &&
                      PMTEsHasComponentTagBetween( p_dvbpsies, 0x87, 0x88 ) ) )
                    return TS_STANDARD_ARIB;
            }
        }
    }
    return TS_STANDARD_AUTO;
}

static void SetupAudioExtendedDescriptors( demux_t *p_demux, ts_es_t *p_es,
                                           const dvbpsi_pmt_es_t *p_dvbpsies )
{
    demux_sys_t *p_sys = p_demux->p_sys;

    if( p_sys->standard == TS_STANDARD_AUTO ||
        p_sys->standard == TS_STANDARD_DVB )
    {
        const dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x7F );
        if( p_dr && p_dr->i_length > 1 && p_dr->p_data[0] == 0x06 /* Tag extension */ )
        {
            static const char *editorial_classification_coding[] = {
                DESC_MAIN_AUDIO,
                DESC_AUDIO_DESC_VISUALLY_IMPAIRED,
                DESC_CLEAN_AUDIO_HEARING_IMPAIRED,
                DESC_SPOKEN_SUBTITLES_VISUAL_IMP,
            };

            uint8_t i_audio_type = (p_dr->p_data[1] & 0x7F) >> 2;

            if( i_audio_type < ARRAY_SIZE(editorial_classification_coding) )
            {
                free( p_es->fmt.psz_description );
                p_es->fmt.psz_description = strdup(editorial_classification_coding[i_audio_type]);
            }

            if( i_audio_type == 0x00 /* Main Audio */ )
                p_es->fmt.i_priority = ES_PRIORITY_SELECTABLE_MIN + 1;

            if( (p_dr->p_data[1] & 0x80) == 0x00 ) /* Split mixed */
                p_es->fmt.i_priority = ES_PRIORITY_NOT_DEFAULTABLE;

            if( (p_dr->p_data[1] & 0x01) && p_dr->i_length >= 5 )
            {
                free( p_es->fmt.psz_language );
                p_es->fmt.psz_language = malloc( 4 );
                if( p_es->fmt.psz_language )
                {
                    memcpy( p_es->fmt.psz_language, &p_dr->p_data[2], 3 );
                    p_es->fmt.psz_language[3] = 0;
                    msg_Dbg( p_demux, "      found language: %s", p_es->fmt.psz_language );
                }
            }
        }
    }
}

static char *GetIso639AudioTypeDesc( uint8_t type )
{
    static const char *audio_type[] = {
        /* "Main audio", */
        DESC_CLEAN_EFFECTS,
        DESC_CLEAN_AUDIO_HEARING_IMPAIRED,
        DESC_VISUAL_IMPAIRED_COMMENTS,
    };

    if ( type == 0 || type >= ARRAY_SIZE(audio_type) )
        return NULL;

    return strdup( audio_type[ --type ] );
}

static void SetupISO639Descriptor( demux_t *p_demux, ts_es_t *p_es,
                                   dvbpsi_descriptor_t *p_dr )
{
    dvbpsi_iso639_dr_t *p_decoded = dvbpsi_DecodeISO639Dr( p_dr );
    if( !p_decoded )
    {
        msg_Err( p_demux, "      Failed to decode a ISO 639 descriptor" );
        return;
    }

    if( !p_es->fmt.psz_language )
    {
        p_es->fmt.psz_language = malloc( 4 );
        if( p_es->fmt.psz_language )
        {
            memcpy( p_es->fmt.psz_language, p_decoded->code[0].iso_639_code, 3 );
            p_es->fmt.psz_language[3] = 0;
            msg_Dbg( p_demux, "      found language: %s", p_es->fmt.psz_language);
        }
    }

    uint8_t type = p_decoded->code[0].i_audio_type;
    if( !p_es->fmt.psz_description )
        p_es->fmt.psz_description = GetIso639AudioTypeDesc( type );
    if (type == 0x00) /* Undefined */
        p_es->fmt.i_priority = ES_PRIORITY_SELECTABLE_MIN + 1; // prioritize normal audio tracks

    if( p_es->fmt.p_extra_languages )
        return;

    p_es->fmt.i_extra_languages = p_decoded->i_code_count-1;
    if( p_es->fmt.i_extra_languages > 0 )
        p_es->fmt.p_extra_languages =
            malloc( sizeof(*p_es->fmt.p_extra_languages) *
                    p_es->fmt.i_extra_languages );
    if( p_es->fmt.p_extra_languages )
    {
        for( unsigned i = 0; i < p_es->fmt.i_extra_languages; i++ )
        {
            extra_languages_t *p_lang = &p_es->fmt.p_extra_languages[i];
            if( (p_lang->psz_language = malloc(4)) )
            {
                memcpy( p_lang->psz_language, p_decoded->code[i+1].iso_639_code, 3 );
                p_lang->psz_language[3] = '\0';
            }
            p_lang->psz_description = GetIso639AudioTypeDesc( p_decoded->code[i].i_audio_type );
        }
    }
}

static void SetupStandardESDescriptors( demux_t *p_demux, ts_es_t *p_es,
                                        const dvbpsi_pmt_es_t *p_dvbpsies )
{
    for( dvbpsi_descriptor_t *p_dr = p_dvbpsies->p_first_descriptor;
                              p_dr; p_dr = p_dr->p_next )
    {
        switch( p_dr->i_tag )
        {
            case 0x0a:
                SetupISO639Descriptor( p_demux, p_es, p_dr );
                break;

            case 0x0E:
            {
                dvbpsi_max_bitrate_dr_t *p_btdr = dvbpsi_DecodeMaxBitrateDr( p_dr );
                if( p_btdr && !p_es->fmt.i_bitrate )
                    p_es->fmt.i_bitrate = p_btdr->i_max_bitrate * 50 * 8;
            }
            break;
        }
    }
}

static void SetupISO14496Descriptors( demux_t *p_demux, ts_stream_t *p_pes,
                                      const ts_pmt_t *p_pmt, const dvbpsi_pmt_es_t *p_dvbpsies )
{
    const dvbpsi_descriptor_t *p_dr = p_dvbpsies->p_first_descriptor;
    ts_es_t *p_es = p_pes->p_es;

    while( p_dr )
    {
        uint8_t i_length = p_dr->i_length;

        switch( p_dr->i_tag )
        {
            case 0x1f: /* FMC Descriptor */
                while( i_length >= 2 /* see below */ && !p_es->i_sl_es_id )
                {
                    p_es->i_sl_es_id = ( p_dr->p_data[0] << 8 ) | p_dr->p_data[1];
                    /* FIXME: map all ids and flexmux channels */
                    /* Handle broken streams with 2 byte 0x1F descriptors
                     * see samples/A-codecs/AAC/freetv_aac_latm.txt */
                    if( i_length == 2 )
                        break;
                    i_length -= 3;
                    msg_Dbg( p_demux, "     - found FMC_descriptor mapping es_id=%"PRIu16, p_es->i_sl_es_id );
                }
                break;
            case 0x1e: /* SL Descriptor */
                if( i_length == 2 )
                {
                    p_es->i_sl_es_id = ( p_dr->p_data[0] << 8 ) | p_dr->p_data[1];
                    msg_Dbg( p_demux, "     - found SL_descriptor mapping es_id=%"PRIu16, p_es->i_sl_es_id );

                    if( p_dvbpsies->i_type == 0x12 ) /* SL AU pes stream */
                    {
                        if( !p_pes->p_proc )
                            p_pes->p_proc = SL_stream_processor_New( p_pes );
                    }
                    else if( p_dvbpsies->i_type == 0x13 ) /* IOD / SL sections */
                    {
                        ts_sections_processor_Add( p_demux,
                                                   &p_pes->p_sections_proc, 0x05, 0x00,
                                                   SLPackets_Section_Handler, p_pes );
                    }
                    p_pes->b_always_receive = true;
                }
                break;
            default:
                break;
        }

        p_dr = p_dr->p_next;
    }

    if( p_es->i_sl_es_id )
    {
        const es_mpeg4_descriptor_t *p_mpeg4desc = GetMPEG4DescByEsId( p_pmt, p_es->i_sl_es_id );
        if( p_mpeg4desc && p_mpeg4desc->b_ok )
        {
            if( !SetupISO14496LogicalStream( p_demux, &p_mpeg4desc->dec_descr, &p_es->fmt ) )
                msg_Dbg( p_demux, "     - IOD not yet available for es_id=%"PRIu16, p_es->i_sl_es_id );
        }
    }
    else
    {
        switch( p_dvbpsies->i_type )
        {
        /* non fatal, set by packetizer */
        case 0x0f: /* ADTS */
        case 0x11: /* LOAS */
            msg_Dbg( p_demux, "     - SL/FMC descriptor not found/matched" );
            break;
        default:
            msg_Err( p_demux, "      - SL/FMC descriptor not found/matched" );
            break;
        }
    }
}

static void SetupMetadataDescriptors( demux_t *p_demux, ts_stream_t *p_stream, const dvbpsi_pmt_es_t *p_dvbpsies )
{
    ts_es_t *p_es = p_stream->p_es;
    for( const dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x26 );
                                   p_dr; p_dr = p_dr->p_next )
    {
        if( p_dr->i_tag != 0x26 || p_dr->i_length < 5 )
            continue;

        const uint8_t *p_data = p_dr->p_data;
        size_t i_data = p_dr->i_length;

        if( GetWBE(p_data) == 0xFFFF ) /* metadata_application_format */
        {
            if( i_data < 9 )
                return;
            p_es->metadata.i_application_format_identifier = VLC_FOURCC(p_data[2], p_data[3], p_data[4], p_data[5]);
            p_data += 4; i_data -= 4;
        }
        p_data += 2; i_data -= 2;

        if(p_data[0] == 0xFF) /* metadata_format */
        {
            if( i_data < 7 )
                return;
            p_es->metadata.i_format_identifier = VLC_FOURCC(p_data[1], p_data[2], p_data[3], p_data[4]);
            p_data += 4; i_data -= 4;
        }
        p_data += 1; i_data -= 1;

        p_es->metadata.i_service_id = p_data[0];
        const uint8_t flags = p_data[1];
        p_data += 2; i_data -= 2;

        /* metadata_application_format_identifier ID3\x20
         * metadata_format_identifier ID3\x20 */
        if( p_es->metadata.i_application_format_identifier == METADATA_IDENTIFIER_ID3 &&
            p_es->metadata.i_format_identifier == METADATA_IDENTIFIER_ID3 &&
            (flags & 0xF0) == 0x00 )
        {
            msg_Dbg( p_demux, "     - found Metadata_descriptor type ID3 with service_id=0x%"PRIx8,
                     p_es->metadata.i_service_id );
            if( !p_stream->p_proc )
                p_stream->p_proc = Metadata_stream_processor_New( p_stream, p_demux->out );
            break;
        }
    }
}

static void SetupAVCDescriptors( demux_t *p_demux, ts_es_t *p_es, const dvbpsi_pmt_es_t *p_dvbpsies )
{
    const dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x28 );
    if( p_dr && p_dr->i_length >= 4 )
    {
        p_es->fmt.i_profile = p_dr->p_data[0];
        p_es->fmt.i_level = p_dr->p_data[2];
        msg_Dbg( p_demux, "     - found AVC_video_descriptor profile=0x%.2x level=0x%.2x",
                 p_es->fmt.i_profile, p_es->fmt.i_level );
    }
}

static void SetupJ2KDescriptors( demux_t *p_demux, ts_es_t *p_es, const dvbpsi_pmt_es_t *p_dvbpsies )
{
    const dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x32 );
    if( p_dr && p_dr->i_length >= 24 )
    {
        es_format_Change( &p_es->fmt, VIDEO_ES, VLC_CODEC_JPEG2000 );
        p_es->fmt.i_profile = p_dr->p_data[0];
        p_es->fmt.i_level = p_dr->p_data[1];
        p_es->fmt.video.i_width = GetDWBE(&p_dr->p_data[2]);
        p_es->fmt.video.i_height = GetDWBE(&p_dr->p_data[6]);
        p_es->fmt.video.i_frame_rate_base = GetWBE(&p_dr->p_data[18]);
        p_es->fmt.video.i_frame_rate = GetWBE(&p_dr->p_data[20]);
        j2k_fill_color_profile( p_dr->p_data[21],
                               &p_es->fmt.video.primaries,
                               &p_es->fmt.video.transfer,
                               &p_es->fmt.video.space );
        p_es->b_interlaced = p_dr->p_data[23] & 0x40;
        if( p_dr->i_length > 24 )
        {
            p_es->fmt.p_extra = malloc(p_dr->i_length - 24);
            if( p_es->fmt.p_extra )
                p_es->fmt.i_extra = p_dr->i_length - 24;
        }
        msg_Dbg( p_demux, "     - found J2K_video_descriptor profile=0x%.2x level=0x%.2x",
                 p_es->fmt.i_profile, p_es->fmt.i_level );
    }
}

static void SetupTTMLExtendedDescriptor( demux_t *p_demux,
                                         const dvbpsi_descriptor_t *p_dr, es_format_t *p_fmt )
{
    static const struct
    {
        uint8_t val;
        const char * psz;
    } subtitle_purpose[] = {
        { 0x00, DESC_SUBS_SAME_LANG },
        { 0x01, DESC_SUBS_FOREIGN_LANG },
        { 0x02, DESC_SUBS_ALL_LANG },
        { 0x10, DESC_SUBS_HEARING_IMPAIRED },
        { 0x11, DESC_SUBS_HEARING_IMPAIRED },
        { 0x12, DESC_SUBS_HEARING_IMPAIRED },
        { 0x30, DESC_SUBS_AUDIO_DESCRIPTION_TRANS },
        { 0x31, DESC_SUBS_COMMENTARY },
    };

    if( p_dr->i_length < 9 )
        return;

    msg_Dbg( p_demux, "     - found TTML_descriptor" );
    if( !p_fmt->psz_language )
    {
        p_fmt->psz_language = strndup( (const char *)&p_dr->p_data[1], 3 );
        msg_Dbg( p_demux, "       language: %s", p_fmt->psz_language );
    }

    /* variable members */
    const uint8_t *p_data = &p_dr->p_data[6];
    size_t i_data = p_dr->i_length - 6;

    /* profiles */
    uint8_t i_profiles_count = (p_dr->p_data[5] & 0x0F);
    if( i_data <= i_profiles_count )
        goto end;
    p_data += i_profiles_count;
    i_data -= i_profiles_count;

    /* qualifier */
    if( p_dr->p_data[5] & 0x40 )
    {
        if( i_data <= 4 )
            goto end;
        p_data += 4;
        i_data -= 4;
    }

    /* font usage */
    if( p_dr->p_data[5] & 0x80 )
    {
        if( i_data <= 1U || i_data <= 1U + p_data[0] )
            goto end;
        i_data -= 1 + p_data[0];
        p_data += 1 + p_data[0];
    }

    /* text... finally */
    if( i_data < 1U || i_data < 1U + p_data[0] )
        goto end;
    if( !p_fmt->psz_description )
        p_fmt->psz_description = strndup( (const char*) &p_data[1], p_data[0] );

end:
    /* Apply */
    for( size_t i=0; i<ARRAY_SIZE(subtitle_purpose); i++ )
    {
        if( subtitle_purpose[i].val == (p_dr->p_data[4] >> 2) )
        {
            if( !p_fmt->psz_description )
                p_fmt->psz_description = strdup( subtitle_purpose[i].psz );
            break;
        }
    }
}

typedef struct
{
    int  i_type;
    int  i_magazine;
    int  i_page;
    char p_iso639[3];
} ts_teletext_page_t;

static const char *const ppsz_teletext_type[] = {
 "",
 DESC_TELETEXT,
 DESC_TELETEXT_SUBTITLES,
 DESC_TELETEXT_ADDTNL_INFO,
 DESC_TELETEXT_SCHEDULE,
 DESC_TELETEXT_SUBS_HEARING_IMPAIRED
};

static void PMTSetupEsTeletext( demux_t *p_demux, ts_stream_t *p_pes,
                                const dvbpsi_pmt_es_t *p_dvbpsies )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    es_format_t *p_fmt = &p_pes->p_es->fmt;

    ts_teletext_page_t p_page[2 * 64 + 20];
    unsigned i_page = 0;
    dvbpsi_descriptor_t *p_dr;

    /* Gather pages information */
    for( unsigned i_tag_idx = 0; i_tag_idx < 2; i_tag_idx++ )
    {
        p_dr = PMTEsFindDescriptor( p_dvbpsies, i_tag_idx == 0 ? 0x46 : 0x56 );
        if( !p_dr )
            continue;

        dvbpsi_teletext_dr_t *p_sub = dvbpsi_DecodeTeletextDr( p_dr );
        if( !p_sub )
            continue;

        for( int i = 0; i < p_sub->i_pages_number; i++ )
        {
            const dvbpsi_teletextpage_t *p_src = &p_sub->p_pages[i];

            if( p_src->i_teletext_type >= 0x06 )
                continue;

            assert( i_page < sizeof(p_page)/sizeof(*p_page) );

            ts_teletext_page_t *p_dst = &p_page[i_page++];

            p_dst->i_type = p_src->i_teletext_type;
            p_dst->i_magazine = p_src->i_teletext_magazine_number
                ? p_src->i_teletext_magazine_number : 8;
            p_dst->i_page = p_src->i_teletext_page_number;
            memcpy( p_dst->p_iso639, p_src->i_iso6392_language_code, 3 );
        }
    }

    p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x59 );
    if( p_dr )
    {
        dvbpsi_subtitling_dr_t *p_sub = dvbpsi_DecodeSubtitlingDr( p_dr );
        for( int i = 0; p_sub && i < p_sub->i_subtitles_number; i++ )
        {
            dvbpsi_subtitle_t *p_src = &p_sub->p_subtitle[i];

            if( p_src->i_subtitling_type < 0x01 || p_src->i_subtitling_type > 0x03 )
                continue;

            assert( i_page < sizeof(p_page)/sizeof(*p_page) );

            ts_teletext_page_t *p_dst = &p_page[i_page++];

            switch( p_src->i_subtitling_type )
            {
            case 0x01:
                p_dst->i_type = 0x02;
                break;
            default:
                p_dst->i_type = 0x03;
                break;
            }
            /* FIXME check if it is the right split */
            p_dst->i_magazine = (p_src->i_composition_page_id >> 8)
                ? (p_src->i_composition_page_id >> 8) : 8;
            p_dst->i_page = p_src->i_composition_page_id & 0xff;
            memcpy( p_dst->p_iso639, p_src->i_iso6392_language_code, 3 );
        }
    }

    /* */
    es_format_Change(p_fmt, SPU_ES, VLC_CODEC_TELETEXT );

    if( !p_sys->b_split_es || i_page <= 0 )
    {
        p_fmt->subs.teletext.i_magazine = 255;
        p_fmt->subs.teletext.i_page = 0;
        p_fmt->psz_description = strdup( vlc_gettext(ppsz_teletext_type[1]) );

        p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x46 );
        if( !p_dr )
            p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x56 );

        if( !p_sys->b_split_es && p_dr && p_dr->i_length > 0 )
        {
            /* Descriptor pass-through */
            p_fmt->p_extra = malloc( p_dr->i_length );
            if( p_fmt->p_extra )
            {
                p_fmt->i_extra = p_dr->i_length;
                memcpy( p_fmt->p_extra, p_dr->p_data, p_dr->i_length );
            }
        }
    }
    else
    {
        ts_es_t *p_page_es = p_pes->p_es;
        enum txt_pass_s
        {
            TXT_SUBTITLES = 0,
            TXT_INDEX_PAGE,
            TXT_OTHER,
        };
        for( enum txt_pass_s pass = TXT_SUBTITLES; pass <= TXT_OTHER; pass++ )
        {
            for( unsigned i = 0; i < i_page; i++ )
            {
                const ts_teletext_page_t *p = &p_page[i];
                switch(p->i_type)
                {
                    case 0x01:
                        if(pass != TXT_INDEX_PAGE)
                            continue;
                        break;
                    case 0x02:
                    case 0x05:
                        if(pass != TXT_SUBTITLES)
                            continue;
                        break;
                    default:
                        if(pass != TXT_OTHER)
                            continue;
                        break;
                }

                /* */
                if( !p_page_es )
                {
                    p_page_es = ts_es_New( p_pes->p_es->p_program );
                    if( !p_page_es )
                        break;

                    es_format_Copy( &p_page_es->fmt, p_fmt );
                    free( p_page_es->fmt.psz_language );
                    free( p_page_es->fmt.psz_description );
                    p_page_es->fmt.psz_language = NULL;
                    p_page_es->fmt.psz_description = NULL;
                    ts_stream_Add_es( p_pes, p_page_es, true );
                }

                /* */
                p_page_es->fmt.i_priority = (pass == TXT_SUBTITLES) ?
                          ES_PRIORITY_SELECTABLE_MIN : ES_PRIORITY_NOT_DEFAULTABLE;
                p_page_es->fmt.psz_language = strndup( p->p_iso639, 3 );
                p_page_es->fmt.psz_description = strdup(vlc_gettext(ppsz_teletext_type[p->i_type]));
                p_page_es->fmt.subs.teletext.i_magazine = p->i_magazine;
                p_page_es->fmt.subs.teletext.i_page = p->i_page;

                msg_Dbg( p_demux,
                             "    * ttxt type=%s lan=%s page=%d%02x",
                             p_page_es->fmt.psz_description,
                             p_page_es->fmt.psz_language,
                             p->i_magazine, p->i_page );

                p_page_es = NULL; /* used */
            }
        }
    }
}
static void PMTSetupEsDvbSubtitle( demux_t *p_demux, ts_stream_t *p_pes,
                                   const dvbpsi_pmt_es_t *p_dvbpsies )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    es_format_t *p_fmt = &p_pes->p_es->fmt;

    es_format_Change( p_fmt, SPU_ES, VLC_CODEC_DVBS );

    dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x59 );
    int i_page = 0;
    dvbpsi_subtitling_dr_t *p_sub = dvbpsi_DecodeSubtitlingDr( p_dr );
    for( int i = 0; p_sub && i < p_sub->i_subtitles_number; i++ )
    {
        const int i_type = p_sub->p_subtitle[i].i_subtitling_type;
        if( ( i_type >= 0x10 && i_type <= 0x14 ) ||
            ( i_type >= 0x20 && i_type <= 0x24 ) )
            i_page++;
    }

    if( !p_sys->b_split_es  || i_page <= 0 )
    {
        p_fmt->subs.dvb.i_id = -1;
        p_fmt->psz_description = strdup( _("DVB subtitles") );

        if( !p_sys->b_split_es && p_dr && p_dr->i_length > 0 )
        {
            /* Descriptor pass-through */
            p_fmt->p_extra = malloc( p_dr->i_length );
            if( p_fmt->p_extra )
            {
                p_fmt->i_extra = p_dr->i_length;
                memcpy( p_fmt->p_extra, p_dr->p_data, p_dr->i_length );
            }
        }
    }
    else
    {
        for( int i = 0; i < p_sub->i_subtitles_number; i++ )
        {
            ts_es_t *p_subs_es;

            /* */
            if( i == 0 )
            {
                p_subs_es = p_pes->p_es;
            }
            else
            {
                p_subs_es = ts_es_New( p_pes->p_es->p_program );
                if( !p_subs_es )
                    break;

                es_format_Copy( &p_subs_es->fmt, p_fmt );
                free( p_subs_es->fmt.psz_language );
                free( p_subs_es->fmt.psz_description );
                p_subs_es->fmt.psz_language = NULL;
                p_subs_es->fmt.psz_description = NULL;

                ts_stream_Add_es( p_pes, p_subs_es, true );
            }

            /* */
            const dvbpsi_subtitle_t *p = &p_sub->p_subtitle[i];
            p_subs_es->fmt.psz_language = strndup( (char *)p->i_iso6392_language_code, 3 );
            switch( p->i_subtitling_type )
            {
            case 0x10: /* unspec. */
            case 0x11: /* 4:3 */
            case 0x12: /* 16:9 */
            case 0x13: /* 2.21:1 */
            case 0x14: /* HD monitor */
                p_subs_es->fmt.psz_description = strdup( _("DVB subtitles") );
                break;
            case 0x20: /* Hearing impaired unspec. */
            case 0x21: /* h.i. 4:3 */
            case 0x22: /* h.i. 16:9 */
            case 0x23: /* h.i. 2.21:1 */
            case 0x24: /* h.i. HD monitor */
                p_subs_es->fmt.psz_description = strdup( _("DVB subtitles: hearing impaired") );
                break;
            default:
                break;
            }

            /* Hack, FIXME */
            p_subs_es->fmt.subs.dvb.i_id = ( p->i_composition_page_id <<  0 ) |
                                      ( p->i_ancillary_page_id   << 16 );
        }
    }
}

static void OpusSetup(demux_t *demux, uint8_t *p, size_t len, es_format_t *p_fmt)
{
    OpusHeader h;
    opus_header_init(&h);

    /* default mapping */
    static const unsigned char map[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
    memcpy(h.stream_map, map, sizeof(map));

    uint8_t mapping;
    int csc;
    unsigned int channels = 0;
    unsigned int stream_count = 0;
    int ccc = p[1]; // channel_config_code
    if (ccc <= 8) {
        channels = ccc;
        if (channels)
            mapping = channels > 2;
        else {
            mapping = 255;
            channels = 2; // dual mono
        }
        static const uint8_t p_csc[8] = { 0, 1, 1, 2, 2, 2, 3, 3 };
        csc = p_csc[channels - 1];
        stream_count = channels - csc;

        static const uint8_t maps[6][7] = {
            { 2,1 },
            { 1,2,3 },
            { 4,1,2,3 },
            { 4,1,2,3,5 },
            { 4,1,2,3,5,6 },
            { 6,1,2,3,4,5,7 },
        };
        if (channels > 2)
            memcpy(&h.stream_map[1], maps[channels-3], channels - 1);
    } else if (ccc == 0x81) {
        if (len < 4)
            goto explicit_config_too_short;

        channels = p[2];
        mapping = p[3];
        csc = 0;
        if (mapping) {
            bs_t s;
            bs_init(&s, &p[4], len - 4);
            stream_count = 1;
            if (channels) {
                int bits = stdc_bit_width(channels - 1);
                if (s.i_left < bits)
                    goto explicit_config_too_short;
                stream_count = bs_read(&s, bits) + 1;
                bits = stdc_bit_width(stream_count);
                if (s.i_left < bits)
                    goto explicit_config_too_short;
                csc = bs_read(&s, bits);
            }
            int channel_bits = stdc_bit_width(stream_count + csc);
            if (s.i_left < channels * channel_bits)
                goto explicit_config_too_short;

            unsigned char silence = (1U << (stream_count + csc + 1)) - 1;
            for (unsigned int i = 0; i < channels; i++) {
                unsigned char m = bs_read(&s, channel_bits);
                if (m == silence)
                    m = 0xff;
                h.stream_map[i] = m;
            }
        }
    } else if (ccc >= 0x80 && ccc <= 0x88) {
        channels = ccc - 0x80;
        if (channels)
            mapping = 1;
        else {
            mapping = 255;
            channels = 2; // dual mono
        }
        csc = 0;
        stream_count = channels;
    } else {
        msg_Err(demux, "Opus channel configuration 0x%.2x is reserved", ccc);
    }

    if (!channels) {
        msg_Err(demux, "Opus channel configuration 0x%.2x not supported yet", p[1]);
        opus_header_clean(&h);
        return;
    }

    opus_prepare_header(channels, 0, &h);
    h.preskip = 0;
    h.input_sample_rate = 48000;
    h.nb_coupled = csc;
    h.nb_streams = channels - csc;
    h.channel_mapping = mapping;

    if (h.channels) {
        uint8_t *p_extra = NULL;
        size_t i_extra = 0;
        opus_write_header(&p_extra, &i_extra, &h, NULL /* FIXME */);
        if (p_extra) {
            es_format_Change(p_fmt, AUDIO_ES, VLC_CODEC_OPUS);
            p_fmt->p_extra = p_extra;
            p_fmt->i_extra = i_extra;
            p_fmt->audio.i_channels = h.channels;
            p_fmt->audio.i_rate = 48000;
        }
    }
    opus_header_clean(&h);

    return;

explicit_config_too_short:
    opus_header_clean(&h);
    msg_Err(demux, "Opus descriptor too short");
}

static void PMTSetupEs0x02( ts_es_t *p_es,
                            const dvbpsi_pmt_es_t *p_dvbpsies )
{
    dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x02 );
    if( p_dr )
    {
        /* sample: wcbs.ts */
        const dvbpsi_vstream_dr_t *p_vdr = dvbpsi_DecodeVStreamDr( p_dr );
        if( p_vdr )
        {
            if( p_vdr->i_frame_rate_code > 1 && p_vdr->i_frame_rate_code < 9 &&
                !p_vdr->b_multiple_frame_rate )
            {
                static const int code_to_frame_rate[8][2] =
                {
                    { 24000, 1001 }, { 24, 1 }, { 25, 1 },       { 30000, 1001 },
                    { 30, 1 },       { 50, 1 }, { 60000, 1001 }, { 60, 1 },
                };
                p_es->fmt.video.i_frame_rate = code_to_frame_rate[p_vdr->i_frame_rate_code - 1][0];
                p_es->fmt.video.i_frame_rate_base = code_to_frame_rate[p_vdr->i_frame_rate_code - 1][1];
            }
            if( !p_vdr->b_mpeg2 && p_es->fmt.i_codec == VLC_CODEC_MPGV )
                p_es->fmt.i_original_fourcc = VLC_CODEC_MP1V;
        }
    }

    /* MPEG2_stereoscopic_video_format_descriptor */
    p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x34 );
    if( p_dr && p_dr->i_length > 0 && (p_dr->p_data[0] & 0x80) )
    {
        video_multiview_mode_t mode;
        switch( p_dr->p_data[0] & 0x7F )
        {
            case 0x03:
                mode = MULTIVIEW_STEREO_SBS; break;
            case 0x04:
                mode = MULTIVIEW_STEREO_TB; break;
            case 0x08:
            default:
                mode = MULTIVIEW_2D; break;
        }
        p_es->fmt.video.multiview_mode = mode;
    }
}

static void PMTSetupEs0x05PrivateData( demux_t *p_demux, ts_es_t *p_es,
                                       const dvbpsi_pmt_es_t *p_dvbpsies )
{
    demux_sys_t *p_sys = p_demux->p_sys;

    VLC_UNUSED(p_es);
    if( p_sys->standard == TS_STANDARD_DVB ||
        p_sys->standard == TS_STANDARD_AUTO )
    {
        dvbpsi_descriptor_t *p_ait_dr = PMTEsFindDescriptor( p_dvbpsies, 0x6F );
        if( p_ait_dr )
        {
            uint8_t *p_data = p_ait_dr->p_data;
            for( uint8_t i_data = p_ait_dr->i_length; i_data >= 3; i_data -= 3, p_data += 3 )
            {
                uint16_t i_app_type = ((p_data[0] & 0x7F) << 8) | p_data[1];
                uint8_t i_version   = p_data[2] & 0x1F;
                msg_Dbg( p_demux, "      - Application type 0x%"PRIx16" version %"PRIu8,
                         i_app_type, i_version);
            }
        }
    }
}

static void PMTSetupEs0x06( demux_t *p_demux, ts_stream_t *p_pes,
                            const dvbpsi_pmt_es_t *p_dvbpsies )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    es_format_t *p_fmt = &p_pes->p_es->fmt;
    dvbpsi_descriptor_t *p_subs_dr = PMTEsFindDescriptor( p_dvbpsies, 0x59 );
    dvbpsi_descriptor_t *desc;
    if( PMTEsHasRegistration( p_demux, p_dvbpsies, "EAC3" ) ||
        PMTEsFindDescriptor( p_dvbpsies, 0x7a ) )
    {
        /* DVB with stream_type 0x06 (ETS EN 300 468) */
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_EAC3 );
    }
    else if( PMTEsHasRegistration( p_demux, p_dvbpsies, "AC-3" ) ||
             PMTEsFindDescriptor( p_dvbpsies, 0x6a ) ||
             PMTEsFindDescriptor( p_dvbpsies, 0x81 ) ) /* AC-3 channel (also in EAC3) */
    {
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_A52 );
    }
    else if( PMTEsHasRegistration( p_demux, p_dvbpsies, "DTS1" ) || /* 512 Bpf */
             PMTEsHasRegistration( p_demux, p_dvbpsies, "DTS2" ) || /* 1024 Bpf */
             PMTEsHasRegistration( p_demux, p_dvbpsies, "DTS3" ) || /* 2048 Bpf */
             PMTEsFindDescriptor( p_dvbpsies, 0x73 ) )
    {
        /*registration descriptor(ETSI TS 101 154 Annex F)*/
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_DTS );
    }
    else if( PMTEsHasRegistration( p_demux, p_dvbpsies, "BSSD" ) && !p_subs_dr )
    {
        /* BSSD is AES3 DATA, but could also be subtitles
         * we need to check for secondary descriptor then s*/
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_302M );
        p_fmt->b_packetized = true;
    }
    else if( PMTEsHasRegistration( p_demux, p_dvbpsies, "HEVC" ) )
    {
        es_format_Change( p_fmt, VIDEO_ES, VLC_CODEC_HEVC );
    }
    else if( (desc = PMTEsFindDescriptor( p_dvbpsies, 0x7f )) &&
             desc->i_length >= 2 )
    {
        /* extended_descriptor on PMT (DVB Bluebook A038) */
        switch( desc->p_data[0] )
        {
            case 0x80: /* User Defined */
                 /* non finalized Opus in TS Draft. Can't really tell...
                  * So ffmpeg produced mixes with System-A reg */
                if( PMTEsHasRegistration(p_demux, p_dvbpsies, "Opus") )
                    OpusSetup(p_demux, desc->p_data, desc->i_length, p_fmt);
                break;
            case 0x0E: /* DTS HD */
                es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_DTS );
                p_fmt->i_profile = PROFILE_DTS_HD;
                break;
            case 0x0F: /* DTS Neural */
                es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_DTS );
                break;
            case 0x15: /* Dolby AC-4 */
                es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_AC4 );
                break;
            case 0x20:
                es_format_Change( p_fmt, SPU_ES, VLC_CODEC_TTML_TS );
                SetupTTMLExtendedDescriptor( p_demux, desc, p_fmt );
                break;
        }
    }
    else if( p_sys->standard == TS_STANDARD_ARIB )
    {
        /* Lookup our data component descriptor first ARIB STD B10 6.4 */
        dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0xFD );
        /* and check that it maps to something ARIB STD B14 Table 5.1/5.2 */
        if ( p_dr && p_dr->i_length >= 2 )
        {
            /* See STD-B10 Annex J, table J-1 mappings */
            const uint16_t i_data_component_id = GetWBE(p_dr->p_data);
            if( i_data_component_id == 0x0008 &&
                PMTEsHasComponentTagBetween( p_dvbpsies, 0x30, 0x37 ) )
            {
                es_format_Change( p_fmt, SPU_ES, VLC_CODEC_ARIB_A );
                p_fmt->psz_description = strdup( _("ARIB subtitles") );
            }
            else if( i_data_component_id == 0x0012 &&
                     PMTEsHasComponentTagBetween( p_dvbpsies, 0x87, 0x88 ) )
            {
                es_format_Change( p_fmt, SPU_ES, VLC_CODEC_ARIB_C );
                p_fmt->psz_description = strdup( _("ARIB subtitles") );
            }
        }
    }
    else
    {
        /* Subtitle/Teletext/VBI fallbacks */
        dvbpsi_subtitling_dr_t *p_sub;
        if( p_subs_dr && ( p_sub = dvbpsi_DecodeSubtitlingDr( p_subs_dr ) ) )
        {
            for( int i = 0; i < p_sub->i_subtitles_number; i++ )
            {
                if( p_fmt->i_cat != UNKNOWN_ES )
                    break;

                switch( p_sub->p_subtitle[i].i_subtitling_type )
                {
                case 0x01: /* EBU Teletext subtitles */
                case 0x02: /* Associated EBU Teletext */
                case 0x03: /* VBI data */
                    PMTSetupEsTeletext( p_demux, p_pes, p_dvbpsies );
                    break;
                case 0x10: /* DVB Subtitle (normal) with no monitor AR critical */
                case 0x11: /*                 ...   on 4:3 AR monitor */
                case 0x12: /*                 ...   on 16:9 AR monitor */
                case 0x13: /*                 ...   on 2.21:1 AR monitor */
                case 0x14: /*                 ...   for display on a high definition monitor */
                case 0x20: /* DVB Subtitle (impaired) with no monitor AR critical */
                case 0x21: /*                 ...   on 4:3 AR monitor */
                case 0x22: /*                 ...   on 16:9 AR monitor */
                case 0x23: /*                 ...   on 2.21:1 AR monitor */
                case 0x24: /*                 ...   for display on a high definition monitor */
                    PMTSetupEsDvbSubtitle( p_demux, p_pes, p_dvbpsies );
                    break;
                default:
                    msg_Err( p_demux, "Unrecognized DVB subtitle type (0x%x)",
                             p_sub->p_subtitle[i].i_subtitling_type );
                    break;
                }
            }
        }

        if( p_fmt->i_cat == UNKNOWN_ES &&
            ( PMTEsFindDescriptor( p_dvbpsies, 0x45 ) ||  /* VBI Data descriptor */
              PMTEsFindDescriptor( p_dvbpsies, 0x46 ) ||  /* VBI Teletext descriptor */
              PMTEsFindDescriptor( p_dvbpsies, 0x56 ) ) ) /* EBU Teletext descriptor */
        {
            /* Teletext/VBI */
            PMTSetupEsTeletext( p_demux, p_pes, p_dvbpsies );
        }
    }

    /* FIXME is it useful ? */
    if( PMTEsFindDescriptor( p_dvbpsies, 0x52 ) )
    {
        dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x52 );
        dvbpsi_stream_identifier_dr_t *p_si = dvbpsi_DecodeStreamIdentifierDr( p_dr );
        if( p_si )
            msg_Dbg( p_demux, "    * Stream Component Identifier: %d", p_si->i_component_tag );
    }
}

static void PMTSetupEs0xEA( demux_t *p_demux, ts_es_t *p_es,
                           const dvbpsi_pmt_es_t *p_dvbpsies )
{
    /* Registration Descriptor */
    if( !PMTEsHasRegistration( p_demux, p_dvbpsies, "VC-1" ) )
    {
        msg_Err( p_demux, "Registration descriptor not found or invalid" );
        return;
    }

    /* registration descriptor for VC-1 (SMPTE rp227) */
    es_format_Change( &p_es->fmt, VIDEO_ES, VLC_CODEC_VC1 );

    /* XXX With Simple and Main profile the SEQUENCE
     * header is modified: video width and height are
     * inserted just after the start code as 2 int16_t
     * The packetizer will take care of that. */
}

static void PMTSetupEs0xD1( demux_t *p_demux, ts_es_t *p_es,
                           const dvbpsi_pmt_es_t *p_dvbpsies )
{
    /* Registration Descriptor */
    if( !PMTEsHasRegistration( p_demux, p_dvbpsies, "drac" ) )
    {
        msg_Err( p_demux, "Registration descriptor not found or invalid" );
        return;
    }

    /* registration descriptor for Dirac
     * (backwards compatible with VC-2 (SMPTE Sxxxx:2008)) */
    es_format_Change( &p_es->fmt, VIDEO_ES, VLC_CODEC_DIRAC );
}

static void PMTSetupEs0xA0( demux_t *p_demux, ts_es_t *p_es,
                           const dvbpsi_pmt_es_t *p_dvbpsies )
{
    /* MSCODEC sent by vlc */
    dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0xa0 );
    if( !p_dr || p_dr->i_length < 10 )
    {
        msg_Warn( p_demux,
                  "private MSCODEC (vlc) without bih private descriptor" );
        return;
    }

    es_format_t *p_fmt = &p_es->fmt;
    es_format_Change( &p_es->fmt, VIDEO_ES,
                      VLC_FOURCC( p_dr->p_data[0], p_dr->p_data[1],
                                 p_dr->p_data[2], p_dr->p_data[3] ) );
    p_fmt->video.i_width = GetWBE( &p_dr->p_data[4] );
    p_fmt->video.i_height = GetWBE( &p_dr->p_data[6] );
    p_fmt->video.i_visible_width = p_fmt->video.i_width;
    p_fmt->video.i_visible_height = p_fmt->video.i_height;
    p_fmt->i_extra = GetWBE( &p_dr->p_data[8] );

    if( p_fmt->i_extra > 0 )
    {
        p_fmt->p_extra = malloc( p_fmt->i_extra );
        if( p_fmt->p_extra )
            memcpy( p_fmt->p_extra, &p_dr->p_data[10],
                    __MIN( p_fmt->i_extra, (size_t)(p_dr->i_length - 10) ) );
        else
            p_fmt->i_extra = 0;
    }
    /* For such stream we will gather them ourself and don't launch a
     * packetizer.
     * Yes it's ugly but it's the only way to have DIV3 working */
    p_fmt->b_packetized = true;
}

static void PMTSetupEs0x83( const dvbpsi_pmt_t *p_pmt, ts_es_t *p_es, int i_pid )
{
    /* WiDi broadcasts without registration on PMT 0x1, PCR 0x1000 and
     * with audio track pid being 0x1100..0x11FF */
    if ( p_pmt->i_program_number == 0x1 &&
         p_pmt->i_pcr_pid == 0x1000 &&
        ( i_pid >> 8 ) == 0x11 )
    {
        /* Not enough ? might contain 0x83 private descriptor, 2 bytes 0x473F */
        es_format_Change( &p_es->fmt, AUDIO_ES, VLC_CODEC_WIDI_LPCM );
    }
    else
        es_format_Change( &p_es->fmt, AUDIO_ES, VLC_CODEC_DVD_LPCM );
}

static bool PMTSetupEsHDMV( demux_t *p_demux, ts_es_t *p_es,
                            const dvbpsi_pmt_es_t *p_dvbpsies )
{
    es_format_t *p_fmt = &p_es->fmt;

    /* Blu-Ray mapping */
    switch( p_dvbpsies->i_type )
    {
    case 0x80:
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_BD_LPCM );
        break;
    case 0x81:
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_A52 );
        break;
    case 0x85: /* DTS-HD High resolution audio */
    case 0x86: /* DTS-HD Master audio */
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_DTS );
        p_fmt->i_profile = PROFILE_DTS_HD;
        break;
    case 0x82:
    case 0xA2: /* Secondary DTS audio */
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_DTS );
        break;

    case 0x83: /* TrueHD AC3 */
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_TRUEHD );
        break;

    case 0x84: /* E-AC3 */
    case 0xA1: /* Secondary E-AC3 */
        es_format_Change( p_fmt, AUDIO_ES, VLC_CODEC_EAC3 );
        break;
    case 0x90: /* Presentation graphics */
        es_format_Change( p_fmt, SPU_ES, VLC_CODEC_BD_PG );
        break;
    case 0x91: /* Interactive graphics */
        es_format_Change( p_fmt, UNKNOWN_ES, 0 ); /* prevent regular ES handler */
        break;
    case 0x92: /* Subtitle */
        es_format_Change( p_fmt, SPU_ES, VLC_CODEC_BD_TEXT );
        break;
    case 0xEA:
        es_format_Change( p_fmt, VIDEO_ES, VLC_CODEC_VC1 );
        break;
    default:
        msg_Info( p_demux, "HDMV registration not implemented for pid 0x%x type 0x%x",
                  p_dvbpsies->i_pid, p_dvbpsies->i_type );
        return false;
    }
    return true;
}

static bool PMTSetupEsRegistration( demux_t *p_demux, ts_es_t *p_es,
                                    const dvbpsi_pmt_es_t *p_dvbpsies )
{
    static const struct
    {
        char                      psz_tag[5];
        enum es_format_category_e i_cat;
        vlc_fourcc_t              i_codec;
    } p_regs[] = {
        { "AC-3", AUDIO_ES, VLC_CODEC_A52   },
        { "EAC3", AUDIO_ES, VLC_CODEC_EAC3  },
        { "DTS1", AUDIO_ES, VLC_CODEC_DTS   },
        { "DTS2", AUDIO_ES, VLC_CODEC_DTS   },
        { "DTS3", AUDIO_ES, VLC_CODEC_DTS   },
        { "BSSD", AUDIO_ES, VLC_CODEC_302M  },
        { "VC-1", VIDEO_ES, VLC_CODEC_VC1   },
        { "drac", VIDEO_ES, VLC_CODEC_DIRAC },
        { "", UNKNOWN_ES, 0 }
    };
    es_format_t *p_fmt = &p_es->fmt;

    for( int i = 0; p_regs[i].i_cat != UNKNOWN_ES; i++ )
    {
        if( PMTEsHasRegistration( p_demux, p_dvbpsies, p_regs[i].psz_tag ) )
        {
            es_format_Change( p_fmt, p_regs[i].i_cat, p_regs[i].i_codec );

            /* System A AC3 extension, see ATSC A/52 Annex G.2 */
            if ( p_regs[i].i_codec == VLC_CODEC_A52 && p_dvbpsies->i_type == 0x87 )
                p_fmt->i_codec = VLC_CODEC_EAC3;

            return true;
        }
    }
    return false;
}

static void PIDFillFormat( demux_t *p_demux, ts_stream_t *p_pes,
                           int i_stream_type, ts_transport_type_t *p_datatype )
{
    es_format_t *fmt = &p_pes->p_es->fmt;
    switch( i_stream_type )
    {
    case 0x01:  /* MPEG-1 video */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_MPGV );
        fmt->i_original_fourcc = VLC_CODEC_MP1V;
        break;
    case 0x02:  /* MPEG-2 video */
    case 0x80:  /* MPEG-2 MOTO video */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_MPGV );
        break;
    case 0x03:  /* MPEG-1 audio */
    case 0x04:  /* MPEG-2 audio */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_MPGA );
        break;
    case 0x0f:  /* ISO/IEC 13818-7 Audio with ADTS transport syntax */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_MP4A );
        fmt->i_original_fourcc = VLC_FOURCC('A','D','T','S');
        break;
    case 0x10:  /* MPEG4 (video) */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_MP4V );
        break;
    case 0x11:  /* MPEG4 (audio) LATM */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_MP4A );
        /* https://trac.ffmpeg.org/ticket/320
         * Haivision's Makito encoder violates mapping by putting ADTS
           instead of LOAS, we can only hint that's not raw AAC */
        fmt->i_original_fourcc = VLC_FOURCC('H','E','A','D');
        break;
    case 0x1B:  /* H264 <- check transport syntax/needed descriptor */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_H264 );
        break;
    case 0x1C:  /* ISO/IEC 14496-3 Audio, without using any additional
                   transport syntax, such as DST, ALS and SLS */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_MP4A );
        break;
    case 0x24:  /* HEVC */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_HEVC );
        break;
    case 0x2D:  /* MPEG-H main audio */
    //case 0x2E:  /* MPEG-H auxiliary audio */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_MPEGH );
        break;
    case 0x33:  /* VVC */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_VVC );
        break;
    case 0x42:  /* CAVS (Chinese AVS) */
        es_format_Change( fmt, VIDEO_ES, VLC_CODEC_CAVS );
        break;

    case 0x81:  /* A52 (audio) */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_A52 );
        break;
    case 0x82:  /* SCTE-27 (sub) */
        es_format_Change( fmt, SPU_ES, VLC_CODEC_SCTE_27 );
        *p_datatype = TS_TRANSPORT_SECTIONS;
        ts_sections_processor_Add( p_demux, &p_pes->p_sections_proc, 0xC6, 0x00,
                                   SCTE27_Section_Callback, p_pes );
        break;
    case 0x84:  /* SDDS (audio) */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_SDDS );
        break;
    case 0x85:  /* DTS (audio) FIXME: HDMV Only ? */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_DTS );
        break;
    case 0x87: /* E-AC3, ATSC */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_EAC3 );
        break;
    case 0x8a: /* DTS (audio) */
        es_format_Change( fmt, AUDIO_ES, VLC_CODEC_DTS );
        break;
    case 0x91:  /* A52 vls (audio) */
        es_format_Change( fmt, AUDIO_ES, VLC_FOURCC( 'a', '5', '2', 'b' ) );
        break;
    case 0x92:  /* DVD_SPU vls (sub) */
        es_format_Change( fmt, SPU_ES, VLC_FOURCC( 's', 'p', 'u', 'b' ) );
        break;

    case 0x94:  /* SDDS (audio) */
        es_format_Change( fmt, AUDIO_ES, VLC_FOURCC( 's', 'd', 'd', 'b' ) );
        break;

    case 0xa0:  /* MSCODEC vlc (video) (fixed later) */
        es_format_Change( fmt, UNKNOWN_ES, 0 );
        break;

    case 0x06:  /* PES_PRIVATE  (fixed later) */
    case 0x12:  /* MPEG-4 generic (sub/scene/...) (fixed later) */
    case 0xEA:  /* Privately managed ES (VC-1) (fixed later */
    default:
        es_format_Change( fmt, UNKNOWN_ES, 0 );
        break;
    }
}

static void FillPESFromDvbpsiES( demux_t *p_demux,
                                 const dvbpsi_pmt_t *p_dvbpsipmt,
                                 const dvbpsi_pmt_es_t *p_dvbpsies,
                                 ts_pmt_registration_type_t registration_type,
                                 const ts_pmt_t *p_pmt,
                                 ts_stream_t *p_pes )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    ts_transport_type_t type_change = TS_TRANSPORT_PES;
    PIDFillFormat( p_demux, p_pes, p_dvbpsies->i_type, &type_change );

    p_pes->i_stream_type = p_dvbpsies->i_type;

    bool b_registration_applied = false;
    if ( p_dvbpsies->i_type >= 0x80 ) /* non standard, extensions */
    {
        if ( registration_type == TS_PMT_REGISTRATION_BLURAY )
        {
            if (( b_registration_applied = PMTSetupEsHDMV( p_demux, p_pes->p_es, p_dvbpsies ) ))
                msg_Dbg( p_demux, "    + HDMV registration applied to pid %d type 0x%x",
                         p_dvbpsies->i_pid, p_dvbpsies->i_type );
        }
        else
        {
            if (( b_registration_applied = PMTSetupEsRegistration( p_demux, p_pes->p_es, p_dvbpsies ) ))
                msg_Dbg( p_demux, "    + registration applied to pid %d type 0x%x",
                    p_dvbpsies->i_pid, p_dvbpsies->i_type );
        }
    }

    if ( !b_registration_applied )
    {
        p_pes->transport = type_change; /* Only change type if registration has not changed meaning */

        switch( p_dvbpsies->i_type )
        {
        case 0x02:
            PMTSetupEs0x02( p_pes->p_es, p_dvbpsies );
            break;
        case 0x05: /* Private data in sections */
            p_pes->transport = TS_TRANSPORT_SECTIONS;
            PMTSetupEs0x05PrivateData( p_demux, p_pes->p_es, p_dvbpsies );
            break;
        case 0x06:
            /* Handle PES private data */
            PMTSetupEs0x06( p_demux, p_pes, p_dvbpsies );
            break;
        case 0x0a: /* DSM-CC */
        case 0x0b:
        case 0x0c:
        case 0x0d:
            p_pes->transport = TS_TRANSPORT_IGNORE;
            break;
        /* All other private or reserved types */
        case 0x13: /* SL in sections */
            p_pes->transport = TS_TRANSPORT_SECTIONS;
            /* fallthrough */
        case 0x0f:
        case 0x10:
        case 0x11:
        case 0x12:
            SetupISO14496Descriptors( p_demux, p_pes, p_pmt, p_dvbpsies );
            break;
        case 0x15:
            SetupMetadataDescriptors( p_demux, p_pes, p_dvbpsies );
            break;
        case 0x1b:
            SetupAVCDescriptors( p_demux, p_pes->p_es, p_dvbpsies );
            break;
        case 0x21:
            SetupJ2KDescriptors( p_demux, p_pes->p_es, p_dvbpsies );
            break;
        case 0x83:
            /* LPCM (audio) */
            PMTSetupEs0x83( p_dvbpsipmt, p_pes->p_es, p_dvbpsies->i_pid );
            break;
        case 0xa0:
            PMTSetupEs0xA0( p_demux, p_pes->p_es, p_dvbpsies );
            break;
        case 0xd1:
            PMTSetupEs0xD1( p_demux, p_pes->p_es, p_dvbpsies );
            break;
        case 0xEA:
            PMTSetupEs0xEA( p_demux, p_pes->p_es, p_dvbpsies );
        default:
            break;
        }
    }

    if( p_pes->p_es->fmt.i_cat == AUDIO_ES )
    {
        SetupAudioExtendedDescriptors( p_demux, p_pes->p_es, p_dvbpsies );
    }

    SetupStandardESDescriptors(  p_demux, p_pes->p_es, p_dvbpsies );

    if ( registration_type == TS_PMT_REGISTRATION_BLURAY )
    {
        /*
         * 0x1011 primary video
         * 0x1100- 0x111f primary audio
         * 0x1a00- 0x1a1f secondary audio
         * 0x1b00- 0x1b1f secondary video */

        /* Disable dolbyvision */
        if ( p_dvbpsies->i_pid == 0x1015 &&
            PMTEsHasRegistration( p_demux, p_dvbpsies, "HDMV" ) )
        {
            p_pes->p_es->fmt.i_priority = ES_PRIORITY_NOT_DEFAULTABLE;
        }
        else if( (p_dvbpsies->i_pid >= 0x1a00 && p_dvbpsies->i_pid <= 0x1a1f) ||
                 (p_dvbpsies->i_pid >= 0x1b00 && p_dvbpsies->i_pid <= 0x1b1f) )
        {
            /* We might just want to prio, but it will trigger multiple videos es */
            p_pes->p_es->fmt.i_priority = ES_PRIORITY_NOT_DEFAULTABLE;
        }
        else
        {
            p_pes->p_es->fmt.i_priority = 0xFFFF - (p_dvbpsies->i_pid & 0xFFFF) +
                                                    ES_PRIORITY_SELECTABLE_MIN;
        }
    }

    /* PES packets usually contain truncated frames */
    p_pes->p_es->fmt.b_packetized = false;

    /* Set Groups / ID */
    p_pes->p_es->fmt.i_group = p_dvbpsipmt->i_program_number;
    if( p_sys->b_es_id_pid )
        p_pes->p_es->fmt.i_id = p_dvbpsies->i_pid;
}

static en50221_capmt_info_t * CreateCAPMTInfo( const dvbpsi_pmt_t *p_pmt )
{
    en50221_capmt_info_t *p_en = en50221_capmt_New( p_pmt->i_version,
                                                    p_pmt->i_program_number );
    if( unlikely(p_en == NULL) )
        return p_en;

    for( const dvbpsi_descriptor_t *p_dr = p_pmt->p_first_descriptor;
                                           p_dr; p_dr = p_dr->p_next )
    {
        if( p_dr->i_tag == 0x09 )
            en50221_capmt_AddCADescriptor( p_en, p_dr->p_data, p_dr->i_length );
    }

    for( const dvbpsi_pmt_es_t *p_es = p_pmt->p_first_es;
                                       p_es; p_es = p_es->p_next )
    {
        en50221_capmt_es_info_t *p_enes = en50221_capmt_EsAdd( p_en,
                                                               p_es->i_type,
                                                               p_es->i_pid );
        if( likely(p_enes) )
        {
            for( const dvbpsi_descriptor_t *p_dr = p_es->p_first_descriptor;
                                                   p_dr; p_dr = p_dr->p_next )
            {
                if( p_dr->i_tag == 0x09 )
                    en50221_capmt_AddESCADescriptor( p_enes, p_dr->p_data, p_dr->i_length );
            }
        }
    }

    return p_en;
}

void SendCAPMTUpdate( demux_t *p_demux, const ts_pmt_t *p_pmt )
{
    demux_sys_t  *p_sys = p_demux->p_sys;
    if( !p_pmt->p_ctx->p_capmt )
        return;
    (void) vlc_stream_Control( p_sys->stream, STREAM_SET_PRIVATE_ID_CA,
                               (void *)p_pmt->p_ctx->p_capmt );
}

static void PMTCallBack( void *data, dvbpsi_pmt_t *p_dvbpsipmt )
{
    demux_t      *p_demux = data;
    demux_sys_t  *p_sys = p_demux->p_sys;

    ts_pid_t     *pmtpid = NULL;
    ts_pmt_t     *p_pmt = NULL;
    bool          b_encryption = false;

    msg_Dbg( p_demux, "PMTCallBack called for program %d", p_dvbpsipmt->i_program_number );

    if (unlikely(GetPID(p_sys, 0)->type != TYPE_PAT))
    {
        assert(GetPID(p_sys, 0)->type == TYPE_PAT);
        dvbpsi_pmt_delete(p_dvbpsipmt);
    }

    const ts_pat_t *p_pat = GetPID(p_sys, 0)->u.p_pat;

    /* First find this PMT declared in PAT */
    for( int i = 0; !pmtpid && i < p_pat->programs.i_size; i++ )
    {
        const int i_pmt_prgnumber = p_pat->programs.p_elems[i]->u.p_pmt->i_number;
        if( i_pmt_prgnumber != TS_USER_PMT_NUMBER &&
            i_pmt_prgnumber == p_dvbpsipmt->i_program_number )
        {
            pmtpid = p_pat->programs.p_elems[i];
            assert(pmtpid->type == TYPE_PMT);
            p_pmt = pmtpid->u.p_pmt;
        }
    }

    if( pmtpid == NULL )
    {
        msg_Warn( p_demux, "unreferenced program (broken stream)" );
        dvbpsi_pmt_delete(p_dvbpsipmt);
        return;
    }

    pmtpid->i_flags |= FLAG_SEEN;

    if( p_pmt->i_version != -1 &&
        ( !p_dvbpsipmt->b_current_next || p_pmt->i_version == p_dvbpsipmt->i_version ) )
    {
        dvbpsi_pmt_delete( p_dvbpsipmt );
        return;
    }

    /* Save old es array */
    DECL_ARRAY(ts_pid_t *) pid_to_decref;
    pid_to_decref.i_alloc = p_pmt->e_streams.i_alloc;
    pid_to_decref.i_size = p_pmt->e_streams.i_size;
    pid_to_decref.p_elems = p_pmt->e_streams.p_elems;
    if( p_pmt->p_atsc_si_basepid )
        ARRAY_APPEND( pid_to_decref, p_pmt->p_atsc_si_basepid );
    if( p_pmt->p_si_sdt_pid )
        ARRAY_APPEND( pid_to_decref, p_pmt->p_si_sdt_pid );
    ARRAY_INIT(p_pmt->e_streams);

    if( p_pmt->iod )
    {
        ODFree( p_pmt->iod );
        p_pmt->iod = NULL;
    }

    msg_Dbg( p_demux, "new PMT program number=%d version=%d pid_pcr=%d",
             p_dvbpsipmt->i_program_number, p_dvbpsipmt->i_version, p_dvbpsipmt->i_pcr_pid );
    p_pmt->i_pid_pcr = p_dvbpsipmt->i_pcr_pid;
    p_pmt->i_version = p_dvbpsipmt->i_version;

    if( ProgramIsSelected( p_sys, p_pmt->i_number ) )
        SetPIDFilter( p_sys, GetPID(p_sys, p_pmt->i_pid_pcr), true ); /* Set demux filter */

    /* Parse PMT descriptors */
    ts_pmt_registration_type_t registration_type = TS_PMT_REGISTRATION_NONE;
    ParsePMTRegistrations( p_demux, p_dvbpsipmt->p_first_descriptor, p_pmt, &registration_type );

    if( p_sys->standard == TS_STANDARD_AUTO )
    {
        switch( registration_type )
        {
            case TS_PMT_REGISTRATION_BLURAY:
                TsChangeStandard( p_sys, TS_STANDARD_MPEG );
                break;
            case TS_PMT_REGISTRATION_ARIB:
                TsChangeStandard( p_sys, TS_STANDARD_ARIB );
                break;
            case TS_PMT_REGISTRATION_ATSC:
                TsChangeStandard( p_sys, TS_STANDARD_ATSC );
                break;
            default:
                if(SEEN(GetPID(p_sys, ATSC_BASE_PID)))
                {
                    TsChangeStandard( p_sys, TS_STANDARD_ATSC );
                }
                else
                {
                    /* Probe using ES */
                    p_sys->standard = ProbePMTStandard( p_dvbpsipmt );
                }
                break;
        }
    }

    /* Private descriptors depends on standard */
    ParsePMTPrivateRegistrations( p_demux, p_dvbpsipmt->p_first_descriptor, p_pmt, p_sys->standard );

    dvbpsi_pmt_es_t *p_dvbpsies;
    for( p_dvbpsies = p_dvbpsipmt->p_first_es; p_dvbpsies != NULL; p_dvbpsies = p_dvbpsies->p_next )
    {
        ts_pid_t *pespid = GetPID(p_sys, p_dvbpsies->i_pid);
        if ( pespid->type != TYPE_STREAM && pespid->type != TYPE_FREE )
        {
            msg_Warn( p_demux, " * PMT wants to create PES on pid %d used by non PES", pespid->i_pid );
            continue;
        }

        char const * psz_typedesc = ISO13818_1_Get_StreamType_Description( p_dvbpsies->i_type );

        msg_Dbg( p_demux, "  * pid=%d type=0x%x %s",
                 p_dvbpsies->i_pid, p_dvbpsies->i_type, psz_typedesc );

        /* PMT element/component descriptors */
        for( dvbpsi_descriptor_t *p_dr = p_dvbpsies->p_first_descriptor;
             p_dr != NULL; p_dr = p_dr->p_next )
        {
            const char *psz_desc = NULL;
            if( registration_type == TS_PMT_REGISTRATION_ARIB )
                psz_desc = ARIB_B10_Get_PMT_Descriptor_Description( p_dr->i_tag );

            if( psz_desc )
                msg_Dbg( p_demux, "    - ES descriptor %s 0x%x", psz_desc, p_dr->i_tag );
            else
                msg_Dbg( p_demux, "    - ES descriptor tag 0x%x", p_dr->i_tag );

            b_encryption |= (p_dr->i_tag == 0x09);
        }

        const bool b_pid_inuse = ( pespid->type == TYPE_STREAM );
        ts_stream_t *p_pes;

        if ( !PIDSetup( p_demux, TYPE_STREAM, pespid, pmtpid ) )
        {
            msg_Warn( p_demux, "  * pid=%d type=0x%x %s (skipped)",
                      p_dvbpsies->i_pid, p_dvbpsies->i_type, psz_typedesc );
            continue;
        }
        else
        {
            if( b_pid_inuse ) /* pes will point to a temp */
            {
                p_pes = ts_stream_New( p_demux, p_pmt );
                if( !p_pes )
                {
                    PIDRelease( p_demux, pespid );
                    continue;
                }
            }
            else  /* pes will point to the new one allocated from PIDSetup */
            {
                p_pes = pespid->u.p_stream;
            }
        }

        /* Add pid to the list of used ones in pmt */
        ARRAY_APPEND( p_pmt->e_streams, pespid );
        pespid->i_flags |= SEEN(GetPID(p_sys, p_dvbpsies->i_pid));

        /* Fill p_pes es and add extra es if any */
        FillPESFromDvbpsiES( p_demux, p_dvbpsipmt, p_dvbpsies,
                             registration_type, p_pmt, p_pes );

        /* Set description and debug */
        if( p_pes->p_es->fmt.i_cat == UNKNOWN_ES )
        {
            msg_Dbg( p_demux, "   => pid %d content is *unknown*",
                     p_dvbpsies->i_pid );
            if( !p_pes->p_es->fmt.psz_description )
                p_pes->p_es->fmt.psz_description = strdup( psz_typedesc );
        }
        else
        {
            msg_Dbg( p_demux, "   => pid %d has now es fcc=%4.4s",
                     p_dvbpsies->i_pid, (char*)&p_pes->p_es->fmt.i_codec );
        }

        dvbpsi_descriptor_t *p_dr = PMTEsFindDescriptor( p_dvbpsies, 0x09 );
        if( p_dr && p_dr->i_length >= 2 )
        {
            msg_Dbg( p_demux, "    - ES descriptor : CA (0x9) SysID 0x%x",
                     (p_dr->p_data[0] << 8) | p_dr->p_data[1] );
            b_encryption = true;
        }

        const bool b_create_es = (p_pes->p_es->fmt.i_cat != UNKNOWN_ES);

        /* Now check and merge */
        if( b_pid_inuse ) /* We need to compare to the existing pes/es */
        {
            ts_es_t *p_existing_es = ts_stream_Find_es( pespid->u.p_stream, p_pmt );
            if( p_existing_es )
            {
                const es_format_t *ofmt = &p_existing_es->fmt;
                const es_format_t *nfmt = &p_pes->p_es->fmt;

                /* Check if we can avoid restarting that ES */
                bool b_canreuse = es_format_IsSimilar( ofmt, nfmt );

                /* Check codecs extra */
                b_canreuse = b_canreuse &&
                (
                    ofmt->i_extra == nfmt->i_extra &&
                    ( ofmt->i_extra == 0 ||
                      memcmp( ofmt->p_extra, nfmt->p_extra, nfmt->i_extra ) == 0 )
                );

                /* Tracks must have same language */
                b_canreuse = b_canreuse &&
                (
                    ( !!ofmt->psz_language == !!nfmt->psz_language ) &&
                    ( ofmt->psz_language == NULL ||
                      !strcmp( ofmt->psz_language, nfmt->psz_language ) )
                );

                /* Check is we have any subtitles */
                b_canreuse = b_canreuse &&
                ( ts_Count_es( p_pes->p_es->p_extraes, false, NULL ) ==
                  ts_Count_es( p_existing_es->p_extraes, false, NULL )
                );

                if( b_canreuse )
                {
                    /* Just keep using previous es */
                    ts_stream_Del( p_demux, p_pes );
                }
                else
                {
                    ts_es_t *p_new = ts_stream_Extract_es( p_pes, p_pmt );
                    ts_es_t *p_old = ts_stream_Extract_es( pespid->u.p_stream, p_pmt );
                    ts_stream_Add_es( pespid->u.p_stream, p_new, false );
                    assert(p_old == p_existing_es);
                    assert(ts_Count_es(p_pes->p_es, false, NULL) == 0);
                    ts_stream_Add_es( p_pes, p_old, false );
                    ts_stream_Del( p_demux, p_pes );
                }
            }
            else /* There was no es for that program on that pid, merge in */
            {
                assert(ts_Count_es(pespid->u.p_stream->p_es, false, NULL)); /* Used by another program */
                ts_es_t *p_new = ts_stream_Extract_es( p_pes, p_pmt );
                assert( p_new );
                ts_stream_Add_es( pespid->u.p_stream, p_new, false );
                ts_stream_Del( p_demux, p_pes );
            }
        }
        /* Nothing to do, pes is now just set */
        if( b_create_es )
            AddAndCreateES( p_demux, pespid, false );
    }



    /* Install CAM descrambling */
    if ( p_sys->standard == TS_STANDARD_ARIB && p_sys->stream == p_demux->s && b_encryption )
    {
        stream_t *wrapper = ts_stream_wrapper_New( p_demux->s );
        if( wrapper )
        {
            p_sys->stream = vlc_stream_FilterNew( wrapper, "aribcam" );
            if( !p_sys->stream )
            {
                vlc_stream_Delete( wrapper );
                p_sys->stream = p_demux->s;
            }
        }
    }

     /* Add arbitrary PID from here */
    if ( p_sys->standard == TS_STANDARD_ATSC )
    {
        ts_pid_t *atsc_base_pid = GetPID(p_sys, ATSC_BASE_PID);
        if ( PIDSetup( p_demux, TYPE_PSIP, atsc_base_pid, pmtpid ) )
        {
            ts_psip_t *p_psip = atsc_base_pid->u.p_psip;
            if( !ATSC_Attach_Dvbpsi_Base_Decoders( p_psip->p_ctx, atsc_base_pid ) )
            {
                msg_Err( p_demux, "dvbpsi_atsc_AttachMGT/STT failed for program %d",
                         p_pmt->i_number );
                PIDRelease( p_demux, atsc_base_pid );
            }
            else
            {
                p_pmt->p_atsc_si_basepid = atsc_base_pid;
                SetPIDFilter( p_demux->p_sys, atsc_base_pid, true );
                msg_Dbg( p_demux, "  * pid=%d listening for MGT/STT", atsc_base_pid->i_pid );

                /* Set up EAS spu es */
                if( p_pmt->e_streams.i_size )
                {
                    ts_es_t *p_eas_es = ts_es_New( p_pmt );
                    if( likely(p_eas_es) )
                    {
                        es_format_Change( &p_eas_es->fmt, SPU_ES, VLC_CODEC_SCTE_18 );
                        p_eas_es->fmt.i_id = ATSC_BASE_PID;
                        p_eas_es->fmt.i_group = p_pmt->i_number;
                        p_eas_es->fmt.psz_description = strdup(SCTE18_DESCRIPTION);
                        if( p_psip->p_eas_es )
                        {
                            ts_es_t *p_next = p_psip->p_eas_es->p_next;
                            p_psip->p_eas_es->p_next = p_eas_es;
                            p_eas_es->p_next = p_next;
                        }
                        else
                        {
                            p_psip->p_eas_es = p_eas_es;
                        }
                        msg_Dbg( p_demux, "  * pid=%d listening for EAS events", ATSC_BASE_PID );
                    }
                }
            }
        }
        else if( atsc_base_pid->type != TYPE_FREE )
        {
            msg_Err( p_demux, "can't attach PSIP table handlers"
                              "on already in use ATSC base pid %d", ATSC_BASE_PID );
        }
    }
    else if( p_sys->standard != TS_STANDARD_MPEG && p_sys->standard != TS_STANDARD_TDMB )
    {
        ts_pid_t *p_sdt_pid = ts_pid_Get( &p_sys->pids, TS_SI_SDT_PID );
        if ( PIDSetup( p_demux, TYPE_SI, p_sdt_pid, pmtpid ) ) /* Create or incref SDT */
        {
            if( !ts_attach_SI_Tables_Decoders( p_sdt_pid ) )
            {
                msg_Err( p_demux, "Can't attach SI table decoders from program %d",
                         p_pmt->i_number );
                PIDRelease( p_demux, p_sdt_pid );
            }
            else
            {
                p_pmt->p_si_sdt_pid = p_sdt_pid;
                SetPIDFilter( p_demux->p_sys, p_sdt_pid, true );
                msg_Dbg( p_demux, "  * pid=%d listening for SDT", p_sdt_pid->i_pid );
            }
        }
        else if( p_sdt_pid->type != TYPE_FREE )
        {
            msg_Err( p_demux, "can't attach SI SDT table handler"
                              "on already in used pid %d (Not DVB ?)", p_sdt_pid->i_pid );
        }
    }

    /* Decref or clean now unused es */
    for( int i = 0; i < pid_to_decref.i_size; i++ )
        PIDRelease( p_demux, pid_to_decref.p_elems[i] );
    ARRAY_RESET( pid_to_decref );

    if( !p_sys->b_trust_pcr )
    {
        int i_cand = FindPCRCandidate( p_pmt );
        p_pmt->i_pid_pcr = i_cand;
        p_pmt->pcr.b_disable = true;
        msg_Warn( p_demux, "PCR not trusted for program %d, set up workaround using pid %d",
                  p_pmt->i_number, i_cand );
    }

    /* Create CAPMT for this ES */
    if( p_pmt->p_ctx->p_capmt )
        en50221_capmt_Delete( p_pmt->p_ctx->p_capmt );
    p_pmt->p_ctx->p_capmt = CreateCAPMTInfo( p_dvbpsipmt );

    UpdatePESFilters( p_demux, p_sys->seltype == PROGRAM_ALL );

    /* Probe Boundaries */
    if( p_sys->b_canfastseek && !p_pmt->b_last_dts_probed )
    {
        ProbeStart( p_demux, p_pmt->i_number );
        ProbeEnd( p_demux, p_pmt->i_number );
        p_pmt->b_last_dts_probed = true;
        if( p_pmt->i_last_dts != VLC_TICK_INVALID &&
            p_pmt->i_last_dts < p_pmt->pcr.i_first_dts )
            p_pmt->i_last_dts = TimeStampWrapAround( p_pmt->pcr.i_first_dts, p_pmt->i_last_dts );
    }

    dvbpsi_pmt_delete( p_dvbpsipmt );
}

int UserPmt( demux_t *p_demux, const char *psz_fmt )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    char *psz_dup = strdup( psz_fmt );
    char *psz = psz_dup;
    int  i_number;

    if( !psz_dup )
        return VLC_ENOMEM;

    /* Parse PID */
    unsigned long i_pid = strtoul( psz, &psz, 0 );
    if( i_pid < 2 || i_pid >= 8192 )
        goto error;

    /* Parse optional program number */
    i_number = 0;
    if( *psz == ':' )
        i_number = strtol( &psz[1], &psz, 0 );

    /* */
    ts_pid_t *pmtpid = GetPID(p_sys, i_pid);

    msg_Dbg( p_demux, "user pmt specified (pid=%lu,number=%d)", i_pid, i_number );
    if ( !PIDSetup( p_demux, TYPE_PMT, pmtpid, GetPID(p_sys, 0) ) )
        goto error;

    /* Dummy PMT */
    ts_pmt_t *p_pmt = pmtpid->u.p_pmt;
    p_pmt->i_number   = i_number != 0 ? i_number : TS_USER_PMT_NUMBER;
    if( !dvbpsi_pmt_attach( p_pmt->p_ctx->p_handle,
                            ((i_number != TS_USER_PMT_NUMBER ? i_number : 1)),
                            PMTCallBack, p_demux ) )
    {
        PIDRelease( p_demux, pmtpid );
        goto error;
    }

    ARRAY_APPEND( GetPID(p_sys, 0)->u.p_pat->programs, pmtpid );
    ARRAY_APPEND( p_sys->programs, pmtpid->i_pid );
    p_sys->b_default_selection = true;

    psz = strchr( psz, '=' );
    if( psz )
        psz++;
    while( psz && *psz )
    {
        char *psz_next = strchr( psz, ',' );

        if( psz_next )
            *psz_next++ = '\0';

        i_pid = strtoul( psz, &psz, 0 );
        if( *psz != ':' || i_pid < 2 || i_pid >= 8192 )
            goto next;

        char *psz_opt = &psz[1];
        if( !strcmp( psz_opt, "pcr" ) )
        {
            p_pmt->i_pid_pcr = i_pid;
        }
        else if( GetPID(p_sys, i_pid)->type == TYPE_FREE )
        {
            ts_pid_t *pid = GetPID(p_sys, i_pid);

            char *psz_arg = strchr( psz_opt, '=' );
            if( psz_arg )
                *psz_arg++ = '\0';

            if ( !PIDSetup( p_demux, TYPE_STREAM, pid, pmtpid ) )
                continue;

            ARRAY_APPEND( p_pmt->e_streams, pid );

            if( p_pmt->i_pid_pcr <= 0 )
                p_pmt->i_pid_pcr = i_pid;

            es_format_t *fmt = &pid->u.p_stream->p_es->fmt;

            if( psz_arg && strlen( psz_arg ) == 4 )
            {
                const vlc_fourcc_t i_codec = VLC_FOURCC( psz_arg[0], psz_arg[1],
                                                         psz_arg[2], psz_arg[3] );
                int i_cat = UNKNOWN_ES;

                if( !strcmp( psz_opt, "video" ) )
                    i_cat = VIDEO_ES;
                else if( !strcmp( psz_opt, "audio" ) )
                    i_cat = AUDIO_ES;
                else if( !strcmp( psz_opt, "spu" ) )
                    i_cat = SPU_ES;

                es_format_Change( fmt, i_cat, i_codec );
                fmt->b_packetized = false;
            }
            else
            {
                const int i_stream_type = strtol( psz_opt, NULL, 0 );
                PIDFillFormat( p_demux, pid->u.p_stream, i_stream_type, &pid->u.p_stream->transport );
            }

            fmt->i_group = i_number;
            if( p_sys->b_es_id_pid )
                fmt->i_id = i_pid;

            if( fmt->i_cat != UNKNOWN_ES )
            {
                msg_Dbg( p_demux, "  * es pid=%lu fcc=%4.4s", i_pid,
                         (char*)&fmt->i_codec );
                pid->u.p_stream->p_es->id = es_out_Add( p_demux->out, fmt );
                p_sys->i_pmt_es++;
            }
        }

    next:
        psz = psz_next;
    }

    p_sys->b_user_pmt = true;
    free( psz_dup );
    return VLC_SUCCESS;

error:
    free( psz_dup );
    return VLC_EGENERIC;
}

bool ts_psi_PAT_Attach( ts_pid_t *patpid, void *cbdata )
{
    if( unlikely(patpid->type != TYPE_PAT || patpid->i_pid != TS_PSI_PAT_PID) )
        return false;
    patpid->u.p_pat->p_ctx->pf_detach = dvbpsi_pat_detach;
    return dvbpsi_pat_attach( patpid->u.p_pat->p_ctx->p_handle, PATCallBack, cbdata );
}

void ts_psi_Packet_Push( ts_pid_t *p_pid, const uint8_t *p_pktbuffer )
{
    if( p_pid->type == TYPE_PAT )
        dvbpsi_packet_push( p_pid->u.p_pat->p_ctx->p_handle, (uint8_t *) p_pktbuffer );
    else if( p_pid->type == TYPE_PMT )
        dvbpsi_packet_push( p_pid->u.p_pmt->p_ctx->p_handle, (uint8_t *) p_pktbuffer );
}

void ts_psi_context_Delete( ts_psi_context_t *p_ctx )
{
    if( dvbpsi_decoder_present( p_ctx->p_handle ) )
    {
        assert( p_ctx->pf_detach );
        p_ctx->pf_detach( p_ctx->p_handle );
    }
    if( p_ctx->p_capmt )
        en50221_capmt_Delete( p_ctx->p_capmt );
    dvbpsi_delete( p_ctx->p_handle );
    free( p_ctx );
}

ts_psi_context_t * ts_psi_context_New( demux_t *p_demux )
{
    ts_psi_context_t *p_ctx = malloc(sizeof(*p_ctx));
    if(likely(p_ctx))
    {
        p_ctx->p_handle = dvbpsi_new( &dvbpsi_messages, DVBPSI_MSG_DEBUG );
        if( !p_ctx->p_handle )
        {
            free( p_ctx );
            return NULL;
        }
        p_ctx->p_handle->p_sys = (void *) p_demux;
        p_ctx->pf_detach = NULL;
        p_ctx->p_capmt = NULL;
    }
    return p_ctx;
}
