Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  h266.c   Sprache: C

 

/*****************************************************************************
 * h266.c: H.266/VVC video packetizer
 *****************************************************************************
 * Copyright © 2019-2026 VideoLabs, VideoLAN and VLC Authors
 *
 * 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 Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/


/*****************************************************************************
 * Preamble
 *****************************************************************************/


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_block.h>
#include <vlc_bits.h>

#include <vlc_block_helper.h>
#include "packetizer_helper.h"
#include "startcode_helper.h"
#include "h266_nal.h"
#include "hxxx_nal.h"
#include "hxxx_sei.h"
#include "hxxx_common.h"

#include <limits.h>

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/

static int  Open (vlc_object_t *);
static void Close(vlc_object_t *);

vlc_module_begin ()
    set_subcategory(SUBCAT_SOUT_PACKETIZER)
    set_description(N_("H266/H.266 video packetizer"))
    set_capability("video packetizer", 50)
    set_callbacks(Open, Close)
vlc_module_end ()


/****************************************************************************
 * Local prototypes
 ****************************************************************************/

struct h266_tuple_s
{
    block_t *p_nal;
    void *p_decoded;
};

struct h266_queue_s
{
    block_t *p_chain;
    block_t **pp_chain_last;
};

typedef struct
{
    /* */
    packetizer_t packetizer;

    struct h266_queue_s pre;
    struct h266_queue_s pus; // completed picture units (PU)
    struct // current PU
    {
        struct h266_queue_s pre;
        struct h266_queue_s vcl;
        struct h266_queue_s post;
    } current;
    struct h266_queue_s post;

    struct h266_tuple_s rg_vps[H266_MAX_NUM_VPS],
                        rg_sps[H266_MAX_NUM_SPS],
                        rg_pps[H266_MAX_NUM_PPS];

    const h266_video_parameter_set_t    *p_active_vps;
    const h266_sequence_parameter_set_t *p_active_sps;
    const h266_picture_parameter_set_t  *p_active_pps;
    enum
    {
        MISSING = 0,
        COMPLETE,
        SENT,
    } sets;
    /* Recovery starts from IFRAME or SEI recovery point */
    bool b_recovery_point;

    /* To evaluate the start of a new AU */
    struct
    {
        bool b_valid;
        uint8_t i_nuh_layer_id;
        int poc;
    } previous;

    h266_poc_ctx_t pocctx[H266_MAX_NUH_LAYER_ID + 1];

    uint8_t i_nal_length_size;

    date_t dts;
    vlc_tick_t pts;
    bool b_need_ts;

    /* */
    cc_storage_t *p_ccs;

} decoder_sys_t;

static block_t *PacketizeAnnexB(decoder_t *, block_t **);
static block_t *PacketizeVVC1(decoder_t *, block_t **);
static void PacketizeFlush(decoder_t *);
static void PacketizeReset(void *p_private, bool b_flush);
static block_t *PacketizeParse(void *p_private, bool *pb_ts_used, block_t *);
static block_t *ParseNALBlock(decoder_t *, bool *pb_ts_used, block_t *);
static inline block_t *ParseNALBlockW(void *opaque, bool *pb_ts_used, block_t *p_frag)
{
    return ParseNALBlock((decoder_t *) opaque, pb_ts_used, p_frag);
}
static int PacketizeValidate(void *p_private, block_t *);
static block_t * PacketizeDrain(void *);
static block_t *GetCc(decoder_t *p_dec, decoder_cc_desc_t *p_desc);
static block_t *GetXPSCopy(decoder_sys_t *p_sys);
static bool ParseSEICallback(const hxxx_sei_data_t *p_sei_data, void *cbdata);

#define BLOCK_FLAG_DROP (1 << BLOCK_FLAG_PRIVATE_SHIFT)

/****************************************************************************
 * Helpers
 ****************************************************************************/

static void InitQueue(block_t **pp_head, block_t ***ppp_tail)
{
    *pp_head = NULL;
    *ppp_tail = pp_head;
}

static void ResetQueue(block_t **pp_head, block_t ***ppp_tail)
{
    if(*pp_head)
        block_ChainRelease(*pp_head);
    InitQueue(pp_head, ppp_tail);
}

#define INITQ(name) InitQueue(&p_sys->name.p_chain, &p_sys->name.pp_chain_last)
#define RESETQ(name) ResetQueue(&p_sys->name.p_chain, &p_sys->name.pp_chain_last)

static block_t * CurrentQueueToPU(decoder_sys_t *p_sys, bool b_all)
{
    block_t *p_output = NULL;
    block_t **pp_output_last = &p_output;
    if(p_sys->current.vcl.p_chain)
    {
        if(p_sys->current.pre.p_chain)
            block_ChainLastAppend(&pp_output_last, p_sys->current.pre.p_chain);
        block_ChainLastAppend(&pp_output_last, p_sys->current.vcl.p_chain);
        if(p_sys->current.post.p_chain)
            block_ChainLastAppend(&pp_output_last, p_sys->current.post.p_chain);
        INITQ(current.pre);
        INITQ(current.vcl);
        INITQ(current.post);
    }
    else
    {
        if(b_all)
            RESETQ(current.pre);
        RESETQ(current.post);
    }
    return p_output;
}

static block_t * OutputQueues(decoder_sys_t *p_sys, bool b_valid)
{
    block_t *p_output = NULL;
    block_t *p_xps = NULL;
    block_t **pp_output_last = &p_output;

    block_t *p_pu = CurrentQueueToPU(p_sys, false);
    if(p_pu)
        block_ChainLastAppend(&p_sys->pus.pp_chain_last, p_pu);

    if(p_sys->b_recovery_point && p_sys->sets != SENT)
        p_xps = GetXPSCopy(p_sys);

    if(p_sys->pre.p_chain)
    {
        while(p_sys->pre.p_chain)
        {
            block_t *p_head = p_sys->pre.p_chain;
            const uint8_t *p_nal = p_head->p_buffer;
            size_t i_nal = p_head->i_buffer;
            if(!hxxx_strip_AnnexB_startcode(&p_nal, &i_nal) || i_nal<2)
                break;
            uint8_t i_type = h266_getNALType(p_nal);
            if(i_type != H266_NAL_AUD && i_type != H266_NAL_OPI)
                break;
            // ensure AUD,OPI are before xPS
            p_sys->pre.p_chain = p_sys->pre.p_chain->p_next;
            p_head->p_next = NULL;
            block_ChainLastAppend(&pp_output_last, p_head);
        }

        if(p_xps)
            block_ChainLastAppend(&pp_output_last, p_xps);

        if(p_sys->pre.p_chain)
            block_ChainLastAppend(&pp_output_last, p_sys->pre.p_chain);
        INITQ(pre);
    }
    else if(p_xps)
    {
        block_ChainLastAppend(&pp_output_last, p_xps);
    }

    if(p_sys->pus.p_chain)
    {
        block_ChainLastAppend(&pp_output_last, p_sys->pus.p_chain);
        p_output->i_dts = date_Get(&p_sys->dts);
        p_output->i_pts = p_sys->pts;
        INITQ(pus);
    }

    if(p_sys->post.p_chain)
    {
        block_ChainLastAppend(&pp_output_last, p_sys->post.p_chain);
        INITQ(post);
    }

    if(p_output)
    {
         /* Because block_ChainGather does not merge flags or times */
        for(block_t *p_head = p_output->p_next; p_head; p_head = p_head->p_next)
            p_output->i_flags |= p_head->i_flags;

        if(!b_valid)
            p_output->i_flags |= BLOCK_FLAG_DROP;
    }

    return p_output;
}


/*****************************************************************************
 * Open
 *****************************************************************************/

static int Open(vlc_object_t *p_this)
{
    decoder_t     *p_dec = (decoder_t*)p_this;
    decoder_sys_t *p_sys;

    if (p_dec->fmt_in->i_codec != VLC_CODEC_VVC)
        return VLC_EGENERIC;

    p_dec->p_sys = p_sys = calloc(1, sizeof(decoder_sys_t));
    if (!p_dec->p_sys)
        return VLC_ENOMEM;

    p_sys->p_ccs = cc_storage_new();
    if(unlikely(!p_sys->p_ccs))
    {
        free(p_dec->p_sys);
        return VLC_ENOMEM;
    }

    INITQ(pre);
    INITQ(pus);
    INITQ(current.pre);
    INITQ(current.vcl);
    INITQ(current.post);
    INITQ(post);

    packetizer_Init(&p_sys->packetizer,
                    annexb_startcode3, 3, startcode_FindAnnexB,
                    annexb_startcode3, 1, 5,
                    PacketizeReset, PacketizeParse, PacketizeValidate, PacketizeDrain,
                    p_dec);

    /* Copy properties */
    es_format_Copy(&p_dec->fmt_out, p_dec->fmt_in);
    p_dec->fmt_out.b_packetized = true;

    /* Init timings */
    if(p_dec->fmt_in->video.i_frame_rate_base &&
        p_dec->fmt_in->video.i_frame_rate &&
        p_dec->fmt_in->video.i_frame_rate <= UINT_MAX / 2)
        date_Init(&p_sys->dts, p_dec->fmt_in->video.i_frame_rate * 2,
                                p_dec->fmt_in->video.i_frame_rate_base);
    else
        date_Init(&p_sys->dts, 2 * 30000, 1001);

    for(int i=0; i<=H266_MAX_NUH_LAYER_ID; i++)
        h266_poc_ctx_init(&p_sys->pocctx[i]);

    p_sys->pts = VLC_TICK_INVALID;
    p_sys->b_need_ts = true;
    p_sys->sets = MISSING;

    /* Set callbacks */
    const uint8_t *p_extra = p_dec->fmt_in->p_extra;
    const size_t i_extra = p_dec->fmt_in->i_extra;
    /* Check if we have hvcC as extradata */
    if(h266_isvvcC(p_extra, i_extra))
    {
        struct h266_dcr_values values = {0};
        struct h266_dcr_params params = {0};
        params.p_values = &values;
        if(!h266_parse_DecoderConfigurationRecord(p_extra, i_extra, ¶ms))
        {
            msg_Err( p_dec, "Invalid DCR extradata");
            Close( VLC_OBJECT(p_dec) );
            return VLC_EGENERIC;
        }

        /* Clear vvcC/VVC1 extra, to be replaced with AnnexB */
        free(p_dec->fmt_out.p_extra);
        p_dec->fmt_out.i_extra = 0;

        size_t i_new_extra = 0;
        p_dec->fmt_out.p_extra = h266_create_AnnexbExtradataFromParams(¶ms, &i_new_extra);
        if(p_dec->fmt_out.p_extra)
            p_dec->fmt_out.i_extra = i_new_extra;

        p_sys->i_nal_length_size = values.nal_length_size;

        p_dec->pf_packetize = PacketizeVVC1;
    }
    else
    {
        p_dec->pf_packetize = PacketizeAnnexB;
    }
    p_dec->pf_flush = PacketizeFlush;
    p_dec->pf_get_cc = GetCc;

    if(p_dec->fmt_out.i_extra)
    {
        /* Feed with AnnexB VPS/SPS/PPS/SEI extradata */
        packetizer_Header(&p_sys->packetizer,
                          p_dec->fmt_out.p_extra, p_dec->fmt_out.i_extra);
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close
 *****************************************************************************/

static void Close(vlc_object_t *p_this)
{
    decoder_t *p_dec = (decoder_t*)p_this;
    decoder_sys_t *p_sys = p_dec->p_sys;
    packetizer_Clean(&p_sys->packetizer);

    RESETQ(pre);
    RESETQ(pus);
    RESETQ(current.pre);
    RESETQ(current.vcl);
    RESETQ(current.post);
    RESETQ(post);

    for(unsigned i=0;i<H266_MAX_NUM_PPS; i++)
    {
        if(p_sys->rg_pps[i].p_decoded)
            h266_rbsp_release_pps(p_sys->rg_pps[i].p_decoded);
        if(p_sys->rg_pps[i].p_nal)
            block_Release(p_sys->rg_pps[i].p_nal);
    }

    for(unsigned i=0;i<H266_MAX_NUM_SPS; i++)
    {
        if(p_sys->rg_sps[i].p_decoded)
            h266_rbsp_release_sps(p_sys->rg_sps[i].p_decoded);
        if(p_sys->rg_sps[i].p_nal)
            block_Release(p_sys->rg_sps[i].p_nal);
    }

    for(unsigned i=0;i<H266_MAX_NUM_VPS; i++)
    {
        if(p_sys->rg_vps[i].p_decoded)
            h266_rbsp_release_vps(p_sys->rg_vps[i].p_decoded);
        if(p_sys->rg_vps[i].p_nal)
            block_Release(p_sys->rg_vps[i].p_nal);
    }

    cc_storage_delete(p_sys->p_ccs);

    free(p_sys);
}

/****************************************************************************
 * Packetize
 ****************************************************************************/

static block_t *PacketizeVVC1(decoder_t *p_dec, block_t **pp_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    return PacketizeXXC1(p_dec, p_dec->obj.logger,
                         p_sys->i_nal_length_size, pp_block,
                         ParseNALBlockW, PacketizeDrain);
}

static block_t *PacketizeAnnexB(decoder_t *p_dec, block_t **pp_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    return packetizer_Packetize(&p_sys->packetizer, pp_block);
}

static void PacketizeFlush(decoder_t *p_dec)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    packetizer_Flush(&p_sys->packetizer);
}

/*****************************************************************************
 * GetCc:
 *****************************************************************************/

static block_t *GetCc(decoder_t *p_dec, decoder_cc_desc_t *p_desc)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    return cc_storage_get_current(p_sys->p_ccs, p_desc);
}

/****************************************************************************
 * Packetizer Helpers
 ****************************************************************************/

static void PacketizeReset(void *p_private, bool b_flush)
{
    decoder_t *p_dec = p_private;
    decoder_sys_t *p_sys = p_dec->p_sys;

    block_t *p_out = OutputQueues(p_sys, false);
    if(p_out)
        block_ChainRelease(p_out);

    p_out = CurrentQueueToPU(p_sys, true);
    if(p_out)
        block_ChainRelease(p_out);

    if(b_flush)
    {
        p_sys->sets = MISSING;
        p_sys->b_recovery_point = false;
    }
    p_sys->previous.b_valid = false;
    for(int i=0; i<=H266_MAX_NUH_LAYER_ID; i++)
        h266_poc_ctx_init(&p_sys->pocctx[i]);
    p_sys->b_need_ts = true;
    date_Set(&p_sys->dts, VLC_TICK_INVALID);
}

static bool InsertXPS(decoder_t *p_dec, uint8_t i_nal_type, uint8_t i_id,
                      const block_t *p_nalb)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    struct h266_tuple_s *p_tuple;
    void **pp_active;

    switch(i_nal_type)
    {
    case H266_NAL_VPS: /* VPS is optional, ID 0 does not exists */
        if(i_id > H266_VPS_ID_MAX)
            return false;
        p_tuple = &p_sys->rg_vps[i_id - 1];
        pp_active = (void**)&p_sys->p_active_vps;
        break;
    case H266_NAL_SPS:
        if(i_id > H266_SPS_ID_MAX)
            return false;
        p_tuple = &p_sys->rg_sps[i_id];
        pp_active = (void**)&p_sys->p_active_sps;
        break;
    case H266_NAL_PPS:
        if(i_id > H266_PPS_ID_MAX)
            return false;
        p_tuple = &p_sys->rg_pps[i_id];
        pp_active = (void**)&p_sys->p_active_pps;
        break;
    default:
        return false;
    }

    /* Check if we really need to re-decode/replace */
    if(p_tuple->p_nal)
    {
        const uint8_t *p_stored = p_tuple->p_nal->p_buffer;
        size_t i_stored = p_tuple->p_nal->i_buffer;
        hxxx_strip_AnnexB_startcode(&p_stored, &i_stored);
        const uint8_t *p_new = p_nalb->p_buffer;
        size_t i_new = p_nalb->i_buffer;
        hxxx_strip_AnnexB_startcode(&p_new, &i_new);
        if(i_stored == i_new && !memcmp(p_stored, p_new, i_new))
            return true;
    }

    /* Free associated decoded version */
    if(p_tuple->p_decoded)
    {
        switch(i_nal_type)
        {
        case H266_NAL_VPS:
            h266_rbsp_release_vps(p_tuple->p_decoded);
            break;
        case H266_NAL_SPS:
            h266_rbsp_release_sps(p_tuple->p_decoded);
            break;
        case H266_NAL_PPS:
            h266_rbsp_release_pps(p_tuple->p_decoded);
            break;
        }
        if(*pp_active == p_tuple->p_decoded)
            *pp_active = NULL;
        else
            pp_active = NULL; /* don't change pointer */
        p_tuple->p_decoded = NULL;
    }
    else pp_active = NULL;

    /* Free raw stored version */
    if(p_tuple->p_nal)
    {
        block_Release(p_tuple->p_nal);
        p_tuple->p_nal = NULL;
    }

    const uint8_t *p_buffer = p_nalb->p_buffer;
    size_t i_buffer = p_nalb->i_buffer;
    if(hxxx_strip_AnnexB_startcode(&p_buffer, &i_buffer))
    {
        /* Create decoded entries */
        switch(i_nal_type)
        {
        case H266_NAL_SPS:
            p_tuple->p_decoded = h266_decode_sps(p_buffer, i_buffer, true);
            if(!p_tuple->p_decoded)
            {
                msg_Err(p_dec, "Failed decoding SPS id %d", i_id);
                return false;
            }
            break;
        case H266_NAL_PPS:
            p_tuple->p_decoded = h266_decode_pps(p_buffer, i_buffer, true);
            if(!p_tuple->p_decoded)
            {
                msg_Err(p_dec, "Failed decoding PPS id %d", i_id);
                return false;
            }
            break;
        case H266_NAL_VPS:
            p_tuple->p_decoded = h266_decode_vps(p_buffer, i_buffer, true);
            if(!p_tuple->p_decoded)
            {
                msg_Err(p_dec, "Failed decoding VPS id %d", i_id);
                return false;
            }
            break;
        }

        if(p_tuple->p_decoded && pp_active) /* restore active by id */
            *pp_active = p_tuple->p_decoded;

        p_tuple->p_nal = block_Duplicate((block_t *)p_nalb);

        return true;
    }

    return false;
}

static block_t *GetXPSCopy(decoder_sys_t *p_sys)
{
    block_t *p_chain = NULL;
    block_t **pp_append = &p_chain;
    struct h266_tuple_s *xpstype[3] = {p_sys->rg_vps, p_sys->rg_sps, p_sys->rg_pps};
    size_t xpsmax[3] = {ARRAY_SIZE(p_sys->rg_vps), ARRAY_SIZE(p_sys->rg_sps), ARRAY_SIZE(p_sys->rg_pps)};
    for(size_t i=0; i<3; i++)
    {
        struct h266_tuple_s *xps = xpstype[i];
        for(size_t j=0; j<xpsmax[i]; j++)
        {
            block_t *p_dup;
            if(xps[j].p_nal &&
                (p_dup = block_Duplicate(xps[j].p_nal)))
                block_ChainLastAppend(&pp_append, p_dup);
        };
    }
    return p_chain;
}

static bool XPSReady(decoder_sys_t *p_sys)
{
    for(unsigned i=0;i<=H266_PPS_ID_MAX; i++)
    {
        const h266_picture_parameter_set_t *p_pps = p_sys->rg_pps[i].p_decoded;
        if (p_pps)
        {
            uint8_t id_sps = h266_get_pps_sps_id(p_pps);
            const h266_sequence_parameter_set_t *p_sps = p_sys->rg_sps[id_sps].p_decoded;
            if(p_sps)
            {
                uint8_t id_vps = h266_get_sps_vps_id(p_sps);
                if(id_vps == 0 || p_sys->rg_vps[id_vps-1].p_decoded)
                    return true;
            }
        }
    }
    return false;
}

static void AppendAsAnnexB(const block_t *p_block,
                           uint8_t **pp_dst, size_t *pi_dst)
{
    if(SIZE_MAX - p_block->i_buffer < *pi_dst)
        return;

    size_t i_realloc = p_block->i_buffer + *pi_dst;
    uint8_t *p_realloc = realloc(*pp_dst, i_realloc);
    if(p_realloc)
    {
        memcpy(&p_realloc[*pi_dst], p_block->p_buffer, p_block->i_buffer);
        *pi_dst = i_realloc;
        *pp_dst = p_realloc;
    }
}

#define APPENDIF(set, rg, b) \
for(size_t i=0; i<ARRAY_SIZE(rg); i++)\
    {\
            if(((set != rg[i].p_decoded) == !b) && rg[i].p_nal)\
        {\
                AppendAsAnnexB(rg[i].p_nal, &p_data, &i_data);\
                if(b) break;\
        }\
    }

static void SetsToAnnexB(decoder_sys_t *p_sys,
                         const h266_picture_parameter_set_t *p_pps,
                         const h266_sequence_parameter_set_t *p_sps,
                         const h266_video_parameter_set_t *p_vps,
                         uint8_t **pp_out, size_t *pi_out)
{
    uint8_t *p_data = NULL;
    size_t i_data = 0;

    APPENDIF(p_vps, p_sys->rg_vps, true);
    APPENDIF(p_vps, p_sys->rg_vps, false);
    APPENDIF(p_sps, p_sys->rg_sps, true);
    APPENDIF(p_sps, p_sys->rg_sps, false);
    APPENDIF(p_pps, p_sys->rg_pps, true);
    APPENDIF(p_pps, p_sys->rg_pps, false);

    /* because we copy to i_extra :/ */
    if(i_data <= INT_MAX)
    {
        *pp_out = p_data;
        *pi_out = i_data;
    }
    else free(p_data);
}

static void ActivateSets(decoder_t *p_dec,
                         const h266_picture_parameter_set_t *p_pps,
                         const h266_sequence_parameter_set_t *p_sps,
                         const h266_video_parameter_set_t *p_vps)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    p_sys->p_active_pps = p_pps;
    p_sys->p_active_sps = p_sps;
    p_sys->p_active_vps = p_vps;

    if(p_sps)
    {
        if(!p_dec->fmt_out.video.i_frame_rate || !p_dec->fmt_out.video.i_frame_rate_base)
        {
            unsigned num, den;
            if(h266_get_frame_rate(p_sps, p_vps, &num, &den))
            {
                p_dec->fmt_out.video.i_frame_rate = num;
                p_dec->fmt_out.video.i_frame_rate_base = den;
                if(num <= UINT_MAX / 2 &&
                    (p_sys->dts.i_divider_den != den ||
                     p_sys->dts.i_divider_num != 2 * num))
                {
                    date_Change(&p_sys->dts, 2 * num, den);
                }
            }
            p_dec->fmt_out.video.i_frame_rate = p_sys->dts.i_divider_num >> 1;
            p_dec->fmt_out.video.i_frame_rate_base = p_sys->dts.i_divider_den;
        }

        if(p_dec->fmt_in->video.primaries == COLOR_PRIMARIES_UNDEF)
        {
            (void) h266_get_colorimetry(p_sps,
                                        &p_dec->fmt_out.video.primaries,
                                        &p_dec->fmt_out.video.transfer,
                                        &p_dec->fmt_out.video.space,
                                        &p_dec->fmt_out.video.color_range);
        }

        unsigned sizes[6];
        if(h266_get_picture_size(p_sps, &sizes[0], &sizes[1],
                                  &sizes[2], &sizes[3],
                                  &sizes[4], &sizes[5]))
        {
            p_dec->fmt_out.video.i_x_offset = sizes[0];
            p_dec->fmt_out.video.i_y_offset = sizes[1];
            p_dec->fmt_out.video.i_width = sizes[2];
            p_dec->fmt_out.video.i_height = sizes[3];
            if(p_dec->fmt_in->video.i_visible_width == 0)
            {
                p_dec->fmt_out.video.i_visible_width = sizes[4];
                p_dec->fmt_out.video.i_visible_height = sizes[5];
            }
        }

        if (p_dec->fmt_in->video.i_sar_num == 0 || p_dec->fmt_in->video.i_sar_den == 0)
        {
            unsigned num, den;
            if (h266_get_aspect_ratio(p_sps, &num, &den))
            {
                p_dec->fmt_out.video.i_sar_num = num;
                p_dec->fmt_out.video.i_sar_den = den;
            }
        }

        if(p_dec->fmt_out.i_extra == 0 && p_pps)
            SetsToAnnexB(p_sys, p_pps, p_sps, p_vps,
                         (uint8_t **)&p_dec->fmt_out.p_extra, &p_dec->fmt_out.i_extra);
    }
}

static void ParseStoredSEIChain(decoder_t *p_dec, const block_t *p_chain)
{
    for(; p_chain; p_chain = p_chain->p_next)
    {
        const uint8_t *p_buffer = p_chain->p_buffer;
        size_t i_buffer = p_chain->i_buffer;

        if(unlikely(!hxxx_strip_AnnexB_startcode(&p_buffer, &i_buffer)))
            continue;

        if(i_buffer < 3)
            continue;

        if(h266_getNALType(p_buffer) == H266_NAL_PREFIX_SEI)
            HxxxParseSEI(p_buffer, i_buffer, 2 /* nal header */, ParseSEICallback, p_dec);
    }
}

static void ParseStoredSEI(decoder_t *p_dec)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    ParseStoredSEIChain(p_dec, p_sys->current.pre.p_chain);
}

static void GetXPSSet(uint8_t i_pps_id, void *priv,
                      h266_picture_parameter_set_t **pp_pps,
                      h266_sequence_parameter_set_t **pp_sps,
                      h266_video_parameter_set_t **pp_vps)
{
    decoder_sys_t *p_sys = priv;
    *pp_sps = NULL;
    *pp_vps = NULL;
    if(!(*pp_pps = p_sys->rg_pps[i_pps_id].p_decoded))
        return;
    if(!(*pp_sps = p_sys->rg_sps[h266_get_pps_sps_id(*pp_pps)].p_decoded))
        return;
    uint8_t vps_id = h266_get_sps_vps_id(*pp_sps);
    if(vps_id == 0) /* optional */
        return;
    *pp_vps = p_sys->rg_vps[vps_id - 1].p_decoded;
}

static block_t *ParseFrameHeader(decoder_t *p_dec, uint8_t i_nal_type,
                                 const uint8_t *p_buffer, size_t i_buffer, block_t *p_frag,
                                 bool b_header,
                                 h266_picture_header_t * (*pf_parse)(const uint8_t *, size_t, bool,
                                                                     pf_get_matchedh266xps, void *))
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_outputchain = NULL;

    if(unlikely(i_buffer < 3))
    {
        block_Release(p_frag); /* to be checked before */
        return NULL;
    }

    bool b_new_au = !p_sys->previous.b_valid;
    const uint8_t i_layer = h266_getNALLayer(p_buffer);
    if(unlikely(i_layer >= H266_MAX_NUH_LAYER_ID))
    {
        block_Release(p_frag);
        return NULL;
    }
    if(b_header)
    {
        h266_sequence_parameter_set_t *p_sps = NULL;
        h266_picture_parameter_set_t *p_pps = NULL;
        h266_video_parameter_set_t *p_vps = NULL; /* /!\ optional */
        h266_picture_header_t *ph = pf_parse(p_buffer, i_buffer, true, GetXPSSet, p_sys);

        // damn new AU picture condition (can't rely on first layer being 0)
        if(p_sys->previous.b_valid)
            b_new_au |= (i_layer <= p_sys->previous.i_nuh_layer_id);
        p_sys->previous.i_nuh_layer_id = i_layer;

        if(ph)
        {
            GetXPSSet(h266_get_picture_header_pps_id(ph), p_sys, &p_pps, &p_sps, &p_vps);
            if(p_sps)
            {
                int poc = h266_compute_picture_order_count(p_sps, ph, &p_sys->pocctx[i_layer]);
                b_new_au |= (poc != p_sys->previous.poc);
                p_sys->previous.poc = poc;
            }
        }
        p_sys->previous.b_valid = true;

        if(b_new_au && p_sys->pus.p_chain)
            p_outputchain = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                                p_sys->b_recovery_point);

        if(ph && b_new_au)
            ActivateSets(p_dec, p_pps, p_sps, p_vps);

        ParseStoredSEI(p_dec);

        switch(i_nal_type)
        {
            case H266_NAL_IDR_W_RADL:
            case H266_NAL_IDR_N_LP:
            case H266_NAL_CRA:
            case H266_NAL_GDR:
                p_frag->i_flags |= BLOCK_FLAG_TYPE_I;
                break;

            default:
            {
                if(ph)
                {
                    enum h266_slice_type_e type;
                    if(h266_get_slice_type(ph, &type))
                    {
                        switch(type)
                        {
                        case H266_SLICE_TYPE_B:
                            p_frag->i_flags |= BLOCK_FLAG_TYPE_B;
                            break;
                        case H266_SLICE_TYPE_P:
                            p_frag->i_flags |= BLOCK_FLAG_TYPE_P;
                            break;
                        case H266_SLICE_TYPE_I:
                            p_frag->i_flags |= BLOCK_FLAG_TYPE_I;
                            break;
                        default:
                            break;
                        }
                    }
               }
           }
           break;
        }

        if(ph)
            h266_rbsp_release_picture_header(ph);
    }

    if(p_sys->sets == MISSING && b_new_au && XPSReady(p_sys))
        p_sys->sets = COMPLETE;

    if(p_sys->sets != MISSING && (p_frag->i_flags & BLOCK_FLAG_TYPE_I))
    {
        p_sys->b_recovery_point = true; /* FIXME: care about SEI recovery */
    }

    if(!p_sys->b_recovery_point) /* content will be dropped */
        cc_storage_reset(p_sys->p_ccs);

    return p_outputchain;
}

static block_t *ParseVCL(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_frag)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_outputchain = NULL;
    const uint8_t *p_buffer = p_frag->p_buffer;
    size_t i_buffer = p_frag->i_buffer;

    if(unlikely(!hxxx_strip_AnnexB_startcode(&p_buffer, &i_buffer) || i_buffer < 3))
    {
        block_ChainLastAppend(&p_sys->current.vcl.pp_chain_last, p_frag); /* might be corrupted */
        return NULL;
    }

    const bool sh_picture_header_in_slice_header_flag = p_buffer[2] & 0x80;

    if(sh_picture_header_in_slice_header_flag)
    {
        block_t *pu = CurrentQueueToPU(p_sys, false);
        if(pu)
             block_ChainLastAppend(&p_sys->pus.pp_chain_last, pu);
    }

    p_outputchain = ParseFrameHeader(p_dec, i_nal_type,
                                     p_buffer, i_buffer, p_frag,
                                     sh_picture_header_in_slice_header_flag,
                                     h266_decode_slice_header);

    block_ChainLastAppend(&p_sys->current.vcl.pp_chain_last, p_frag);

    return p_outputchain;
}

static block_t *ParsePH(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_frag)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_outputchain = NULL;
    const uint8_t *p_buffer = p_frag->p_buffer;
    size_t i_buffer = p_frag->i_buffer;

    if(unlikely(!hxxx_strip_AnnexB_startcode(&p_buffer, &i_buffer) || i_buffer < 3))
    {
        block_ChainLastAppend(&p_sys->pre.pp_chain_last, p_frag); /* might be corrupted */
        return NULL;
    }

    /* close the current PU if any */
    block_t *pu = CurrentQueueToPU(p_sys, false);
    if(pu)
        block_ChainLastAppend(&p_sys->pus.pp_chain_last, pu);

    p_outputchain = ParseFrameHeader(p_dec, i_nal_type,
                                     p_buffer, i_buffer, p_frag,
                                     true,
                                     h266_decode_picture_header);

    block_ChainLastAppend(&p_sys->current.pre.pp_chain_last, p_frag);

    return p_outputchain;
}

static block_t * ParseVCLAsso(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_nalb)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_ret = NULL;

    switch(i_nal_type)
    {
        /* Shall precede the first VCL */
        case H266_NAL_PREFIX_APS:
        /* shall not follow the first VCL */
        case H266_NAL_PREFIX_SEI:
            if(p_sys->current.vcl.p_chain || p_sys->current.post.p_chain)
            {
                block_t *pu = CurrentQueueToPU(p_sys, true);
                if(pu)
                     block_ChainLastAppend(&p_sys->pus.pp_chain_last, pu);
            }
            block_ChainLastAppend(&p_sys->current.pre.pp_chain_last, p_nalb);
            break;

        /* Shall follow the first VCL */
        case H266_NAL_SUFFIX_APS:
        /* Shall not precede the first VCL */
        case H266_NAL_SUFFIX_SEI:
        case H266_NAL_FD:
        case H266_NAL_RSV_NVCL_27:
        case H266_NAL_UNSPEC_30:
        case H266_NAL_UNSPEC_31:
            if(!p_sys->current.vcl.p_chain)
            {
                p_ret = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                            p_sys->b_recovery_point);
                block_Release(p_nalb);
                return p_ret;
            }
            block_ChainLastAppend(&p_sys->current.post.pp_chain_last, p_nalb);
            break;

        default:
            block_Release(p_nalb);
            return NULL;
    }

    return p_ret;
}

static block_t * ParseAUHead(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_nalb)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_ret = NULL;
    const uint8_t *p_buffer = p_nalb->p_buffer;
    size_t i_buffer = p_nalb->i_buffer;

    if(p_sys->post.p_chain)
        p_ret = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                    p_sys->b_recovery_point);

    switch(i_nal_type)
    {
        // explicit boundary
        case H266_NAL_AUD:
        case H266_NAL_OPI:
            if(!p_ret && (p_sys->pus.p_chain || p_sys->current.vcl.p_chain))
                p_ret = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                            p_sys->b_recovery_point);
            p_sys->previous.b_valid = false;
            block_ChainLastAppend(&p_sys->pre.pp_chain_last, p_nalb);
            return p_ret;

        // can still be associated
        case H266_NAL_DCI:
            if(p_dec->fmt_out.i_profile == -1 &&
                hxxx_strip_AnnexB_startcode(&p_buffer, &i_buffer))
            {
                h266_decoding_capability_information_t *p_dci =
                    h266_decode_dci(p_buffer, i_buffer, true);
                if(p_dci)
                {
                    uint8_t pl[2];
                    if(h266_get_profile_tier_level(p_dci, NULL, &pl[0], &pl[e='color: green'>1]))
                    {
                        p_dec->fmt_out.i_profile = pl[0];
                        p_dec->fmt_out.i_level = pl[1];
                    }
                    h266_rbsp_release_dci(p_dci);
                }
            }
            break;

        case H266_NAL_VPS:
        case H266_NAL_PPS:
        case H266_NAL_SPS:
            {
                uint8_t i_id;
                const uint8_t *p_xps = p_nalb->p_buffer;
                size_t i_xps = p_nalb->i_buffer;
                if(hxxx_strip_AnnexB_startcode(&p_xps, &i_xps) &&
                   h266_get_xps_id(p_xps, i_xps, &i_id))
                    InsertXPS(p_dec, i_nal_type, i_id, p_nalb);
                if(p_sys->sets != SENT) /* will store/inject on first recovery point */
                {
                    block_Release(p_nalb);
                    return p_ret;
                }
            }
            break;

        default:
            break;
    }

    block_ChainLastAppend(&p_sys->current.pre.pp_chain_last, p_nalb);

    return p_ret;
}

static block_t * ParseAUTail(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_nalb)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    if(i_nal_type == H266_NAL_EOS)
    {
        p_nalb->i_flags |= BLOCK_FLAG_END_OF_SEQUENCE;
        block_ChainLastAppend(&p_sys->current.post.pp_chain_last, p_nalb);

        for(int i=0; i<=H266_MAX_NUH_LAYER_ID; i++)
            h266_poc_ctx_init(&p_sys->pocctx[i]);
        p_sys->previous.b_valid = false;

        return NULL;
    }

    block_ChainLastAppend(&p_sys->post.pp_chain_last, p_nalb);

    block_t *p_ret = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                  p_sys->b_recovery_point);
    if(p_ret)
        p_ret->i_flags |= BLOCK_FLAG_END_OF_SEQUENCE;

    if(!p_ret && p_sys->pus.p_chain == NULL)
        p_ret = OutputQueues(p_sys, false);

    return p_ret;
}

static block_t * ParseNonVCL(decoder_t *p_dec, uint8_t i_nal_type, block_t *p_nalb)
{
    block_t *p_ret = NULL;

    switch(i_nal_type)
    {
        case H266_NAL_OPI:
        case H266_NAL_DCI:
        case H266_NAL_VPS:
        case H266_NAL_SPS:
        case H266_NAL_PPS:
        case H266_NAL_AUD:
        case H266_NAL_RSV_NVCL_26:
        case H266_NAL_UNSPEC_28:
        case H266_NAL_UNSPEC_29:
            p_ret = ParseAUHead(p_dec, i_nal_type, p_nalb);
            break;

        case H266_NAL_PH:
            p_ret = ParsePH(p_dec, i_nal_type, p_nalb);
            break;

        case H266_NAL_FD:
        case H266_NAL_PREFIX_APS:
        case H266_NAL_SUFFIX_APS:
        case H266_NAL_PREFIX_SEI:
        case H266_NAL_SUFFIX_SEI:
        case H266_NAL_RSV_NVCL_27:
        case H266_NAL_UNSPEC_30:
        case H266_NAL_UNSPEC_31:
            p_ret = ParseVCLAsso(p_dec, i_nal_type, p_nalb);
            break;

        case H266_NAL_EOS:
        case H266_NAL_EOB:
        default:
            p_ret = ParseAUTail(p_dec, i_nal_type, p_nalb);
            break;
    }

    return p_ret;
}

static block_t *GatherAndValidateChain(block_t *p_outputchain)
{
    block_t *p_output = NULL;

    if(p_outputchain)
    {
        if(p_outputchain->i_flags & BLOCK_FLAG_DROP)
            p_output = p_outputchain; /* Avoid useless gather */
        else
            p_output = block_ChainGather(p_outputchain);
    }

    if(p_output && (p_output->i_flags & BLOCK_FLAG_DROP))
    {
        block_ChainRelease(p_output); /* Chain! see above */
        p_output = NULL;
    }

    return p_output;
}

static void SetOutputBlockProperties(decoder_t *p_dec, block_t *p_output)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    /* Set frame duration */

    const vlc_tick_t i_start = date_Get(&p_sys->dts);
    if(i_start != VLC_TICK_INVALID)
    {
        date_Increment(&p_sys->dts, 2);
        p_output->i_length = date_Get(&p_sys->dts) - i_start;
    }
    p_sys->pts = VLC_TICK_INVALID;

    p_output->i_flags &= ~BLOCK_FLAG_AU_END;
}

/*****************************************************************************
 * ParseNALBlock: parses annexB type NALs
 * All p_frag blocks are required to start with 0 0 0 1 4-byte startcode
 *****************************************************************************/

static block_t *ParseNALBlock(decoder_t *p_dec, bool *pb_ts_used, block_t *p_frag)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    *pb_ts_used = false;
    bool b_au_end = p_frag->i_flags & BLOCK_FLAG_AU_END;

    if(p_sys->b_need_ts)
    {
        if(p_frag->i_dts != VLC_TICK_INVALID)
            date_Set(&p_sys->dts, p_frag->i_dts);
        p_sys->pts = p_frag->i_pts;
        if(date_Get(&p_sys->dts) != VLC_TICK_INVALID)
            p_sys->b_need_ts = false;
        *pb_ts_used = true;
    }

    if(unlikely(p_frag->i_buffer < 5))
    {
        msg_Warn(p_dec,"NAL too small");
        block_Release(p_frag);
        return NULL;
    }

    /* Get NALU type */
    const vlc_tick_t dts = p_frag->i_dts, pts = p_frag->i_pts;
    block_t * p_output = NULL;
    uint8_t i_nal_type = h266_getNALType(&p_frag->p_buffer[4]);
    switch(i_nal_type)
    {
        case H266_NAL_IDR_W_RADL:
        case H266_NAL_IDR_N_LP:
        case H266_NAL_CRA:
        case H266_NAL_GDR:
        case H266_NAL_RSV_IRAP_11:
            p_frag->i_flags |= BLOCK_FLAG_TYPE_I;
            // fallthrough
        case H266_NAL_TRAIL:
        case H266_NAL_STSA:
        case H266_NAL_RADL:
        case H266_NAL_RASL:
        case H266_NAL_RSV_VCL_4:
        case H266_NAL_RSV_VCL_5:
        case H266_NAL_RSV_VCL_6:
            /* NAL is a VCL NAL */
            p_output = ParseVCL(p_dec, i_nal_type, p_frag);
            if (p_output && (p_output->i_flags & BLOCK_FLAG_DROP))
                msg_Info(p_dec, "Waiting for SPS/PPS");
            break;
        default:
            p_output = ParseNonVCL(p_dec, i_nal_type, p_frag);
            break;
    }

    if(b_au_end)
    {
        if(!p_output)
            p_output = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                           p_sys->b_recovery_point);
        p_sys->previous.b_valid = false;
    }

    p_output = GatherAndValidateChain(p_output);
    if(p_output)
    {
        if(p_sys->sets != SENT)
        {
            assert(p_sys->sets == COMPLETE);
            p_sys->sets = SENT;
        }
        SetOutputBlockProperties(p_dec, p_output);
        if (dts != VLC_TICK_INVALID && dts > date_Get(&p_sys->dts))
            date_Set(&p_sys->dts, dts);
        p_sys->pts = pts;
        *pb_ts_used = true;
    }

    return p_output;
}

static block_t *PacketizeParse(void *p_private, bool *pb_ts_used, block_t *p_block)
{
    decoder_t *p_dec = p_private;
    decoder_sys_t *p_sys = p_dec->p_sys;
    VLC_UNUSED(p_sys);

    /* Remove trailing 0 bytes */
    while (p_block->i_buffer > 5 && p_block->p_buffer[p_block->i_buffer-1] == 0x00)
        p_block->i_buffer--;

    p_block = ParseNALBlock(p_dec, pb_ts_used, p_block);

    return p_block;
}

static int PacketizeValidate(void *p_private, block_t *p_au)
{
    VLC_UNUSED(p_private);
    VLC_UNUSED(p_au);
    return VLC_SUCCESS;
}

static block_t * PacketizeDrain(void *p_private)
{
    decoder_t *p_dec = p_private;
    decoder_sys_t *p_sys = p_dec->p_sys;

    block_t *p_out = NULL;

    block_t *pu = CurrentQueueToPU(p_sys, true);
    if(pu)
        block_ChainLastAppend(&p_sys->pus.pp_chain_last, pu);

    if(p_sys->pus.p_chain)
    {
        p_out = OutputQueues(p_sys, p_sys->sets != MISSING &&
                                    p_sys->b_recovery_point);
        if(p_out)
        {
            p_out = GatherAndValidateChain(p_out);
            if(p_out)
                SetOutputBlockProperties(p_dec, p_out);
        }
    }
    return p_out;
}

static bool ParseSEICallback(const hxxx_sei_data_t *p_sei_data, void *cbdata)
{
    decoder_t *p_dec = (decoder_t *) cbdata;
    decoder_sys_t *p_sys = p_dec->p_sys;

    switch(p_sei_data->i_type)
    {
    case HXXX_SEI_USER_DATA_REGISTERED_ITU_T_T35:
    {
        if(p_sei_data->itu_t35.type == HXXX_ITU_T35_TYPE_CC)
        {
            cc_storage_append(p_sys->p_ccs, true, p_sei_data->itu_t35.u.cc.p_data,
                              p_sei_data->itu_t35.u.cc.i_data);
        }
    } break;
    case HXXX_SEI_FRAME_PACKING_ARRANGEMENT:
    {
        if(p_dec->fmt_in->video.multiview_mode == MULTIVIEW_2D)
        {
            video_multiview_mode_t mode;
            switch(p_sei_data->frame_packing.type)
            {
            case FRAME_PACKING_INTERLEAVED_CHECKERBOARD:
                mode = MULTIVIEW_STEREO_CHECKERBOARD; break;
            case FRAME_PACKING_INTERLEAVED_COLUMN:
                mode = MULTIVIEW_STEREO_COL; break;
            case FRAME_PACKING_INTERLEAVED_ROW:
                mode = MULTIVIEW_STEREO_ROW; break;
            case FRAME_PACKING_SIDE_BY_SIDE:
                mode = MULTIVIEW_STEREO_SBS; break;
            case FRAME_PACKING_TOP_BOTTOM:
                mode = MULTIVIEW_STEREO_TB; break;
            case FRAME_PACKING_TEMPORAL:
                mode = MULTIVIEW_STEREO_FRAME; break;
            case FRAME_PACKING_TILED:
            default:
                mode = MULTIVIEW_2D; break;
            }
            p_dec->fmt_out.video.multiview_mode = mode;
        }
    } break;
    case HXXX_SEI_MASTERING_DISPLAY_COLOUR_VOLUME:
    {
        video_format_t *p_fmt = &p_dec->fmt_out.video;
        for (size_t i=0; i<ARRAY_SIZE(p_sei_data->colour_volume.primaries); ++i)
            p_fmt->mastering.primaries[i] = p_sei_data->colour_volume.primaries[i];
        for (size_t i=0; i<ARRAY_SIZE(p_sei_data->colour_volume.white_point); ++i)
            p_fmt->mastering.white_point[i] = p_sei_data->colour_volume.white_point[i];
        p_fmt->mastering.max_luminance = p_sei_data->colour_volume.max_luminance;
        p_fmt->mastering.min_luminance = p_sei_data->colour_volume.min_luminance;
    } break;
    case HXXX_SEI_CONTENT_LIGHT_LEVEL:
    {
        video_format_t *p_fmt = &p_dec->fmt_out.video;
        p_fmt->lighting.MaxCLL = p_sei_data->content_light_lvl.MaxCLL;
        p_fmt->lighting.MaxFALL = p_sei_data->content_light_lvl.MaxFALL;
    } break;
    }

    return true;
}

Messung
CHZG
839610093

¤ Dauer der Verarbeitung: 0.19 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=1104848
#Domains=416966