/*****************************************************************************
 * avi.c : AVI file Stream input module for vlc
 *****************************************************************************
 * Copyright (C) 2001 VideoLAN
 * $Id: avi.c,v 1.22 2002/06/07 14:30:40 sam Exp $
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */
#include <string.h>                                              /* strdup() */
#include <errno.h>
#include <sys/types.h>

#include <vlc/vlc.h>
#include <vlc/input.h>

#include "video.h"

/*****************************************************************************
 * Constants
 *****************************************************************************/

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void input_getfunctions( function_list_t * );
static int  AVIDemux         ( input_thread_t * );
static int  AVIInit          ( input_thread_t * );
static void AVIEnd           ( input_thread_t * );

/*****************************************************************************
 * Build configuration tree.
 *****************************************************************************/
MODULE_CONFIG_START
MODULE_CONFIG_STOP

MODULE_INIT_START
    SET_DESCRIPTION( "RIFF-AVI Stream input" )
    ADD_CAPABILITY( DEMUX, 150 )
MODULE_INIT_STOP

MODULE_ACTIVATE_START
    input_getfunctions( &p_module->p_functions->demux );
MODULE_ACTIVATE_STOP

MODULE_DEACTIVATE_START
MODULE_DEACTIVATE_STOP

/*****************************************************************************
 * Definition of structures and libraries for this plugins 
 *****************************************************************************/
#include "libLE.c"
#include "libioRIFF.c"
#include "avi.h"

/*****************************************************************************
 * Functions exported as capabilities. They are declared as static so that
 * we don't pollute the namespace too much.
 *****************************************************************************/
static void input_getfunctions( function_list_t * p_function_list )
{
#define input p_function_list->functions.demux
    input.pf_init             = AVIInit;
    input.pf_end              = AVIEnd;
    input.pf_demux            = AVIDemux;
    input.pf_rewind           = NULL;
#undef input
}

/********************************************************************/


static void __AVIFreeDemuxData( input_thread_t *p_input )
{
    int i;
    demux_data_avi_file_t *p_avi_demux;
    p_avi_demux = (demux_data_avi_file_t*)p_input->p_demux_data  ; 
    
    if( p_avi_demux->p_riff != NULL ) 
            RIFF_DeleteChunk( p_input, p_avi_demux->p_riff );
    if( p_avi_demux->p_hdrl != NULL ) 
            RIFF_DeleteChunk( p_input, p_avi_demux->p_hdrl );
    if( p_avi_demux->p_movi != NULL ) 
            RIFF_DeleteChunk( p_input, p_avi_demux->p_movi );
    if( p_avi_demux->p_idx1 != NULL ) 
            RIFF_DeleteChunk( p_input, p_avi_demux->p_idx1 );
    if( p_avi_demux->pp_info != NULL )
    {
        for( i = 0; i < p_avi_demux->i_streams; i++ )
        {
            if( p_avi_demux->pp_info[i] != NULL ) 
            {
                if( p_avi_demux->pp_info[i]->p_index != NULL )
                {
                      free( p_avi_demux->pp_info[i]->p_index );
                }
                free( p_avi_demux->pp_info[i] ); 
            }
        }
         free( p_avi_demux->pp_info );
    }
}

static void __AVI_Parse_avih( MainAVIHeader_t *p_avih, byte_t *p_buff )
{
    p_avih->i_microsecperframe = __GetDoubleWordLittleEndianFromBuff( p_buff );
    p_avih->i_maxbytespersec = __GetDoubleWordLittleEndianFromBuff( p_buff + 4);
    p_avih->i_reserved1 = __GetDoubleWordLittleEndianFromBuff( p_buff + 8);
    p_avih->i_flags = __GetDoubleWordLittleEndianFromBuff( p_buff + 12);
    p_avih->i_totalframes = __GetDoubleWordLittleEndianFromBuff( p_buff + 16);
    p_avih->i_initialframes = __GetDoubleWordLittleEndianFromBuff( p_buff + 20);
    p_avih->i_streams = __GetDoubleWordLittleEndianFromBuff( p_buff + 24);
    p_avih->i_suggestedbuffersize = 
                        __GetDoubleWordLittleEndianFromBuff( p_buff + 28);
    p_avih->i_width = __GetDoubleWordLittleEndianFromBuff( p_buff + 32 );
    p_avih->i_height = __GetDoubleWordLittleEndianFromBuff( p_buff + 36 );
    p_avih->i_scale = __GetDoubleWordLittleEndianFromBuff( p_buff + 40 );
    p_avih->i_rate = __GetDoubleWordLittleEndianFromBuff( p_buff + 44 );
    p_avih->i_start = __GetDoubleWordLittleEndianFromBuff( p_buff + 48);
    p_avih->i_length = __GetDoubleWordLittleEndianFromBuff( p_buff + 52);
}

static void __AVI_Parse_Header( AVIStreamHeader_t *p_strh, byte_t *p_buff )
{
    p_strh->i_type = __GetDoubleWordLittleEndianFromBuff( p_buff );
    p_strh->i_handler = __GetDoubleWordLittleEndianFromBuff( p_buff + 4 );
    p_strh->i_flags = __GetDoubleWordLittleEndianFromBuff( p_buff + 8 );
    p_strh->i_reserved1 = __GetDoubleWordLittleEndianFromBuff( p_buff + 12);
    p_strh->i_initialframes = __GetDoubleWordLittleEndianFromBuff( p_buff + 16);
    p_strh->i_scale = __GetDoubleWordLittleEndianFromBuff( p_buff + 20);
    p_strh->i_rate = __GetDoubleWordLittleEndianFromBuff( p_buff + 24);
    p_strh->i_start = __GetDoubleWordLittleEndianFromBuff( p_buff + 28);
    p_strh->i_length = __GetDoubleWordLittleEndianFromBuff( p_buff + 32);
    p_strh->i_suggestedbuffersize = 
                        __GetDoubleWordLittleEndianFromBuff( p_buff + 36);
    p_strh->i_quality = __GetDoubleWordLittleEndianFromBuff( p_buff + 40);
    p_strh->i_samplesize = __GetDoubleWordLittleEndianFromBuff( p_buff + 44);
}

int avi_ParseBitMapInfoHeader( bitmapinfoheader_t *h, byte_t *p_data )
{
    h->i_size          = __GetDoubleWordLittleEndianFromBuff( p_data );
    h->i_width         = __GetDoubleWordLittleEndianFromBuff( p_data + 4 );
    h->i_height        = __GetDoubleWordLittleEndianFromBuff( p_data + 8 );
    h->i_planes        = __GetWordLittleEndianFromBuff( p_data + 12 );
    h->i_bitcount      = __GetWordLittleEndianFromBuff( p_data + 14 );
    h->i_compression   = __GetDoubleWordLittleEndianFromBuff( p_data + 16 );
    h->i_sizeimage     = __GetDoubleWordLittleEndianFromBuff( p_data + 20 );
    h->i_xpelspermeter = __GetDoubleWordLittleEndianFromBuff( p_data + 24 );
    h->i_ypelspermeter = __GetDoubleWordLittleEndianFromBuff( p_data + 28 );
    h->i_clrused       = __GetDoubleWordLittleEndianFromBuff( p_data + 32 );
    h->i_clrimportant  = __GetDoubleWordLittleEndianFromBuff( p_data + 36 );
    return( 0 );
}

int avi_ParseWaveFormatEx( waveformatex_t *h, byte_t *p_data )
{
    h->i_formattag     = __GetWordLittleEndianFromBuff( p_data );
    h->i_channels      = __GetWordLittleEndianFromBuff( p_data + 2 );
    h->i_samplespersec = __GetDoubleWordLittleEndianFromBuff( p_data + 4 );
    h->i_avgbytespersec= __GetDoubleWordLittleEndianFromBuff( p_data + 8 );
    h->i_blockalign    = __GetWordLittleEndianFromBuff( p_data + 12 );
    h->i_bitspersample = __GetWordLittleEndianFromBuff( p_data + 14 );
    h->i_size          = __GetWordLittleEndianFromBuff( p_data + 16 );
    return( 0 );
}

static inline int __AVIGetESTypeFromTwoCC( u16 i_type )
{
    switch( i_type )
    {
        case( TWOCC_wb ):
            return( AUDIO_ES );
         case( TWOCC_dc ):
         case( TWOCC_db ):
            return( VIDEO_ES );
    }
    return( UNKNOWN_ES );
}


static int __AVI_ParseStreamHeader( u32 i_id, int *i_number, u16 *i_type )
{
    int c1,c2,c3,c4;

    c1 = ( i_id ) & 0xFF;
    c2 = ( i_id >>  8 ) & 0xFF;
    c3 = ( i_id >> 16 ) & 0xFF;
    c4 = ( i_id >> 24 ) & 0xFF;

    if( c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9' )
    {
        return( -1 );
    }
    *i_number = (c1 - '0') * 10 + (c2 - '0' );
    *i_type = ( c4 << 8) + c3;
    return( 0 );
}   

static void __AVI_AddEntryIndex( AVIStreamInfo_t *p_info,
                                 AVIIndexEntry_t *p_index)
{
    AVIIndexEntry_t *p_tmp;
    if( p_info->p_index == NULL )
    {
        p_info->i_idxmax = 16384;
        p_info->i_idxnb = 0;
        p_info->p_index = calloc( p_info->i_idxmax, 
                                  sizeof( AVIIndexEntry_t ) );
        if( p_info->p_index == NULL ) {return;}
    }
    if( p_info->i_idxnb >= p_info->i_idxmax )
    {
        p_info->i_idxmax += 16384;
        p_tmp = realloc( (void*)p_info->p_index,
                           p_info->i_idxmax * 
                           sizeof( AVIIndexEntry_t ) );
        if( p_tmp == NULL ) 
        { 
            p_info->i_idxmax -= 16384;
            return; 
        }
        p_info->p_index = p_tmp;
    }
    /* calculate cumulate length */
    if( p_info->i_idxnb > 0 )
    {
        p_index->i_lengthtotal = p_info->p_index[p_info->i_idxnb-1].i_length +
                p_info->p_index[p_info->i_idxnb-1].i_lengthtotal;
    }
    else
    {
        p_index->i_lengthtotal = 0;
    }

    p_info->p_index[p_info->i_idxnb] = *p_index;
    p_info->i_idxnb++;
}

static void __AVI_GetIndex( input_thread_t *p_input )
{
    AVIIndexEntry_t index;
    byte_t          *p_buff;
    riffchunk_t     *p_idx1;
    int             i_read;
    int             i;
    int             i_number;
    u16             i_type;
    int             i_totalentry = 0;
    demux_data_avi_file_t *p_avi_demux =
                        (demux_data_avi_file_t*)p_input->p_demux_data  ;    

    if( RIFF_FindAndGotoDataChunk( p_input,
                                   p_avi_demux->p_riff, 
                                   &p_idx1, 
                                   FOURCC_idx1)!=0 )
    {
        msg_Warn( p_input, "cannot find index" );
        RIFF_GoToChunk( p_input, p_avi_demux->p_hdrl );        
        return;
    }
    p_avi_demux->p_idx1 = p_idx1;
    msg_Dbg( p_input, "loading index" ); 
    for(;;)
    {
        i_read = __MIN( 16*1024, p_idx1->i_size - i_totalentry *16);
        if( ((i_read = input_Peek( p_input, &p_buff, i_read )) < 16 )
              ||( i_totalentry *16 >= p_idx1->i_size ) )
        {
            msg_Dbg( p_input, "read %d idx entries", i_totalentry );
            return;
        }
        i_read /= 16 ;
        for( i = 0; i < i_read; i++ )
        {
            byte_t  *p_peek = p_buff + i * 16;
            i_totalentry++;
            index.i_id = __GetDoubleWordLittleEndianFromBuff( p_peek );
            index.i_flags = __GetDoubleWordLittleEndianFromBuff( p_peek+4);
            index.i_pos = __GetDoubleWordLittleEndianFromBuff( p_peek+8);
            index.i_length = __GetDoubleWordLittleEndianFromBuff(p_peek+12);
            
            if( (__AVI_ParseStreamHeader( index.i_id, &i_number, &i_type ) == 0)
             &&(i_number <  p_avi_demux->i_streams )
             &&(p_avi_demux->pp_info[i_number]->i_cat == 
                     __AVIGetESTypeFromTwoCC( i_type ))) 
            {
                __AVI_AddEntryIndex( p_avi_demux->pp_info[i_number],
                                     &index );
            }
        }
        __RIFF_SkipBytes( p_input, 16 * i_read );
    }

}
static int __AVI_SeekToChunk( input_thread_t *p_input, AVIStreamInfo_t *p_info )
{
    if( (p_info->p_index )&&(p_info->i_idxposc < p_info->i_idxnb) )
    {
        p_input->pf_seek( p_input, 
                          (off_t)p_info->p_index[p_info->i_idxposc].i_pos);
        input_AccessReinit( p_input );
        return( 0 );
    }
    /* no index can't arrive but ...*/
    msg_Warn( p_input, "cannot seek");
    return( -1 );
}


/* XXX call after get p_movi */
static void __AVI_UpdateIndexOffset( input_thread_t *p_input )
{
    int i_stream;
    int b_start = 1;/* if index pos is based on start of file or not (p_movi) */
    demux_data_avi_file_t *p_avi_demux =
                        (demux_data_avi_file_t*)p_input->p_demux_data;

/* FIXME some work to do :
        * test in the ile if it's true, if not do a RIFF_Find...
*/
#define p_info p_avi_demux->pp_info[i_stream]
    for( i_stream = 0; i_stream < p_avi_demux->i_streams; i_stream++ )
    {
        if( ( p_info->p_index )
           && ( p_info->p_index[0].i_pos < p_avi_demux->p_movi->i_pos + 8 ))
        {
            b_start = 0;
            break;
        }
    }
    if( !b_start )
    {
        for( i_stream = 0; i_stream < p_avi_demux->i_streams; i_stream++ )
        {
            int i;
            if( p_info->p_index )
            {
                for( i = 0; i < p_info->i_idxnb; i++ )
                {
                    p_info->p_index[i].i_pos += p_avi_demux->p_movi->i_pos + 8;
                }
            }
        }
    }
#undef p_info
}

static int __AVI_AudioGetType( u32 i_type )
{
    switch( i_type )
    {
/*        case( WAVE_FORMAT_PCM ):
            return( WAVE_AUDIO_ES ); */
        case( WAVE_FORMAT_AC3 ):
            return( AC3_AUDIO_ES );
        case( WAVE_FORMAT_MPEG):
        case( WAVE_FORMAT_MPEGLAYER3):
            return( MPEG2_AUDIO_ES ); /* 2 for mpeg-2 layer 1 2 ou 3 */
        default:
            return( 0 );
    }
}

static int __AVI_VideoGetType( u32 i_type )
{
    switch( i_type )
    {
/* FIXME FIXME :  what are the correct tag for msmpeg4 v1 */
        case(  FOURCC_MPG4  ):
        case(  FOURCC_mpg4  ):
        case(  FOURCC_DIV2  ):
        case(  FOURCC_div2  ):
        case(  FOURCC_MP42  ):
        case(  FOURCC_mp42  ):
             return( MSMPEG4v2_VIDEO_ES );
           
        case(  FOURCC_MPG3  ):
        case(  FOURCC_mpg3  ):
        case(  FOURCC_div3  ):
        case(  FOURCC_MP43  ):
        case(  FOURCC_mp43  ):
        case(  FOURCC_DIV3  ):
        case(  FOURCC_DIV4  ):
        case(  FOURCC_div4  ):
        case(  FOURCC_DIV5  ):
        case(  FOURCC_div5  ):
        case(  FOURCC_DIV6  ):
        case(  FOURCC_div6  ):
        case(  FOURCC_AP41  ):
        case(  FOURCC_3IV1  ):
            return( MSMPEG4v3_VIDEO_ES );


        case(  FOURCC_DIVX  ):
        case(  FOURCC_divx  ):
        case(  FOURCC_DIV1  ):
        case(  FOURCC_div1  ):
        case(  FOURCC_MP4S  ):
        case(  FOURCC_mp4s  ):
        case(  FOURCC_M4S2  ):
        case(  FOURCC_m4s2  ):
        case(  FOURCC_xvid  ):
        case(  FOURCC_XVID  ):
        case(  FOURCC_XviD  ):
        case(  FOURCC_DX50  ):
        case(  FOURCC_mp4v  ):
        case(  FOURCC_4     ):
            return( MPEG4_VIDEO_ES );

        default:
            return( 0 );
    }
}

/*****************************************************************************
 * AVIInit: check file and initializes AVI structures
 *****************************************************************************/
static int AVIInit( input_thread_t *p_input )
{
    riffchunk_t *p_riff,*p_hdrl,*p_movi;
    riffchunk_t *p_avih;
    riffchunk_t *p_strl,*p_strh,*p_strf;
    demux_data_avi_file_t *p_avi_demux;
    es_descriptor_t *p_es = NULL; /* for not warning */

    int i;

    /* we need to seek to be able to readcorrectly */
    if( !p_input->stream.b_seekable ) 
    {
        msg_Warn( p_input, "RIFF-AVI module discarded (not seekable)" );
        return( -1 );
    }
    p_input->p_demux_data = 
                p_avi_demux = malloc( sizeof(demux_data_avi_file_t) );
    if( p_avi_demux == NULL )
    {
        msg_Err( p_input, "out of memory" );
        return( -1 );
    }
    memset( p_avi_demux, 0, sizeof( demux_data_avi_file_t ) );
    p_avi_demux->i_rate = DEFAULT_RATE;
    /* FIXME I don't know what it's do, copied from ESInit */
    /* Initialize access plug-in structures. */
    if( p_input->i_mtu == 0 )
    {
        /* Improve speed. */
        p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
    }

    if( RIFF_TestFileHeader( p_input, &p_riff, FOURCC_AVI ) != 0 )    
    {
        __AVIFreeDemuxData( p_input );
        msg_Warn( p_input, "RIFF-AVI module discarded" );
        return( -1 );
    }
    p_avi_demux->p_riff = p_riff;

    if ( RIFF_DescendChunk(p_input) != 0 )
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot look for subchunk" );
        return ( -1 );
    }

    /* it's a riff-avi file, so search for LIST-hdrl */
    if( RIFF_FindListChunk(p_input ,&p_hdrl,p_riff, FOURCC_hdrl) != 0 )
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot find \"LIST-hdrl\"" );
        return( -1 );
    }
    p_avi_demux->p_hdrl = p_hdrl;

    if( RIFF_DescendChunk(p_input) != 0 )
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot look for subchunk" );
        return ( -1 );
    }
    /* in  LIST-hdrl search avih */
    if( RIFF_FindAndLoadChunk( p_input, p_hdrl, 
                                    &p_avih, FOURCC_avih ) != 0 )
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot find \"avih\" chunk" );
        return( -1 );
    }
    __AVI_Parse_avih( &p_avi_demux->avih, p_avih->p_data->p_payload_start );
    RIFF_DeleteChunk( p_input, p_avih );
    
    if( p_avi_demux->avih.i_streams == 0 )  
    /* no stream found, perhaps it would be cool to find it */
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "no stream defined!" );
        return( -1 );
    }

    /*  create one program */
    vlc_mutex_lock( &p_input->stream.stream_lock );
    if( input_InitStream( p_input, 0 ) == -1)
    {
        vlc_mutex_unlock( &p_input->stream.stream_lock );
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot init stream" );
        return( -1 );
    }
    if( input_AddProgram( p_input, 0, 0) == NULL )
    {
        vlc_mutex_unlock( &p_input->stream.stream_lock );
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot add program" );
        return( -1 );
    }
    p_input->stream.p_selected_program = p_input->stream.pp_programs[0];
    p_input->stream.i_mux_rate = p_avi_demux->avih.i_maxbytespersec / 50;
    vlc_mutex_unlock( &p_input->stream.stream_lock ); 

    /* now read info on each stream and create ES */
    p_avi_demux->i_streams = p_avi_demux->avih.i_streams;
    
    p_avi_demux->pp_info = calloc( p_avi_demux->i_streams, 
                                    sizeof( AVIStreamInfo_t* ) );
    memset( p_avi_demux->pp_info, 0, 
                        sizeof( AVIStreamInfo_t* ) * p_avi_demux->i_streams );

    for( i = 0 ; i < p_avi_demux->i_streams; i++ )
    {
#define p_info  p_avi_demux->pp_info[i]
        p_info = malloc( sizeof(AVIStreamInfo_t ) );
        memset( p_info, 0, sizeof( AVIStreamInfo_t ) );        

        if( ( RIFF_FindListChunk(p_input,
                                &p_strl,p_hdrl, FOURCC_strl) != 0 )
                ||( RIFF_DescendChunk(p_input) != 0 ))
        {
            __AVIFreeDemuxData( p_input );
            msg_Err( p_input, "cannot find \"LIST-strl\"" );
            return( -1 );
        }
        
        /* in  LIST-strl search strh */
        if( RIFF_FindAndLoadChunk( p_input, p_hdrl, 
                                &p_strh, FOURCC_strh ) != 0 )
        {
            RIFF_DeleteChunk( p_input, p_strl );
            __AVIFreeDemuxData( p_input );
            msg_Err( p_input, "cannot find \"strh\"" );
            return( -1 );
        }
        __AVI_Parse_Header( &p_info->header,
                            p_strh->p_data->p_payload_start);
        RIFF_DeleteChunk( p_input, p_strh );      

        /* in  LIST-strl search strf */
        if( RIFF_FindAndLoadChunk( p_input, p_hdrl, 
                                &p_strf, FOURCC_strf ) != 0 )
        {
            RIFF_DeleteChunk( p_input, p_strl );
            __AVIFreeDemuxData( p_input );
            msg_Err( p_input, "cannot find \"strf\"" );
            return( -1 );
        }
        /* we don't get strd, it's useless for divx,opendivx,mepgaudio */ 
        if( RIFF_AscendChunk(p_input, p_strl) != 0 )
        {
            RIFF_DeleteChunk( p_input, p_strf );
            RIFF_DeleteChunk( p_input, p_strl );
            __AVIFreeDemuxData( p_input );
            msg_Err( p_input, "cannot go out (\"strl\")" );
            return( -1 );
        }

        /* add one ES */
        vlc_mutex_lock( &p_input->stream.stream_lock );
        p_es = input_AddES( p_input,
                            p_input->stream.p_selected_program, 1+i,
                            p_strf->i_size );
        vlc_mutex_unlock( &p_input->stream.stream_lock );
        p_es->i_stream_id =i; /* XXX: i don't use it */ 
       
        switch( p_info->header.i_type )
        {
            case( FOURCC_auds ):
                p_es->i_cat = AUDIO_ES;
                avi_ParseWaveFormatEx( &p_info->audio_format,
                                   p_strf->p_data->p_payload_start ); 
                p_es->b_audio = 1;
                p_es->i_type = 
                    __AVI_AudioGetType( p_info->audio_format.i_formattag );
                if( p_es->i_type == 0 )
                {
                    msg_Err( p_input, "stream(%d,0x%x) not supported", i,
                                       p_info->audio_format.i_formattag );
                    p_es->i_cat = UNKNOWN_ES;
                }
                break;
                
            case( FOURCC_vids ):
                p_es->i_cat = VIDEO_ES;
                avi_ParseBitMapInfoHeader( &p_info->video_format,
                                   p_strf->p_data->p_payload_start ); 
                p_es->b_audio = 0;
                p_es->i_type = 
                    __AVI_VideoGetType( p_info->video_format.i_compression );
                if( p_es->i_type == 0 )
                {
                    msg_Err( p_input, "stream(%d,%4.4s) not supported", i,
                              (char*)&p_info->video_format.i_compression);
                    p_es->i_cat = UNKNOWN_ES;
                }
                break;
            default:
                msg_Err( p_input, "unknown stream(%d) type", i );
                p_es->i_cat = UNKNOWN_ES;
                break;
        }
        p_info->p_es = p_es;
        p_info->i_cat = p_es->i_cat;
        /* We copy strf for decoder in p_es->p_demux_data */
        memcpy( p_es->p_demux_data, 
                p_strf->p_data->p_payload_start,
                p_strf->i_size );
        RIFF_DeleteChunk( p_input, p_strf );
        RIFF_DeleteChunk( p_input, p_strl );
#undef p_info           
    }



    /* go out of p_hdrl */
    if( RIFF_AscendChunk(p_input, p_hdrl) != 0)
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot go out (\"hdrl\")" );
        return( -1 );
    }

    /* go to movi chunk to get it*/
    if( RIFF_FindListChunk(p_input ,&p_movi,p_riff, FOURCC_movi) != 0 )
    {
        msg_Err( p_input, "cannot find \"LIST-movi\"" );
        __AVIFreeDemuxData( p_input );
        return( -1 );
    }
    p_avi_demux->p_movi = p_movi;
    
    /* get index  XXX need to have p_movi */
    if( (p_avi_demux->avih.i_flags&AVIF_HASINDEX) != 0 )
    {
        /* get index */
        __AVI_GetIndex( p_input ); 
        /* try to get i_idxoffset for each stream  */
        __AVI_UpdateIndexOffset( p_input );
    }
    else
    {
        msg_Warn( p_input, "no index!" );
    }

    
    /* we verify that each stream have at least one entry or create it */
    for( i = 0; i < p_avi_demux->i_streams ; i++ )
    {
        AVIIndexEntry_t index;
        riffchunk_t     *p_chunk;
#define p_info  p_avi_demux->pp_info[i]
        if( p_info->p_index == NULL )
        {
            RIFF_GoToChunk( p_input, p_avi_demux->p_movi );
            if( RIFF_DescendChunk(p_input) != 0 ) { continue; }
            p_chunk = NULL;
            switch( p_info->i_cat ) 
            {
                case( AUDIO_ES ):
                    if( RIFF_FindChunk( p_input, 
                               VLC_FOURCC('0'+i/10, '0'+i%10,'w','b' ), 
                                             p_movi ) == 0)
                    {
                       p_chunk = RIFF_ReadChunk( p_input );
                    }
                    break;
                    
                case( VIDEO_ES ):
                    if( (RIFF_FindChunk( p_input, 
                                    VLC_FOURCC('0'+i/10, '0'+i%10,'d','c' ),
                                            p_movi ) == 0) )
                    {
                        p_chunk = RIFF_ReadChunk( p_input ); 
                    }
                    else
                    {
                        RIFF_GoToChunk( p_input, p_avi_demux->p_movi );
                        if( RIFF_DescendChunk(p_input) != 0 ) { continue; }
                        if( (RIFF_FindChunk( p_input,
                                        VLC_FOURCC('0'+i/10, '0'+i%10,'d','b' ),
                                            p_movi ) == 0) )
                        {
                            p_chunk = RIFF_ReadChunk( p_input );
                        }
                    }
                    break;
            }
            if( p_chunk != NULL )
            {
                index.i_id = p_chunk->i_id;
                index.i_flags = AVIIF_KEYFRAME;
                index.i_pos = p_chunk->i_pos;
                index.i_length = p_chunk->i_size;
                __AVI_AddEntryIndex( p_info, &index );
                msg_Dbg( p_input, "add index entry (%4.4s) (%d)", 
                                  (char*)&p_chunk->i_id, i ); 
               
            }
        }
#undef p_info
    }

    /* to make sure to go the begining because unless demux will see a seek */
    RIFF_GoToChunk( p_input, p_avi_demux->p_movi );
    if( RIFF_DescendChunk( p_input ) != 0 )
    {
        __AVIFreeDemuxData( p_input );
        msg_Err( p_input, "cannot go in (\"movi\")" );
        return( -1 );
    }

    /* print informations on streams */
    msg_Dbg( p_input, "AVIH: %d stream, flags %s%s%s%s%s%s ", 
             p_avi_demux->i_streams,
             p_avi_demux->avih.i_flags&AVIF_HASINDEX?" HAS_INDEX":"",
             p_avi_demux->avih.i_flags&AVIF_MUSTUSEINDEX?" MUST_USE_INDEX":"",
             p_avi_demux->avih.i_flags&AVIF_ISINTERLEAVED?" IS_INTERLEAVED":"",
             p_avi_demux->avih.i_flags&AVIF_TRUSTCKTYPE?" TRUST_CKTYPE":"",
             p_avi_demux->avih.i_flags&AVIF_WASCAPTUREFILE?" CAPTUREFILE":"",
             p_avi_demux->avih.i_flags&AVIF_COPYRIGHTED?" COPYRIGHTED":"" );

    for( i = 0; i < p_avi_demux->i_streams; i++ )
    {
#define p_info  p_avi_demux->pp_info[i]
        switch( p_info->p_es->i_cat )
        {
            case( VIDEO_ES ):
                msg_Dbg( p_input, "video(%4.4s) %dx%d %dbpp %ffps (size %d)",
                         (char*)&p_info->video_format.i_compression,
                         p_info->video_format.i_width,
                         p_info->video_format.i_height,
                         p_info->video_format.i_bitcount,
                         (float)p_info->header.i_rate /
                             (float)p_info->header.i_scale,
                         p_info->header.i_samplesize );
                if( (p_avi_demux->p_info_video == NULL) ) 
                {
                    p_avi_demux->p_info_video = p_info;
                }
                break;

            case( AUDIO_ES ):
                msg_Dbg( p_input, "audio(0x%x) %d channels %dHz %dbits %ffps (size %d)",
                         p_info->audio_format.i_formattag,
                         p_info->audio_format.i_channels,
                         p_info->audio_format.i_samplespersec,
                         p_info->audio_format.i_bitspersample,
                         (float)p_info->header.i_rate /
                             (float)p_info->header.i_scale,
                         p_info->header.i_samplesize );
                if( (p_avi_demux->p_info_audio == NULL) ) 
                {
                    p_avi_demux->p_info_audio = p_info;
                }
                break;
            case( UNKNOWN_ES ):
                msg_Warn( p_input, "unhandled stream %d", i );
        }
#undef p_info    
    }

    /* we select the first audio and video ES */
    vlc_mutex_lock( &p_input->stream.stream_lock );
    if( p_avi_demux->p_info_video != NULL ) 
    {
        input_SelectES( p_input, p_avi_demux->p_info_video->p_es );
        /*  it seems that it's useless to select es because there are selected 
         *  by the interface but i'm not sure of that */
    }
    else
    {
        msg_Err( p_input, "no video stream found" );
    }

    if( p_avi_demux->p_info_audio != NULL ) 
    {
        input_SelectES( p_input, p_avi_demux->p_info_audio->p_es );
    }
    else
    {
        msg_Warn( p_input, "no audio stream found!" );
    }
    p_input->stream.p_selected_program->b_is_ok = 1;
    vlc_mutex_unlock( &p_input->stream.stream_lock );
    
    return( 0 );
}


/*****************************************************************************
 * AVIEnd: frees unused data
 *****************************************************************************/
static void AVIEnd( input_thread_t *p_input )
{   
    __AVIFreeDemuxData( p_input ); 
    return;
}


static mtime_t AVI_GetPTS( AVIStreamInfo_t *p_info )
{
    /* p_info->p_index[p_info->i_idxposc] need to be valid !! */
    /* be careful to  *1000000 before round  ! */
    if( p_info->header.i_samplesize != 0 )
    {
        return( (mtime_t)( (double)1000000.0 *
                   (double)(p_info->p_index[p_info->i_idxposc].i_lengthtotal +
                             p_info->i_idxposb )*
                    (double)p_info->header.i_scale /
                    (double)p_info->header.i_rate /
                    (double)p_info->header.i_samplesize ) );
    }
    else
    {
        return( (mtime_t)( (double)1000000.0 *
                    (double)(p_info->i_idxposc ) *
                    (double)p_info->header.i_scale /
                    (double)p_info->header.i_rate) );
    }
}



static int __AVI_NextIndexEntry( input_thread_t *p_input, 
                                  AVIStreamInfo_t *p_info )
{
    AVIIndexEntry_t index;
    riffchunk_t     *p_chunk;
    AVIStreamInfo_t *p_info_tmp;
    int             i;
    int             i_idxposc;
    int             b_inc = 0;
    demux_data_avi_file_t *p_avi_demux =
                        (demux_data_avi_file_t*)p_input->p_demux_data;

    p_info->i_idxposc++;

    if( p_info->i_idxposc < p_info->i_idxnb )
    {
        return( 0 );
    }
    if( p_info->i_idxposc > p_info->i_idxnb )
    {
        return( -1 );
    }
    p_info->i_idxposc--;
    
    /* create entry on the fly */
    /* search for the more advance stream and parse from it for all streams*/
    p_info_tmp = p_info;
    for( i = 0; i < p_avi_demux->i_streams; i++ )
    {
#define p_info_i p_avi_demux->pp_info[i]
        if( ( p_info_i->p_index )
            && ( p_info_i->p_index[p_info_i->i_idxnb - 1].i_pos >
            p_info_tmp->p_index[p_info_tmp->i_idxnb - 1].i_pos ) )
        {
            p_info_tmp = p_info_i;
        }
#undef  p_info_i
    }
       
    /* go to last defined entry */
    i_idxposc = p_info_tmp->i_idxposc; /* save p_info_tmp->i_idxposc */
    p_info_tmp->i_idxposc = p_info_tmp->i_idxnb - 1;
    __AVI_SeekToChunk( p_input, p_info_tmp );
    p_info_tmp->i_idxposc = i_idxposc;

    if( RIFF_NextChunk( p_input, p_avi_demux->p_movi ) != 0 )
    {
        p_info->i_idxposc++;
        return( -1 );
    }

    /* save idxpos of p_info */
    /* now parse for all stream and stop when reach next chunk for p_info */
    for( i = 0; (i < 15)||(!b_inc); i++)
    {
        int i_number;
        u16 i_type;
        if( (p_chunk = RIFF_ReadChunk( p_input )) == NULL )
        {
            p_info->i_idxposc++;
            return( b_inc == 1 ? 0 : -1 );
        }

        index.i_id = p_chunk->i_id;
        index.i_flags = AVIIF_KEYFRAME;
        index.i_pos = p_chunk->i_pos;
        index.i_length = p_chunk->i_size;
        RIFF_DeleteChunk( p_input, p_chunk );
#define p_info_i    p_avi_demux->pp_info[i_number]
       if( (__AVI_ParseStreamHeader( index.i_id, &i_number, &i_type ) == 0)
             &&( i_number < p_avi_demux->i_streams )
             &&( p_info_i->p_index )
             &&( p_info_i->p_index[p_info_i->i_idxnb - 1].i_pos + 
                     p_info_i->p_index[p_info_i->i_idxnb - 1].i_length + 8<= 
                        index.i_pos ) 
             &&( __AVIGetESTypeFromTwoCC( i_type ) == p_info_i->i_cat ) )
        {
            __AVI_AddEntryIndex( p_info_i, &index );
            if( (p_info_i == p_info)&&(!b_inc) )
            {
                b_inc = 1;
            }
        }
#undef  p_info_i
        if( RIFF_NextChunk( p_input, p_avi_demux->p_movi ) != 0 )
        {
            p_info->i_idxposc++;
            return( b_inc == 1 ? 0 : -1 );
        }

    } 

    p_info->i_idxposc++;
    return( 0 );
}

/*****************************************************************************
 * Functions to acces streams data 
 * Uses it, because i plane to read unseekable stream
 * Don't work for the moment for unseekable stream 
 *****************************************************************************/

/*      __AVI_ReadStreamChunkInPES;   load an entire chunk 
        __AVI_ReadStreamBytesInPES;   load bytes 
        __AVI_GoToStreamChunk;        go to chunk
        __AVI_GoToStreamBytes;        go to bytes in the all stream  
 not seekable file is not yet supported
 */

static int __AVI_GoToStreamChunk( input_thread_t    *p_input, 
                                  AVIStreamInfo_t   *p_info,
                                  int   i_chunk )
{
    u32   u32_pos;
    off_t i_pos;
    
    if( !p_input->stream.b_seekable )
    {
        msg_Err( p_input, "need the ability to seek in stream" );
        return( -1 );
    }

    if( p_info->p_index != NULL )
    {
        if( i_chunk >= p_info->i_idxnb )
        {
            p_info->i_idxposc = p_info->i_idxnb-1;
            while( p_info->i_idxposc < i_chunk )
            {
                if( __AVI_NextIndexEntry( p_input, p_info ) != 0)
                {
                    return( -1 );
                }
            }
        }
        else
        {
            p_info->i_idxposc = i_chunk;
        }

        /* now do we have valid index for the chunk */
        __RIFF_TellPos( p_input, &u32_pos );

        i_pos = (off_t)p_info->p_index[i_chunk].i_pos;
        if( i_pos != u32_pos )
        {
            p_input->pf_seek( p_input, i_pos );
            input_AccessReinit( p_input );
        }
        p_info->i_idxposb = 0;
        return( 0 );
    }
    else
    {
        return( -1 );
    }
}

static int __AVI_GoToStreamBytes( input_thread_t    *p_input, 
                                  AVIStreamInfo_t   *p_info,
                                  int   i_byte )
{
    u32   u32_pos;
    off_t i_pos;

    if( !p_input->stream.b_seekable )
    {
        msg_Err( p_input, "need the ability to seek in stream" );
        return( -1 );
    }

    /* now do we have valid index for the chunk */
    if( p_info->p_index != NULL )
    {
        if( p_info->p_index[p_info->i_idxnb - 1].i_lengthtotal +
            p_info->p_index[p_info->i_idxnb - 1].i_length <= i_byte)
        {
            p_info->i_idxposc = p_info->i_idxnb - 1;
            while( p_info->p_index[p_info->i_idxposc].i_lengthtotal +
                        p_info->p_index[p_info->i_idxposc].i_length <= i_byte)
            {
                if( __AVI_NextIndexEntry( p_input, p_info ) != 0)
                {
                    return( -1 );
                }
            }
        }
        else
        {
            /* uses dichototmie to be fast enougth */
            int i_idxposc = p_info->i_idxposc;
            int i_idxmax  = p_info->i_idxnb;
            int i_idxmin  = 0;
            for( ;; )
            {
                if( p_info->p_index[i_idxposc].i_lengthtotal > i_byte )
                {
                    i_idxmax  = i_idxposc ;
                    i_idxposc = ( i_idxmin + i_idxposc ) / 2 ;
                }
                else
                {
                    if( p_info->p_index[i_idxposc].i_lengthtotal + 
                            p_info->p_index[i_idxposc].i_length <= i_byte)
                    {
                        i_idxmin  = i_idxposc ;
                        i_idxposc = (i_idxmax + i_idxposc ) / 2 ;
                    }
                    else
                    {
                        p_info->i_idxposc = i_idxposc;
                       break;
                   }
                }
            }
        }

        p_info->i_idxposb = i_byte - 
                       p_info->p_index[p_info->i_idxposc].i_lengthtotal;
        
        i_pos = (off_t)p_info->p_index[p_info->i_idxposc].i_pos +
                      p_info->i_idxposb + 8;
        __RIFF_TellPos( p_input, &u32_pos );
        if( u32_pos != i_pos )
        {
            p_input->pf_seek( p_input, i_pos );
            input_AccessReinit( p_input );
        }
        return( 0 );
    }
    else
    {
        return( -1 );
    }
}

static pes_packet_t *__AVI_ReadStreamChunkInPES(  input_thread_t    *p_input,
                                                  AVIStreamInfo_t   *p_info )
{
    pes_packet_t    *p_pes;
    if( p_info->i_idxposc >= p_info->i_idxnb )
    {
        return( NULL );
    }
    /*- XXX avio is sh*t sometime chunk size is broken, and 
      we must choose index sie */
    
    if( ( __AVI_GoToStreamChunk( p_input, p_info, p_info->i_idxposc ) != 0 )
         ||( RIFF_LoadChunkDataInPES( p_input, 
                                      &p_pes,
                            p_info->p_index[p_info->i_idxposc].i_length ) != 0 ) )
    {
        return( NULL );
    }
    else
    {
      __AVI_NextIndexEntry( p_input, p_info);
      p_info->i_idxposb = 0;
      return( p_pes );
    }
}

static pes_packet_t *__AVI_ReadStreamBytesInPES(  input_thread_t    *p_input,
                                                  AVIStreamInfo_t   *p_info,
                                                  int i_byte )
{
    pes_packet_t    *p_pes = NULL;
    data_packet_t   *p_data;
    int             i_read;

    if( ( p_pes = input_NewPES( p_input->p_method_data ) ) == NULL )
    {
        return( NULL );
    }
    
    while( i_byte > 0 )
    {
        if( p_info->i_idxposc >= p_info->i_idxnb )
        {
            input_DeletePES( p_input->p_method_data, p_pes );
            return( NULL );
        }
        if( __AVI_GoToStreamBytes( p_input, p_info, 
                       p_info->p_index[p_info->i_idxposc].i_lengthtotal + 
                        p_info->i_idxposb ) != 0 )
        {
            input_DeletePES( p_input->p_method_data, p_pes );
            return( NULL );
        }

        i_read = input_SplitBuffer(p_input, &p_data,
                                 __MIN( i_byte, 
                                 p_info->p_index[p_info->i_idxposc].i_length 
                                    - p_info->i_idxposb ) );
        if( i_read <= 0 )
        {
            input_DeletePES( p_input->p_method_data, p_pes );
            return( NULL );
        }
        p_pes->i_nb_data++;
        if( !p_pes->p_first )
        {
            p_pes->p_first = p_data;
        }
        else
        {
            p_pes->p_last->p_next = p_data;
        }
        p_pes->p_last = p_data;
        p_pes->i_pes_size += i_read;

        i_byte -= i_read;
        p_info->i_idxposb += i_read;

        if( p_info->i_idxposb >= p_info->p_index[p_info->i_idxposc].i_length )
        {
            p_info->i_idxposb = 0;
            if( __AVI_NextIndexEntry( p_input, p_info) != 0 )
            {
                input_DeletePES( p_input->p_method_data, p_pes );
                return( NULL );
            }
        }
    };
   return( p_pes );
}
        
/*****************************************************************************
 * Function to convert pts to chunk or byte
 *****************************************************************************/

static inline mtime_t __AVI_PTSToChunk( AVIStreamInfo_t *p_info, 
                                        mtime_t i_pts )
{
    return( (mtime_t)((double)i_pts *
                      (double)p_info->header.i_rate /
                      (double)p_info->header.i_scale /
                      (double)1000000.0 ) );
}

static inline mtime_t __AVI_PTSToByte( AVIStreamInfo_t *p_info,
                                       mtime_t i_pts )
{
    return( (mtime_t)((double)i_pts * 
                      (double)p_info->header.i_samplesize *
                      (double)p_info->header.i_rate /
                      (double)p_info->header.i_scale /
                      (double)1000000.0 ) );

}

/* try to realign after a seek */
static int AVI_ReAlign( input_thread_t *p_input )
{
    u32     u32_pos;
    off_t   i_pos;
    int     b_after = 0;
    AVIStreamInfo_t *p_info;
    demux_data_avi_file_t *p_avi_demux =
                        (demux_data_avi_file_t*)p_input->p_demux_data;

    p_info = (p_avi_demux->p_info_video != NULL ) ? 
                    p_avi_demux->p_info_video :
                    p_avi_demux->p_info_audio;
    
    __RIFF_TellPos( p_input, &u32_pos );
    
    i_pos = (off_t)u32_pos;
    
    if( i_pos <= p_info->p_index[0].i_pos )
    {
        /*  before beginning of stream  */
        if( !p_info->header.i_samplesize )
        {
            __AVI_GoToStreamChunk( p_input, 
                                   p_info,
                                   0 );
        } 
        else
        {
            __AVI_GoToStreamBytes( p_input, 
                                   p_info, 
                                   0);
        }
        return( 0 );
    }

    if( (p_info->p_index[p_info->i_idxposc].i_pos <= i_pos)
        && ( i_pos < p_info->p_index[p_info->i_idxposc].i_pos +
                p_info->p_index[p_info->i_idxposc].i_length ) )
    {
        /* FIXME if master == audio and samplesize != 0 */
        /* don't work with big chunk */
        /* don't do anything we are in the current chunk  */
        return( 0 );
    }
    if( i_pos < p_info->p_index[p_info->i_idxposc].i_pos )
    {
        b_after = 0;
    }
    else
    {
        b_after = 1;
    }
    /* now find in what chunk we are */
    while( ( i_pos < p_info->p_index[p_info->i_idxposc].i_pos )
           &&( p_info->i_idxposc > 0 ) )
    {
        /* search before i_idxposc */
        p_info->i_idxposc--;
    }
    
    while( i_pos >= p_info->p_index[p_info->i_idxposc].i_pos +
               p_info->p_index[p_info->i_idxposc].i_length + 8 )
    {
        /* search after i_idxposc */
        if( __AVI_NextIndexEntry( p_input, p_info ) != 0 )
        {
            return( -1 );
        }
    }
    /* search nearest key frame */
    if( ( !p_info->header.i_samplesize ) && ( p_info->i_cat == VIDEO_ES ) )
    {
        /* only for chunk stream */
        if( b_after )
        {
            while(!(p_info->p_index[p_info->i_idxposc].i_flags&AVIIF_KEYFRAME) )
            {
                if( __AVI_NextIndexEntry( p_input, p_info ) != 0 )
                {
                    break;
                }
            }
        }
        else
        { 
            while( ( p_info->i_idxposc > 0 ) &&
              (!(p_info->p_index[p_info->i_idxposc].i_flags&AVIIF_KEYFRAME)) )
            {
                p_info->i_idxposc--;
            }
        }
    } 
    __AVI_GoToStreamChunk( p_input, p_info, p_info->i_idxposc );
   
    return( 0 );
}

/* make difference between audio and video pts as little as possible */
static void AVI_SynchroReInit( input_thread_t *p_input )
{
    demux_data_avi_file_t *p_avi_demux =
                        (demux_data_avi_file_t*)p_input->p_demux_data;
#define p_info_video p_avi_demux->p_info_video
#define p_info_audio p_avi_demux->p_info_audio
    if( ( p_info_video )&&( p_info_audio ) )
    {
        /* now resynch audio video video */
        /*don't care of AVIF_KEYFRAME */
        if( !p_info_audio->header.i_samplesize )
        {
            int i_chunk = __AVI_PTSToChunk( p_info_audio, 
                                            AVI_GetPTS( p_info_video ));
            __AVI_GoToStreamChunk( p_input, 
                                   p_info_audio, 
                                   i_chunk );
        }
        else
        {
            int i_byte = __AVI_PTSToByte( p_info_audio, 
                                          AVI_GetPTS( p_info_video ) ) ;
            __AVI_GoToStreamBytes( p_input,
                                   p_info_audio,
                                   i_byte );
        }
   }
#undef p_info_video
#undef p_info_audio
} 

/*****************************************************************************
 * AVI_GetFrameInPES : get dpts length(µs) in pes from stream
 *****************************************************************************
 * Handle multiple pes, and set pts to the good value 
 *****************************************************************************/
static pes_packet_t *AVI_GetFrameInPES( input_thread_t *p_input,
                                        AVIStreamInfo_t *p_info,
                                        mtime_t i_dpts)
{
    int i;
    pes_packet_t *p_pes = NULL;
    pes_packet_t *p_pes_tmp = NULL;
    pes_packet_t *p_pes_first = NULL;
    mtime_t i_pts;

    /* we now that p_info != NULL */
    if( i_dpts < 1000 ) 
    { 
        return( NULL ) ; 
    }

    if( !p_info->header.i_samplesize )
    {
        /* stream is chunk based , easy */
        int i_chunk = __MAX( __AVI_PTSToChunk( p_info, i_dpts), 1 );
        /* at least one frame */
        /* read them */
        p_pes_first = NULL;
        for( i = 0; i < i_chunk; i++ )
        {
            /* get pts while is valid */
            i_pts = AVI_GetPTS( p_info );
            p_pes_tmp = __AVI_ReadStreamChunkInPES( p_input, p_info );

            if( !p_pes_tmp )
            {
                return( p_pes_first );
            }
            p_pes_tmp->i_pts = i_pts;
            if( !p_pes_first )
            {
                p_pes_first = p_pes_tmp;
            }
            else
            {
                p_pes->p_next = p_pes_tmp;
            }
            p_pes = p_pes_tmp;
        }
        return( p_pes_first );
    }
    else
    {
        /* stream is byte based */
        int i_byte = __AVI_PTSToByte( p_info, i_dpts);
        if( !i_byte )
        {
            return( NULL );
        }
        /* at least one Kbyte */
        /*i_byte = __MIN( 1024*1000, i_byte ); *//* but no more than 1000ko */
        i_pts = AVI_GetPTS( p_info );

        p_pes = __AVI_ReadStreamBytesInPES( p_input, p_info, i_byte);
        
        if( p_pes != NULL )
        {
            p_pes->i_pts = i_pts;
        }
        return( p_pes );
    }
}
/*****************************************************************************
 * AVI_DecodePES : send a pes to decoder 
 *****************************************************************************
 * Handle multiple pes, and set pts to the good value 
 *****************************************************************************/
static inline void AVI_DecodePES( input_thread_t *p_input,
                                  AVIStreamInfo_t *p_info,
                                  pes_packet_t *p_pes )
{
    pes_packet_t    *p_pes_next;
    /* input_decode want only one pes, but AVI_GetFrameInPES give
          multiple pes so send one by one */
    /* we now that p_info != NULL */
    while( p_pes )
    {
        p_pes_next = p_pes->p_next;
        p_pes->p_next = NULL;
        p_pes->i_pts = input_ClockGetTS( p_input, 
                                         p_input->stream.p_selected_program, 
                                         p_pes->i_pts * 9/100);
        input_DecodePES( p_info->p_es->p_decoder_fifo, p_pes );
        p_pes = p_pes_next;
    };
  
}

/*****************************************************************************
 * AVIDemux: reads and demuxes data packets
 *****************************************************************************
 * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
 *****************************************************************************/
static int AVIDemux( input_thread_t *p_input )
{
    int i;
    pes_packet_t *p_pes;
    AVIStreamInfo_t *p_info_master;
    AVIStreamInfo_t *p_info_slave;    

    demux_data_avi_file_t *p_avi_demux = 
                (demux_data_avi_file_t*)p_input->p_demux_data;

    /* search new video and audio stream selected 
          if current have been unselected*/
    if( ( !p_avi_demux->p_info_video )
            || ( !p_avi_demux->p_info_video->p_es->p_decoder_fifo ) )
    {
        p_avi_demux->p_info_video = NULL;
        for( i = 0; i < p_avi_demux->i_streams; i++ )
        {
            if( ( p_avi_demux->pp_info[i]->i_cat == VIDEO_ES )
                  &&( p_avi_demux->pp_info[i]->p_es->p_decoder_fifo != NULL ) )
            {
                p_avi_demux->p_info_video = p_avi_demux->pp_info[i];
                p_avi_demux->p_info_video->b_selected = 1;
                break;
            }
        }
    }
    if( ( !p_avi_demux->p_info_audio )
            ||( !p_avi_demux->p_info_audio->p_es->p_decoder_fifo ) )
    {
        p_avi_demux->p_info_audio = NULL;
        for( i = 0; i < p_avi_demux->i_streams; i++ )
        {
            if( ( p_avi_demux->pp_info[i]->i_cat == AUDIO_ES )
                  &&( p_avi_demux->pp_info[i]->p_es->p_decoder_fifo != NULL ) )
            {
                p_avi_demux->p_info_audio = p_avi_demux->pp_info[i];
                p_avi_demux->p_info_audio->b_selected = 1;
                break;
            }
        }
    }
    /* by default video is master for resync audio (after a seek .. ) */
    if( p_avi_demux->p_info_video )
    {
        p_info_master = p_avi_demux->p_info_video;
        p_info_slave  = p_avi_demux->p_info_audio;
    }
    else
    {
        p_info_master = p_avi_demux->p_info_audio;
        p_info_slave  = NULL;
    }

    if( !p_info_master ) 
    {
        msg_Err( p_input, "no stream selected" );
        return( -1 );
    }

    /* check for signal from interface */
    if( p_input->stream.p_selected_program->i_synchro_state == SYNCHRO_REINIT )
    { 
        /* we can supposed that is a seek */
        /* first wait for empty buffer, arbitrary time */
        msleep( DEFAULT_PTS_DELAY );
        /* then try to realign in stream */
        if( AVI_ReAlign( p_input ) != 0 )
        {
            return( 0 ); /* assume EOF */
        }
        AVI_SynchroReInit( p_input ); 
    }
    /* manage rate, if not default: skeep audio */
    vlc_mutex_lock( &p_input->stream.stream_lock );
    if( p_input->stream.control.i_rate != p_avi_demux->i_rate )
    {
        if( p_avi_demux->p_info_audio)
        {
             p_avi_demux->p_info_audio->b_selected = 1;
        }
        p_avi_demux->i_rate = p_input->stream.control.i_rate;
    }
    vlc_mutex_unlock( &p_input->stream.stream_lock );    
    if( p_avi_demux->i_rate != DEFAULT_RATE )
    {
        p_info_slave = NULL;
    }
 
    /* take care of newly selected audio ES */
    if( p_info_master->b_selected )
    {
        p_info_master->b_selected = 0;
        AVI_SynchroReInit( p_input ); 
    }
    if( ( p_info_slave )&&( p_info_slave->b_selected ) )
    {
        p_info_slave->b_selected = 0;
        AVI_SynchroReInit( p_input );
    }

    /* wait for the good time */
    input_ClockManageRef( p_input,
                          p_input->stream.p_selected_program,
                          p_avi_demux->i_pcr ); 
    /* calculate pcr, time when we must read the next data */
    /* 9/100 kludge ->need to convert to 1/1000000 clock unit to 1/90000 */
    if( p_info_slave )
    {
        p_avi_demux->i_pcr =  __MIN( AVI_GetPTS( p_info_master ),
                                     AVI_GetPTS( p_info_slave ) ) * 9/100;
    }
    else
    {
        p_avi_demux->i_pcr =  AVI_GetPTS( p_info_master ) * 9/100;
    }

    /* get video and audio frames */
    p_pes = AVI_GetFrameInPES( p_input,
                               p_info_master,
                               100000 ); /* 100 ms */

    AVI_DecodePES( p_input,
                   p_info_master,
                   p_pes);

    if( p_info_slave )
    {
        p_pes = AVI_GetFrameInPES( p_input,
                                   p_info_slave,
                                   AVI_GetPTS( p_info_master ) -
                                       AVI_GetPTS( p_info_slave));
        AVI_DecodePES( p_input,
                       p_info_slave,
                       p_pes );
    }
    /* at the end ? */
    return( p_info_master->i_idxposc >= p_info_master->i_idxnb ? 0 : 1 );
}
