Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  ts_psi.c   Sprache: C

 

/*****************************************************************************
 * 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, ®istration_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;
}

Messung
CHZG
86958789

¤ Dauer der Verarbeitung: 0.55 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417717