/*****************************************************************************
 * hevc.c: h.265/hevc video packetizer
 *****************************************************************************
 * Copyright (C) 2014 VLC authors and VideoLAN
 * $Id: 51f169fcf646c6a54d663a688206a4cc963ab234 $
 *
 * Authors: Denis Charmet <typx@videolan.org>
 *
 * 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 "hevc_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_category(CAT_SOUT)
    set_subcategory(SUBCAT_SOUT_PACKETIZER)
    set_description(N_("HEVC/H.265 video packetizer"))
    set_capability("packetizer", 50)
    set_callbacks(Open, Close)
vlc_module_end ()


/****************************************************************************
 * Local prototypes
 ****************************************************************************/
static block_t *PacketizeAnnexB(decoder_t *, block_t **);
static block_t *PacketizeHVC1(decoder_t *, block_t **);
static void PacketizeFlush( decoder_t * );
static void PacketizeReset(void *p_private, bool b_broken);
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 int PacketizeValidate(void *p_private, block_t *);
static block_t * PacketizeDrain(void *);
static bool ParseSEICallback( const hxxx_sei_data_t *, void * );
static block_t *GetCc( decoder_t *, decoder_cc_desc_t * );

struct decoder_sys_t
{
    /* */
    packetizer_t packetizer;

    struct
    {
        block_t *p_chain;
        block_t **pp_chain_last;
    } frame, pre, post;

    uint8_t  i_nal_length_size;

    struct
    {
        block_t *p_nal;
        void *p_decoded;
    } rg_vps[HEVC_VPS_ID_MAX + 1],
      rg_sps[HEVC_SPS_ID_MAX + 1],
      rg_pps[HEVC_PPS_ID_MAX + 1];

    const hevc_video_parameter_set_t    *p_active_vps;
    const hevc_sequence_parameter_set_t *p_active_sps;
    const hevc_picture_parameter_set_t  *p_active_pps;
    hevc_sei_pic_timing_t *p_timing;
    bool b_init_sequence_complete;

    date_t dts;
    mtime_t pts;
    bool b_need_ts;

    /* */
    cc_storage_t *p_ccs;
};

#define BLOCK_FLAG_DROP (1 << BLOCK_FLAG_PRIVATE_SHIFT)

static const uint8_t p_hevc_startcode[3] = {0x00, 0x00, 0x01};
/****************************************************************************
 * Helpers
 ****************************************************************************/
static inline void InitQueue( block_t **pp_head, block_t ***ppp_tail )
{
    *pp_head = NULL;
    *ppp_tail = pp_head;
}
#define INITQ(name) InitQueue(&p_sys->name.p_chain, &p_sys->name.pp_chain_last)

static block_t * OutputQueues(decoder_sys_t *p_sys, bool b_valid)
{
    block_t *p_output = NULL;
    block_t **pp_output_last = &p_output;
    uint32_t i_flags = 0; /* Because block_ChainGather does not merge flags or times */

    if(p_sys->pre.p_chain)
    {
        i_flags |= p_sys->pre.p_chain->i_flags;
        block_ChainLastAppend(&pp_output_last, p_sys->pre.p_chain);
        INITQ(pre);
    }

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

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

    if(p_output)
    {
        p_output->i_flags |= 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_HEVC)
        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(frame);
    INITQ(post);

    packetizer_Init(&p_dec->p_sys->packetizer,
                    p_hevc_startcode, sizeof(p_hevc_startcode), startcode_FindAnnexB,
                    p_hevc_startcode, 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 );
    date_Set( &p_sys->dts, VLC_TS_INVALID );
    p_sys->pts = VLC_TS_INVALID;
    p_sys->b_need_ts = true;

    /* 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(hevc_ishvcC(p_extra, i_extra))
    {
        p_dec->pf_packetize = PacketizeHVC1;

        /* Clear hvcC/HVC1 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 =
                hevc_hvcC_to_AnnexB_NAL(p_extra, i_extra,
                                        &i_new_extra, &p_sys->i_nal_length_size);
        if(p_dec->fmt_out.p_extra)
            p_dec->fmt_out.i_extra = i_new_extra;
    }
    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);

    block_ChainRelease(p_sys->frame.p_chain);
    block_ChainRelease(p_sys->pre.p_chain);
    block_ChainRelease(p_sys->post.p_chain);

    for(unsigned i=0;i<=HEVC_PPS_ID_MAX; i++)
    {
        if(p_sys->rg_pps[i].p_decoded)
            hevc_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<=HEVC_SPS_ID_MAX; i++)
    {
        if(p_sys->rg_sps[i].p_decoded)
            hevc_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<=HEVC_VPS_ID_MAX; i++)
    {
        if(p_sys->rg_vps[i].p_decoded)
            hevc_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);
    }

    hevc_release_sei_pic_timing( p_sys->p_timing );

    cc_storage_delete( p_sys->p_ccs );

    free(p_sys);
}

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

    return PacketizeXXC1( p_dec, p_sys->i_nal_length_size,
                          pp_block, ParseNALBlock );
}

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 )
{
    return cc_storage_get_current( p_dec->p_sys->p_ccs, p_desc );
}

/****************************************************************************
 * Packetizer Helpers
 ****************************************************************************/
static void PacketizeReset(void *p_private, bool b_broken)
{
    VLC_UNUSED(b_broken);

    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_sys->b_init_sequence_complete = false;
    p_sys->b_need_ts = true;
    date_Set(&p_sys->dts, VLC_TS_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;
    void **pp_decoded;
    void **pp_active;
    block_t **pp_nal;

    switch(i_nal_type)
    {
        case HEVC_NAL_VPS:
            if(i_id > HEVC_VPS_ID_MAX)
                return false;
            pp_decoded = &p_sys->rg_vps[i_id].p_decoded;
            pp_nal = &p_sys->rg_vps[i_id].p_nal;
            pp_active = (void**)&p_sys->p_active_vps;
            break;
        case HEVC_NAL_SPS:
            if(i_id > HEVC_SPS_ID_MAX)
                return false;
            pp_decoded = &p_sys->rg_sps[i_id].p_decoded;
            pp_nal = &p_sys->rg_sps[i_id].p_nal;
            pp_active = (void**)&p_sys->p_active_sps;
            break;
        case HEVC_NAL_PPS:
            if(i_id > HEVC_PPS_ID_MAX)
                return false;
            pp_decoded = &p_sys->rg_pps[i_id].p_decoded;
            pp_nal = &p_sys->rg_pps[i_id].p_nal;
            pp_active = (void**)&p_sys->p_active_pps;
            break;
        default:
            return false;
    }

    /* Check if we really need to re-decode/replace */
    if(*pp_nal)
    {
        const uint8_t *p_stored = (*pp_nal)->p_buffer;
        size_t i_stored = (*pp_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(*pp_decoded)
    {
        switch(i_nal_type)
        {
            case HEVC_NAL_VPS:
                hevc_rbsp_release_vps(*pp_decoded);
                break;
            case HEVC_NAL_SPS:
                hevc_rbsp_release_sps(*pp_decoded);
                break;
            case HEVC_NAL_PPS:
                hevc_rbsp_release_pps(*pp_decoded);
                break;
        }
        if(*pp_active == *pp_decoded)
            *pp_active = NULL;
        else
            pp_active = NULL; /* don't change pointer */
        *pp_decoded = NULL;
    }
    else pp_active = NULL;

    /* Free raw stored version */
    if(*pp_nal)
    {
        block_Release(*pp_nal);
        *pp_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 HEVC_NAL_SPS:
                *pp_decoded = hevc_decode_sps(p_buffer, i_buffer, true);
                if(!*pp_decoded)
                {
                    msg_Err(p_dec, "Failed decoding SPS id %d", i_id);
                    return false;
                }
                break;
            case HEVC_NAL_PPS:
                *pp_decoded = hevc_decode_pps(p_buffer, i_buffer, true);
                if(!*pp_decoded)
                {
                    msg_Err(p_dec, "Failed decoding PPS id %d", i_id);
                    return false;
                }
                break;
            case HEVC_NAL_VPS:
                *pp_decoded = hevc_decode_vps(p_buffer, i_buffer, true);
                if(!*pp_decoded)
                {
                    msg_Err(p_dec, "Failed decoding VPS id %d", i_id);
                    return false;
                }
                break;
        }

        if(*pp_decoded && pp_active) /* restore active by id */
            *pp_active = *pp_decoded;

        *pp_nal = block_Duplicate((block_t *)p_nalb);

        return true;
    }

    return false;
}

static bool XPSReady(decoder_sys_t *p_sys)
{
    for(unsigned i=0;i<=HEVC_PPS_ID_MAX; i++)
    {
        const hevc_picture_parameter_set_t *p_pps = p_sys->rg_pps[i].p_decoded;
        if (p_pps)
        {
            uint8_t id_sps = hevc_get_pps_sps_id(p_pps);
            const hevc_sequence_parameter_set_t *p_sps = p_sys->rg_sps[id_sps].p_decoded;
            if(p_sps)
            {
                uint8_t id_vps = hevc_get_sps_vps_id(p_sps);
                if(p_sys->rg_vps[id_vps].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(idmax, set, rg, b) \
    for(size_t i=0; i<=idmax; i++)\
    {\
        if(((set != rg[i].p_decoded) == !b) && rg[i].p_nal)\
        {\
            AppendAsAnnexB(rg[i].p_nal, &p_data, &i_data);\
            break;\
        }\
    }

static void SetsToAnnexB(decoder_sys_t *p_sys,
                         const hevc_picture_parameter_set_t *p_pps,
                         const hevc_sequence_parameter_set_t *p_sps,
                         const hevc_video_parameter_set_t *p_vps,
                         uint8_t **pp_out, int *pi_out)
{
    uint8_t *p_data = NULL;
    size_t i_data = 0;

    APPENDIF(HEVC_VPS_ID_MAX, p_vps, p_sys->rg_vps, true);
    APPENDIF(HEVC_VPS_ID_MAX, p_vps, p_sys->rg_vps, false);
    APPENDIF(HEVC_SPS_ID_MAX, p_sps, p_sys->rg_sps, true);
    APPENDIF(HEVC_SPS_ID_MAX, p_sps, p_sys->rg_sps, false);
    APPENDIF(HEVC_PPS_ID_MAX, p_pps, p_sys->rg_pps, true);
    APPENDIF(HEVC_PPS_ID_MAX, 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 hevc_picture_parameter_set_t *p_pps,
                         const hevc_sequence_parameter_set_t *p_sps,
                         const hevc_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(hevc_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(p_sys->dts.i_divider_den != den &&
                   p_sys->dts.i_divider_num != 2 * num &&
                   num <= UINT_MAX / 2)
                    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) hevc_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.b_color_range_full);
        }

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

        if(p_dec->fmt_in.i_profile == -1)
        {
            uint8_t i_profile, i_level;
            if( hevc_get_sps_profile_tier_level( p_sps, &i_profile, &i_level ) )
            {
                p_dec->fmt_out.i_profile = i_profile;
                p_dec->fmt_out.i_level = i_level;
            }
        }

        if(p_dec->fmt_out.i_extra == 0 && p_vps && 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 GetXPSSet(uint8_t i_pps_id, void *priv,
                      hevc_picture_parameter_set_t **pp_pps,
                      hevc_sequence_parameter_set_t **pp_sps,
                      hevc_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))
        if((*pp_sps = p_sys->rg_sps[hevc_get_pps_sps_id(*pp_pps)].p_decoded))
            *pp_vps = p_sys->rg_vps[hevc_get_sps_vps_id(*pp_sps)].p_decoded;
}

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

    for( block_t *p_nal = p_sys->pre.p_chain;
                  p_nal; p_nal = p_nal->p_next )
    {
        if( p_nal->i_buffer < 5 )
            continue;

        if( hevc_getNALType(&p_nal->p_buffer[4]) == HEVC_NAL_PREF_SEI )
        {
            HxxxParse_AnnexB_SEI( p_nal->p_buffer, p_nal->i_buffer,
                                  2 /* nal header */, ParseSEICallback, p_dec );
        }
    }
}

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->frame.pp_chain_last, p_frag); /* might be corrupted */
        return NULL;
    }

    const uint8_t i_layer = hevc_getNALLayer( p_buffer );
    bool b_first_slice_in_pic = p_buffer[2] & 0x80;
    if (b_first_slice_in_pic)
    {
        if(p_sys->frame.p_chain)
        {
            /* Starting new frame: return previous frame data for output */
            p_outputchain = OutputQueues(p_sys, p_sys->b_init_sequence_complete);
        }

        hevc_slice_segment_header_t *p_sli = hevc_decode_slice_header(p_buffer, i_buffer, true,
                                                                      GetXPSSet, p_sys);
        if(p_sli && i_layer == 0)
        {
            hevc_sequence_parameter_set_t *p_sps;
            hevc_picture_parameter_set_t *p_pps;
            hevc_video_parameter_set_t *p_vps;
            GetXPSSet(hevc_get_slice_pps_id(p_sli), p_sys, &p_pps, &p_sps, &p_vps);
            ActivateSets(p_dec, p_pps, p_sps, p_vps);
        }

        ParseStoredSEI( p_dec );

        switch(i_nal_type)
        {
            case HEVC_NAL_BLA_W_LP:
            case HEVC_NAL_BLA_W_RADL:
            case HEVC_NAL_BLA_N_LP:
            case HEVC_NAL_IDR_W_RADL:
            case HEVC_NAL_IDR_N_LP:
            case HEVC_NAL_CRA:
                p_frag->i_flags |= BLOCK_FLAG_TYPE_I;
                break;

            default:
            {
                if(p_sli)
                {
                    enum hevc_slice_type_e type;
                    if(hevc_get_slice_type( p_sli, &type ))
                    {
                        switch(type)
                        {
                            case HEVC_SLICE_TYPE_B:
                                p_frag->i_flags |= BLOCK_FLAG_TYPE_B;
                                break;
                            case HEVC_SLICE_TYPE_P:
                                p_frag->i_flags |= BLOCK_FLAG_TYPE_P;
                                break;
                            case HEVC_SLICE_TYPE_I:
                                p_frag->i_flags |= BLOCK_FLAG_TYPE_I;
                                break;
                        }
                    }
                }
                else p_frag->i_flags |= BLOCK_FLAG_TYPE_B;
            }
            break;
        }

        if(p_sli)
            hevc_rbsp_release_slice_header(p_sli);
    }

    if(!p_sys->b_init_sequence_complete && i_layer == 0 &&
       (p_frag->i_flags & BLOCK_FLAG_TYPE_I) && XPSReady(p_sys))
    {
        p_sys->b_init_sequence_complete = true;
    }

    if( !p_sys->b_init_sequence_complete )
        cc_storage_reset( p_sys->p_ccs );

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

    return p_outputchain;
}

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;

    if(p_sys->post.p_chain || p_sys->frame.p_chain)
        p_ret = OutputQueues(p_sys, p_sys->b_init_sequence_complete);

    switch(i_nal_type)
    {
        case HEVC_NAL_AUD:
            if(!p_ret && p_sys->pre.p_chain)
                p_ret = OutputQueues(p_sys, p_sys->b_init_sequence_complete);
            break;

        case HEVC_NAL_VPS:
        case HEVC_NAL_SPS:
        case HEVC_NAL_PPS:
        {
            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) &&
               hevc_get_xps_id(p_nalb->p_buffer, p_nalb->i_buffer, &i_id))
                InsertXPS(p_dec, i_nal_type, i_id, p_nalb);
            break;
        }

        case HEVC_NAL_PREF_SEI:
            /* stored an parsed later when we get sps & frame */
        default:
            break;
    }

    block_ChainLastAppend(&p_sys->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;
    block_t *p_ret = NULL;

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

    switch(i_nal_type)
    {
        case HEVC_NAL_EOS:
        case HEVC_NAL_EOB:
            p_ret = OutputQueues(p_sys, p_sys->b_init_sequence_complete);
            if( p_ret )
                p_ret->i_flags |= BLOCK_FLAG_END_OF_SEQUENCE;
            break;

        case HEVC_NAL_SUFF_SEI:
            HxxxParse_AnnexB_SEI( p_nalb->p_buffer, p_nalb->i_buffer,
                                  2 /* nal header */, ParseSEICallback, p_dec );
            break;
    }

    if(!p_ret && p_sys->frame.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;

    if ( (i_nal_type >= HEVC_NAL_VPS        && i_nal_type <= HEVC_NAL_AUD) ||
          i_nal_type == HEVC_NAL_PREF_SEI ||
         (i_nal_type >= HEVC_NAL_RSV_NVCL41 && i_nal_type <= HEVC_NAL_RSV_NVCL44) ||
         (i_nal_type >= HEVC_NAL_UNSPEC48   && i_nal_type <= HEVC_NAL_UNSPEC55) )
    {
        p_ret = ParseAUHead(p_dec, i_nal_type, p_nalb);
    }
    else
    {
        p_ret = ParseAUTail(p_dec, i_nal_type, p_nalb);
    }

    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 */
    if(p_sys->p_active_sps)
    {
        uint8_t i_num_clock_ts = hevc_get_num_clock_ts(p_sys->p_active_sps,
                                                       p_sys->p_timing);
        const mtime_t i_start = date_Get(&p_sys->dts);
        if( i_start != VLC_TS_INVALID )
        {
            date_Increment(&p_sys->dts, i_num_clock_ts);
            p_output->i_length = date_Get(&p_sys->dts) - i_start;
        }
        p_sys->pts = VLC_TS_INVALID;
    }
    hevc_release_sei_pic_timing(p_sys->p_timing);
    p_sys->p_timing = NULL;
}

/*****************************************************************************
 * 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;

    if(p_sys->b_need_ts)
    {
        if(p_frag->i_dts > VLC_TS_INVALID)
            date_Set(&p_sys->dts, p_frag->i_dts);
        p_sys->pts = p_frag->i_pts;
        if(date_Get( &p_sys->dts ) != VLC_TS_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;
    }

    if(p_frag->p_buffer[4] & 0x80)
    {
        msg_Warn(p_dec,"Forbidden zero bit not null, corrupted NAL");
        block_Release(p_frag);
        return GatherAndValidateChain(OutputQueues(p_sys, false)); /* will drop */
    }

    /* Get NALU type */
    const mtime_t dts = p_frag->i_dts, pts = p_frag->i_pts;
    block_t * p_output = NULL;
    uint8_t i_nal_type = hevc_getNALType(&p_frag->p_buffer[4]);

    if (i_nal_type < HEVC_NAL_VPS)
    {
        /* 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 VPS/SPS/PPS");
    }
    else
    {
        p_output = ParseNonVCL(p_dec, i_nal_type, p_frag);
    }

    p_output = GatherAndValidateChain(p_output);
    if(p_output)
    {
        SetOutputBlockProperties( p_dec, p_output );
        if (dts > VLC_TS_INVALID)
            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;

    /* 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 );
    if( p_block )
        cc_storage_commit( p_sys->p_ccs, 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;

    if( p_sys->frame.p_chain &&
        p_sys->b_init_sequence_complete )
    {
        p_out = OutputQueues(p_sys, true);
        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_PIC_TIMING:
        {
            if( p_sys->p_active_sps )
            {
                hevc_release_sei_pic_timing( p_sys->p_timing );
                p_sys->p_timing = hevc_decode_sei_pic_timing( p_sei_data->p_bs,
                                                              p_sys->p_active_sps );
            }
        } break;
        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;
}
