/*****************************************************************************
 * mpeg4audio.c: parse and packetize an MPEG 4 audio stream
 *****************************************************************************
 * Copyright (C) 2001, 2002, 2006 VLC authors and VideoLAN
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *          Gildas Bazin <gbazin@netcourrier.com>
 *
 * 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 "mpeg4audio.h"

#include <assert.h>

/* AAC Config in ES:
 *
 * AudioObjectType          5 bits
 * samplingFrequencyIndex   4 bits
 * if (samplingFrequencyIndex == 0xF)
 *  samplingFrequency   24 bits
 * channelConfiguration     4 bits
 * GA_SpecificConfig
 *  FrameLengthFlag         1 bit 1024 or 960
 *  DependsOnCoreCoder      1 bit (always 0)
 *  ExtensionFlag           1 bit (always 0)
 */

/*****************************************************************************
 * decoder_sys_t : decoder descriptor
 *****************************************************************************/

typedef struct
{
    /*
     * Input properties
     */
    enum vlc_packetizer_state i_state;
    int i_type;

    block_bytestream_t bytestream;

    /*
     * Common properties
     */
    date_t  end_date;
    vlc_tick_t i_pts;
    bool b_discontuinity;

    int i_frame_size;
    unsigned int i_channels;
    unsigned int i_rate, i_frame_length, i_header_size;
    int i_aac_profile;

    int i_input_rate;

    /* LOAS */
    bool b_latm_cfg;
    MPEG4_streammux_config_t latm;

    int i_warnings;
} decoder_sys_t;

enum
{
    WARN_CRC_UNSUPPORTED = 1
};

#define WARN_ONCE(warn, msg) do{\
        decoder_sys_t *p_sys = p_dec->p_sys;\
        if( (p_sys->i_warnings & warn) == 0 )\
        {\
            p_sys->i_warnings |= warn;\
            msg_Warn( p_dec, msg );\
        }\
    } while(0)

enum {
    TYPE_UNKNOWN, /* AAC samples with[out] headers */
    TYPE_UNKNOWN_NONRAW, /* [un]packetized ADTS or LOAS */
    TYPE_RAW,    /* RAW AAC frames */
    TYPE_ADTS,
    TYPE_LOAS
};

static int ChannelConfigurationToVLC(uint8_t i_channel)
{
    if (i_channel == 7)
        return 8; // 7.1
    if (i_channel >= 8)
        return -1;
    return i_channel;
}

static int AOTtoAACProfile(uint8_t i_object_type)
{
    switch(i_object_type)
    {
        case MPEG4_AOT_AAC_MAIN:
        case MPEG4_AOT_AAC_LC:
        case MPEG4_AOT_AAC_SSR:
        case MPEG4_AOT_AAC_LTP:
        case MPEG4_AOT_AAC_SBR:
        case MPEG4_AOT_AAC_SC:
        case MPEG4_AOT_ER_AAC_LD:
        case MPEG4_AOT_AAC_PS:
        case MPEG4_AOT_ER_AAC_ELD:
            {
            static_assert(MPEG4_AOT_AAC_MAIN == AAC_PROFILE_MAIN + 1,
                          "invalid profile to object mapping");
            return i_object_type - 1;
            }
        default:
            return -1;
    }
}

#define ADTS_HEADER_SIZE 9
#define LOAS_HEADER_SIZE 3

/****************************************************************************
 * Local prototypes
 ****************************************************************************/
static int  OpenPacketizer(vlc_object_t *);
static void ClosePacketizer(vlc_object_t *);

static block_t *Packetize    (decoder_t *, block_t **);
static void     Flush( decoder_t * );

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
vlc_module_begin ()
    set_subcategory(SUBCAT_SOUT_PACKETIZER)
    set_description(N_("MPEG4 audio packetizer"))
    set_capability("audio packetizer", 50)
    set_callbacks(OpenPacketizer, ClosePacketizer)
vlc_module_end ()

/*****************************************************************************
 * OpenPacketizer: probe the packetizer and return score
 *****************************************************************************/
static int OpenPacketizer(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_MP4A)
        return VLC_EGENERIC;

    /* Allocate the memory needed to store the decoder's structure */
    if ((p_dec->p_sys = p_sys = (decoder_sys_t *)malloc(sizeof(decoder_sys_t))) == NULL)
        return VLC_ENOMEM;

    /* Misc init */
    p_sys->i_state = STATE_NOSYNC;
    p_sys->b_discontuinity = false;
    block_BytestreamInit(&p_sys->bytestream);
    p_sys->i_aac_profile = -1;
    p_sys->b_latm_cfg = false;
    p_sys->i_warnings = 0;
    p_sys->i_channels = 0;
    p_sys->i_rate = 0;
    p_sys->i_frame_length = 0;

    /* Set output properties */
    p_dec->fmt_out.i_codec = VLC_CODEC_MP4A;

    msg_Dbg(p_dec, "running MPEG4 audio packetizer");

    /*
     * We need to handle 3 cases.
     * Case 1 : RAW AAC samples without sync header
     *          The demuxer shouldn't need packetizer, see next case.
     * Case 2 : AAC samples with ADTS or LOAS/LATM header
     *          Some mux (avi) can't distinguish the both
     *          cases above, and then forwards to packetizer
     *          which should check for header and rewire to case below
     * Case 3 : Non packetized ADTS or LOAS/LATM
     *          The demuxer needs to set original_codec for hardwiring
     */

    switch (p_dec->fmt_in->i_original_fourcc)
    {
        case VLC_FOURCC('L','A','T','M'):
            p_sys->i_type = TYPE_LOAS;
            msg_Dbg(p_dec, "LOAS/LATM Mode");
            break;

        case VLC_FOURCC('A','D','T','S'):
            p_sys->i_type = TYPE_ADTS;
            msg_Dbg(p_dec, "ADTS Mode");
            break;

        case VLC_FOURCC('H','E','A','D'):
            p_sys->i_type = TYPE_UNKNOWN_NONRAW;
            break;

        default:
            p_sys->i_type = TYPE_UNKNOWN;
            break;
    }

    /* Some mux (avi) do send RAW AAC without extradata,
       and LATM can be sent with out-of-band audioconfig,
       (avformat sets m4a extradata in both cases)
       so we can't rely on extradata to guess multiplexing */
    p_dec->fmt_out.audio.i_rate = p_dec->fmt_in->audio.i_rate;

    if(p_dec->fmt_in->i_extra)
    {
        MPEG4_asc_t asc;
        bs_t s;
        bs_init(&s, p_dec->fmt_in->p_extra, p_dec->fmt_in->i_extra);
        if(MPEG4_read_AudioSpecificConfig(&s, &asc, true) == VLC_SUCCESS)
        {
            p_dec->fmt_out.audio.i_rate = asc.i_samplerate;
            p_dec->fmt_out.audio.i_frame_length = asc.i_frame_length;
            p_dec->fmt_out.audio.i_channels =
                    ChannelConfigurationToVLC(asc.i_channel_configuration);
            if(p_dec->fmt_out.i_profile != -1)
                p_dec->fmt_out.i_profile = AOTtoAACProfile(asc.i_object_type);

            msg_Dbg(p_dec, "%sAAC%s %dHz %d samples/frame",
                    (asc.i_sbr) ? "HE-" : "",
                    (asc.i_ps) ? "v2" : "",
                    (asc.i_sbr) ? p_dec->fmt_out.audio.i_rate << 1
                                : p_dec->fmt_out.audio.i_rate,
                    p_dec->fmt_out.audio.i_frame_length);
        }

        p_dec->fmt_out.p_extra = malloc(p_dec->fmt_in->i_extra);
        if (!p_dec->fmt_out.p_extra)
            return VLC_ENOMEM;
        p_dec->fmt_out.i_extra = p_dec->fmt_in->i_extra;
        memcpy(p_dec->fmt_out.p_extra, p_dec->fmt_in->p_extra,
                p_dec->fmt_in->i_extra);
    }
    /* else() We will try to create a AAC Config from adts/loas */

    date_Init(&p_sys->end_date, p_dec->fmt_out.audio.i_rate ?
                                p_dec->fmt_out.audio.i_rate : 48000, 1);

    /* Set callbacks */
    p_dec->pf_packetize = Packetize;
    p_dec->pf_flush = Flush;
    p_dec->pf_get_cc = NULL;

    return VLC_SUCCESS;
}

/*****************************************************************************
 * ClosePacketizer: clean up the packetizer
 *****************************************************************************/
static void ClosePacketizer(vlc_object_t *p_this)
{
    decoder_t *p_dec = (decoder_t *)p_this;
    decoder_sys_t *p_sys = p_dec->p_sys;

    block_BytestreamRelease(&p_sys->bytestream);
    free(p_sys);
}

/****************************************************************************
 * ForwardRawBlock:
 ****************************************************************************
 * This function must be fed with complete frames.
 ****************************************************************************/
static block_t *ForwardRawBlock(decoder_t *p_dec, block_t **pp_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_block;

    if (!pp_block || !*pp_block)
        return NULL;

    p_block = *pp_block;
    *pp_block = NULL; /* Don't reuse this block */

    vlc_tick_t i_diff = 0;
    if (p_block->i_pts != VLC_TICK_INVALID &&
        p_block->i_pts != date_Get(&p_sys->end_date))
    {
        if(date_Get(&p_sys->end_date) != VLC_TICK_INVALID)
            i_diff = llabs( date_Get(&p_sys->end_date) - p_block->i_pts );
        date_Set(&p_sys->end_date, p_block->i_pts);
    }

    p_block->i_pts = p_block->i_dts = date_Get(&p_sys->end_date);

    /* Might not be known due to missing extradata,
       will be set to block pts above */
    if(p_dec->fmt_out.audio.i_frame_length && p_block->i_pts != VLC_TICK_INVALID)
    {
        p_block->i_length = date_Increment(&p_sys->end_date,
            p_dec->fmt_out.audio.i_frame_length) - p_block->i_pts;

        if( i_diff > p_block->i_length )
            p_sys->b_discontuinity = true;
    }

    return p_block;
}

/****************************************************************************
 * ADTS helpers
 ****************************************************************************/
static int ADTSSyncInfo(decoder_t * p_dec, const uint8_t * p_buf,
                         unsigned int * pi_channels,
                         unsigned int * pi_sample_rate,
                         unsigned int * pi_frame_length,
                         unsigned int * pi_header_size)
{
    int i_profile, i_sample_rate_idx, i_frame_size;
    bool b_crc;

    /* Fixed header between frames */
    //int i_id = ((p_buf[1] >> 3) & 0x01) ? 2 : 4; /* MPEG-2 or 4 */
    b_crc = !(p_buf[1] & 0x01);
    i_profile = p_buf[2] >> 6;
    i_sample_rate_idx = (p_buf[2] >> 2) & 0x0f;
    *pi_sample_rate = pi_sample_rates[i_sample_rate_idx];
    //private_bit = (p_buf[2] >> 1) & 0x01;
    *pi_channels = ((p_buf[2] & 0x01) << 2) | ((p_buf[3] >> 6) & 0x03);
    if (*pi_channels == 0) /* workaround broken streams */
        *pi_channels = 2;
    //original_copy = (p_buf[3] >> 5) & 0x01;
    //home = (p_buf[3] >> 4) & 0x01;

    /* Variable header */
    //copyright_id_bit = (p_buf[3] >> 3) & 0x01;
    //copyright_id_start = (p_buf[3] >> 2) & 0x01;
    i_frame_size = ((p_buf[3] & 0x03) << 11) | (p_buf[4] << 3) |
                   ((p_buf[5] >> 5) /*& 0x7*/);
    //uint16_t buffer_fullness = ((p_buf[5] & 0x1f) << 6) | (p_buf[6] >> 2);
    unsigned short i_raw_blocks_in_frame = p_buf[6] & 0x03;

    if (!*pi_sample_rate || !i_frame_size) {
        msg_Warn(p_dec, "Invalid ADTS header");
        return 0;
    }

    *pi_frame_length = 1024;

    if (i_raw_blocks_in_frame == 0) {
        if (b_crc) {
            WARN_ONCE(WARN_CRC_UNSUPPORTED, "ADTS CRC not supported");
            //uint16_t crc = (p_buf[7] << 8) | p_buf[8];
        }
    } else {
        msg_Err(p_dec, "Multiple blocks per frame in ADTS not supported");
        return 0;
#if 0
        int i;
        const uint8_t *p_pos = p_buf + 7;
        uint16_t crc_block;
        uint16_t i_block_pos[3];
        if (b_crc) {
            for (i = 0 ; i < i_raw_blocks_in_frame ; i++) {
                /* the 1st block's position is known ... */
                i_block_pos[i] = (*p_pos << 8) | *(p_pos+1);
                p_pos += 2;
            }
            crc_block = (*p_pos << 8) | *(p_pos+1);
            p_pos += 2;
        }
        for (i = 0 ; i <= i_raw_blocks_in_frame ; i++) {
            //read 1 block
            if (b_crc) {
                WARN_ONCE(WARN_CRC_UNSUPPORTED, "ADTS CRC not supported");
                //uint16_t crc = (*p_pos << 8) | *(p_pos+1);
                //p_pos += 2;
            }
        }
#endif
    }


    /* Build the decoder specific info header */
    if (!p_dec->fmt_out.i_extra) {
        p_dec->fmt_out.p_extra = malloc(2);
        if (!p_dec->fmt_out.p_extra)
            return 0;
        p_dec->fmt_out.i_extra = 2;
        ((uint8_t *)p_dec->fmt_out.p_extra)[0] =
            (i_profile + 1) << 3 | (i_sample_rate_idx >> 1);
        ((uint8_t *)p_dec->fmt_out.p_extra)[1] =
            ((i_sample_rate_idx & 0x01) << 7) | (*pi_channels <<3);
    }

    /* ADTS header length */
    *pi_header_size = b_crc ? 9 : 7;

    return i_frame_size - *pi_header_size;
}

/****************************************************************************
 * LOAS helpers
 ****************************************************************************/
static int LOASSyncInfo(uint8_t p_header[LOAS_HEADER_SIZE], unsigned int *pi_header_size)
{
    *pi_header_size = 3;
    return ((p_header[1] & 0x1f) << 8) + p_header[2];
}

static int LOASParse(decoder_t *p_dec, uint8_t *p_buffer, int i_buffer)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    bs_t s;
    int i_accumulated = 0;

    bs_init(&s, p_buffer, i_buffer);

    /* Read the stream mux configuration if present */
    if (!bs_read1(&s) && !MPEG4_parse_StreamMuxConfig(&s, &p_sys->latm) &&
            p_sys->latm.i_streams > 0) {
        const MPEG4_audio_stream_t *st = &p_sys->latm.stream[0];

        if(st->cfg.i_samplerate == 0 || st->cfg.i_frame_length == 0 ||
           ChannelConfigurationToVLC(st->cfg.i_channel_configuration) == 0)
            return 0;

        p_sys->i_channels = ChannelConfigurationToVLC(st->cfg.i_channel_configuration);
        p_sys->i_rate = st->cfg.i_samplerate;
        p_sys->i_frame_length = st->cfg.i_frame_length;
        p_sys->i_aac_profile = AOTtoAACProfile(st->cfg.i_object_type);

        if (p_sys->i_channels && p_sys->i_rate && p_sys->i_frame_length > 0)
        {
            if(p_dec->fmt_out.i_extra != st->i_extra ||
               (p_dec->fmt_out.i_extra > 0 &&
                memcmp(p_dec->fmt_out.p_extra, st->extra, st->i_extra)) )
            {
                if(p_dec->fmt_out.i_extra)
                    free(p_dec->fmt_out.p_extra);
                p_dec->fmt_out.p_extra = st->i_extra ? malloc(st->i_extra) : NULL;
                if(p_dec->fmt_out.p_extra)
                {
                    p_dec->fmt_out.i_extra = st->i_extra;
                    memcpy(p_dec->fmt_out.p_extra, st->extra, st->i_extra);
                    p_sys->b_latm_cfg = true;
                }
                else
                {
                    p_dec->fmt_out.i_extra = 0;
                    p_sys->b_latm_cfg = false;
                }
            }
        }
    }

    /* Wait for the configuration */
    if (!p_sys->b_latm_cfg)
    {
        /* WAVE_FORMAT_MPEG_LOAS, configuration provided as AAC header :/ */
        if( p_dec->fmt_in->i_extra > 0 &&
            p_sys->i_channels && p_sys->i_rate && p_sys->i_frame_length )
        {
            p_sys->b_latm_cfg = true;
        }
        else return 0;
    }

    if(bs_eof(&s) && i_buffer)
        goto truncated;

    /* FIXME do we need to split the subframe into independent packet ? */
    if (p_sys->latm.numSubFrames != 0)
        msg_Err(p_dec, "latm sub frames not yet supported, please send a sample");

    for (uint8_t i_sub = 0; i_sub <= p_sys->latm.numSubFrames; i_sub++) {
        unsigned pi_payload[MPEG4_STREAMMUX_MAX_PROGRAM][MPEG4_STREAMMUX_MAX_LAYER];
        if (p_sys->latm.allStreamsSameTimeFraming) {
            /* Payload length */
            for (uint8_t i_program = 0; i_program <= p_sys->latm.numProgram; i_program++) {
                for (uint8_t i_layer = 0; i_layer <= p_sys->latm.p_numLayer[i_program]; i_layer++) {
                    const MPEG4_audio_stream_t *st = &p_sys->latm.stream[p_sys->latm.pi_stream[i_program][i_layer]];
                    if (st->i_frame_length_type == 0) {
                        unsigned i_payload = 0;
                        for (;;) {
                            uint8_t i_tmp = bs_read(&s, 8);
                            i_payload += i_tmp;
                            if (i_tmp != 255)
                                break;
                        }
                        pi_payload[i_program][i_layer] = i_payload;
                    } else if (st->i_frame_length_type == 1) {
                        pi_payload[i_program][i_layer] = st->i_frame_length / 8; /* XXX not correct */
                    } else if ((st->i_frame_length_type == 3) ||
                             (st->i_frame_length_type == 5) ||
                             (st->i_frame_length_type == 7)) {
                        bs_skip(&s, 2); // muxSlotLengthCoded
                        pi_payload[i_program][i_layer] = 0; /* TODO */
                    } else {
                        pi_payload[i_program][i_layer] = 0; /* TODO */
                    }
                }
            }

            /* Payload Data */
            for (uint8_t i_program = 0; i_program <= p_sys->latm.numProgram; i_program++) {
                for (uint8_t i_layer = 0; i_layer <= p_sys->latm.p_numLayer[i_program]; i_layer++) {
                    /* XXX we only extract 1 stream */
                    if (i_program != 0 || i_layer != 0)
                        break;

                    if (pi_payload[i_program][i_layer] <= 0)
                        continue;

                    /* FIXME that's slow (and a bit ugly to write in place) */
                    for (unsigned i = 0; i < pi_payload[i_program][i_layer]; i++) {
                        if (i_accumulated >= i_buffer)
                            return 0;
                        p_buffer[i_accumulated++] = bs_read(&s, 8);
                        if(bs_error(&s))
                            goto truncated;
                    }
                }
            }
        } else {
            const int i_chunks = bs_read(&s, 4);
#if 0
            int pi_program[16];
            int pi_layer[16];
#endif

            msg_Err(p_dec, "latm without same time frameing not yet supported, please send a sample");

            for (int i_chunk = 0; i_chunk < i_chunks; i_chunk++) {
                const int streamIndex = bs_read(&s, 4);
                const MPEG4_audio_stream_t *st = &p_sys->latm.stream[streamIndex];
                const int i_program = st->i_program;
                const int i_layer = st->i_layer;

#if 0
                pi_program[i_chunk] = i_program;
                pi_layer[i_chunk] = i_layer;
#endif

                if (st->i_frame_length_type == 0) {
                    int i_payload = 0;
                    for (;;) {
                        int i_tmp = bs_read(&s, 8);
                        i_payload += i_tmp;
                        if (i_tmp != 255)
                            break;
                    }
                    pi_payload[i_program][i_layer] = i_payload;
                    bs_skip(&s, 1); // auEndFlag
                } else if (st->i_frame_length_type == 1) {
                    pi_payload[i_program][i_layer] = st->i_frame_length / 8; /* XXX not correct */
                } else if ((st->i_frame_length_type == 3) ||
                         (st->i_frame_length_type == 5) ||
                         (st->i_frame_length_type == 7)) {
                    bs_read(&s, 2); // muxSlotLengthCoded
                }
            }
#if 0
            for (int i_chunk = 0; i_chunk < i_chunks; i_chunk++) {
                //const int i_program = pi_program[i_chunk];
                //const int i_layer = pi_layer[i_chunk];

                /* TODO ? Payload */
            }
#endif
        }
    }


    bs_skip(&s, p_sys->latm.otherDataLenBits);
    bs_align(&s);

    return i_accumulated;

truncated:
    msg_Warn(p_dec,"Truncated LOAS packet. Wrong format ?");
    return 0;
}

/*****************************************************************************
 *
 *****************************************************************************/
static void SetupOutput(decoder_t *p_dec, block_t *p_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    if (p_dec->fmt_out.audio.i_rate != p_sys->i_rate && p_sys->i_rate > 0)
    {
        msg_Info(p_dec, "AAC channels: %d samplerate: %d",
                  p_sys->i_channels, p_sys->i_rate);
        date_Change(&p_sys->end_date, p_sys->i_rate, 1);
    }

    p_dec->fmt_out.audio.i_rate     = p_sys->i_rate;
    p_dec->fmt_out.audio.i_channels = p_sys->i_channels;
    p_dec->fmt_out.audio.i_bytes_per_frame = p_sys->i_frame_size;
    p_dec->fmt_out.audio.i_frame_length = p_sys->i_frame_length;
    /* Will reload extradata on change */
    p_dec->fmt_out.i_profile = p_sys->i_aac_profile;

#if 0
    p_dec->fmt_out.audio.i_physical_channels = p_sys->i_channels_conf;
#endif

    p_block->i_pts = p_block->i_dts = date_Get(&p_sys->end_date);

    p_block->i_length =
        date_Increment(&p_sys->end_date, p_sys->i_frame_length) - p_block->i_pts;
}

/*****************************************************************************
 * FlushStreamBlock:
 *****************************************************************************/
static void Flush(decoder_t *p_dec)
{
    decoder_sys_t *p_sys = p_dec->p_sys;

    p_sys->i_state = STATE_NOSYNC;
    block_BytestreamEmpty(&p_sys->bytestream);
    date_Set(&p_sys->end_date, VLC_TICK_INVALID);
    p_sys->b_discontuinity = true;
}

static inline bool HasADTSHeader( const uint8_t *p_header )
{
    return p_header[0] == 0xff && (p_header[1] & 0xf6) == 0xf0;
}

static inline bool HasLoasHeader( const uint8_t *p_header )
{
    return p_header[0] == 0x56 && (p_header[1] & 0xe0) == 0xe0;
}

/****************************************************************************
 * PacketizeStreamBlock: ADTS/LOAS packetizer
 ****************************************************************************/
static block_t *PacketizeStreamBlock(decoder_t *p_dec, block_t **pp_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    uint8_t p_header[ADTS_HEADER_SIZE + LOAS_HEADER_SIZE];
    block_t *p_out_buffer;
    uint8_t *p_buf;

    block_t *p_block = pp_block ? *pp_block : NULL;

    if(p_block)
    {
        block_BytestreamPush(&p_sys->bytestream, p_block);
        *pp_block = NULL;
    }

    for (;;) switch(p_sys->i_state) {
    case STATE_NOSYNC:
        while (block_PeekBytes(&p_sys->bytestream, p_header, 2) == VLC_SUCCESS) {
            /* Look for sync word - should be 0xfff(adts) or 0x2b7(loas) */
            if ((p_sys->i_type == TYPE_ADTS || p_sys->i_type == TYPE_UNKNOWN_NONRAW) &&
                HasADTSHeader( p_header ) )
            {
                if (p_sys->i_type != TYPE_ADTS)
                    msg_Dbg(p_dec, "detected ADTS format");

                p_sys->i_state = STATE_SYNC;
                p_sys->i_type = TYPE_ADTS;
                break;
            }
            else if ((p_sys->i_type == TYPE_LOAS || p_sys->i_type == TYPE_UNKNOWN_NONRAW) &&
                      HasLoasHeader( p_header ) )
            {
                if (p_sys->i_type != TYPE_LOAS)
                    msg_Dbg(p_dec, "detected LOAS format");

                p_sys->i_state = STATE_SYNC;
                p_sys->i_type = TYPE_LOAS;
                break;
            }
            (void) block_SkipByte(&p_sys->bytestream);
        }
        if (p_sys->i_state != STATE_SYNC) {
            block_BytestreamFlush(&p_sys->bytestream);

            /* Need more data */
            return NULL;
        }
        /* fallthrough */

    case STATE_SYNC:
        /* New frame, set the Presentation Time Stamp */
        p_sys->i_pts = p_sys->bytestream.p_block->i_pts;
        if (p_sys->i_pts != VLC_TICK_INVALID &&
            p_sys->i_pts != date_Get(&p_sys->end_date))
            date_Set(&p_sys->end_date, p_sys->i_pts);
        p_sys->i_state = STATE_HEADER;
        break;

    case STATE_HEADER:
        if (p_sys->i_type == TYPE_ADTS) {
            /* Get ADTS frame header (ADTS_HEADER_SIZE bytes) */
            if (block_PeekBytes(&p_sys->bytestream, p_header,
                        ADTS_HEADER_SIZE) != VLC_SUCCESS)
                return NULL; /* Need more data */

            /* Check if frame is valid and get frame info */
            p_sys->i_frame_size = ADTSSyncInfo(p_dec, p_header,
                    &p_sys->i_channels,
                    &p_sys->i_rate,
                    &p_sys->i_frame_length,
                    &p_sys->i_header_size);
        } else {
            assert(p_sys->i_type == TYPE_LOAS);
            /* Get LOAS frame header (LOAS_HEADER_SIZE bytes) */
            if (block_PeekBytes(&p_sys->bytestream, p_header,
                        LOAS_HEADER_SIZE) != VLC_SUCCESS)
                return NULL; /* Need more data */

            /* Check if frame is valid and get frame info */
            p_sys->i_frame_size = LOASSyncInfo(p_header, &p_sys->i_header_size);
        }

        if (p_sys->i_frame_size <= 0) {
            msg_Dbg(p_dec, "emulated sync word");
            block_SkipByte(&p_sys->bytestream);
            p_sys->i_state = STATE_NOSYNC;
            break;
        }

        p_sys->i_state = STATE_NEXT_SYNC;
        /* fallthrough */

    case STATE_NEXT_SYNC:
        if (p_sys->bytestream.p_block == NULL) {
            p_sys->i_state = STATE_NOSYNC;
            block_BytestreamFlush(&p_sys->bytestream);
            return NULL;
        }

        /* Check if next expected frame contains the sync word */
        if (block_PeekOffsetBytes(&p_sys->bytestream, p_sys->i_frame_size
                    + p_sys->i_header_size, p_header, 2) != VLC_SUCCESS)
        {
            if(p_block == NULL) /* drain */
            {
                p_sys->i_state = STATE_SEND_DATA;
                break;
            }
            return NULL; /* Need more data */
        }

        assert((p_sys->i_type == TYPE_ADTS) || (p_sys->i_type == TYPE_LOAS));
        if ( (p_sys->i_type == TYPE_ADTS && !HasADTSHeader( p_header )) ||
             (p_sys->i_type == TYPE_LOAS && !HasLoasHeader( p_header )) )
        {
            /* Check spacial padding case. Failing if need more bytes is ok since
               that should have been sent as a whole block */
            if( block_PeekOffsetBytes(&p_sys->bytestream,
                                      p_sys->i_frame_size + p_sys->i_header_size,
                                      p_header, 3) == VLC_SUCCESS &&
                p_header[0] == 0x00 &&
               ((p_sys->i_type == TYPE_ADTS && HasADTSHeader( &p_header[1] )) ||
                (p_sys->i_type == TYPE_LOAS && !HasLoasHeader( &p_header[1] ))))
            {
                p_sys->i_state = STATE_SEND_DATA;
            }
            else
            {
                msg_Dbg(p_dec, "emulated sync word (no sync on following frame)"
                               " 0x%"PRIx8" 0x%"PRIx8, p_header[0], p_header[1] );
                p_sys->i_state = STATE_NOSYNC;
                block_SkipByte(&p_sys->bytestream);
            }
            break;
        }

        p_sys->i_state = STATE_SEND_DATA;
        break;

    case STATE_GET_DATA:
        /* Make sure we have enough data.
         * (Not useful if we went through NEXT_SYNC) */
        if (block_WaitBytes(&p_sys->bytestream, p_sys->i_frame_size +
                    p_sys->i_header_size) != VLC_SUCCESS)
            return NULL; /* Need more data */
        p_sys->i_state = STATE_SEND_DATA;
        /* fallthrough */

    case STATE_SEND_DATA:
        /* When we reach this point we already know we have enough
         * data available. */

        if (unlikely( block_BytestreamRemaining(&p_sys->bytestream) <
                      p_sys->i_header_size + p_sys->i_frame_size ))
            return NULL; // need more data

        p_out_buffer = block_Alloc(p_sys->i_frame_size);
        if (!p_out_buffer) {
            return NULL;
        }
        p_buf = p_out_buffer->p_buffer;

        /* Skip the ADTS/LOAS header */
        (void) block_SkipBytes(&p_sys->bytestream, p_sys->i_header_size);

        /* Copy the whole frame into the buffer */
        (void) block_GetBytes(&p_sys->bytestream, p_buf, p_sys->i_frame_size);
        if (p_sys->i_type != TYPE_ADTS) { /* parse/extract the whole frame */
            assert(p_sys->i_type == TYPE_LOAS);
            p_out_buffer->i_buffer = LOASParse(p_dec, p_buf, p_sys->i_frame_size);
            if (p_out_buffer->i_buffer <= 0)
            {
                if (!p_sys->b_latm_cfg)
                    msg_Warn(p_dec, "waiting for header");

                block_Release(p_out_buffer);
                p_out_buffer = NULL;
                p_sys->i_state = STATE_NOSYNC;
                break;
            }
        }
        SetupOutput(p_dec, p_out_buffer);
        /* Make sure we don't reuse the same pts twice */
        if (p_sys->i_pts == p_sys->bytestream.p_block->i_pts)
            p_sys->i_pts = p_sys->bytestream.p_block->i_pts = VLC_TICK_INVALID;

        /* So p_block doesn't get re-added several times */
        if( pp_block )
            *pp_block = block_BytestreamPop(&p_sys->bytestream);

        p_sys->i_state = STATE_NOSYNC;

        return p_out_buffer;

    case STATE_CUSTOM_FIRST:
        break; // do nothing
    }

    return NULL;
}

/****************************************************************************
 * Packetize: just forwards raw blocks, or packetizes LOAS/ADTS
 *            and strips headers
 ****************************************************************************/
static block_t *Packetize(decoder_t *p_dec, block_t **pp_block)
{
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_block = pp_block ? *pp_block : NULL;

    if(p_block)
    {
        if (p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED))
        {
            if(p_sys->i_type == TYPE_ADTS || p_sys->i_type == TYPE_LOAS)
            {
                /* First always drain complete blocks before discontinuity */
                block_t *p_drain = PacketizeStreamBlock(p_dec, NULL);
                if(p_drain)
                    return p_drain;
            }

            Flush(p_dec);

            if (p_block->i_flags & BLOCK_FLAG_CORRUPTED)
            {
                block_Release(p_block);
                return NULL;
            }
        }

        if ( p_block->i_pts == VLC_TICK_INVALID &&
             date_Get(&p_sys->end_date) == VLC_TICK_INVALID )
        {
            /* We've just started the stream, wait for the first PTS. */
            block_Release(p_block);
            return NULL;
        }
    }

    if(p_block && p_sys->i_type == TYPE_UNKNOWN)
    {
        p_sys->i_type = TYPE_RAW;
        if(p_block->i_buffer > 1)
        {
            if(p_block->p_buffer[0] == 0xff && (p_block->p_buffer[1] & 0xf6) == 0xf0)
            {
                p_sys->i_type = TYPE_ADTS;
            }
            else if(p_block->p_buffer[0] == 0x56 && (p_block->p_buffer[1] & 0xe0) == 0xe0)
            {
                p_sys->i_type = TYPE_LOAS;
            }
        }
    }

    if(p_sys->i_type == TYPE_RAW)
        p_block = ForwardRawBlock(p_dec, pp_block);
    else
        p_block = PacketizeStreamBlock(p_dec, pp_block);

    if(p_block && p_sys->b_discontuinity)
    {
        p_block->i_flags |= BLOCK_FLAG_DISCONTINUITY;
        p_sys->b_discontuinity = false;
    }

    return p_block;
}
