//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2011 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: AVI demuxing  (based on the FFMPEG lib)

//#define MPXPLAY_USE_DEBUGF 1
//#define MPXPLAY_USE_DEBUGMSG 1
#define MPXPLAY_DEBUG_OUTPUT stdout

#ifdef MPXPLAY_USE_DEBUGF
 //#define MPXPLAY_AVIDEBUG_HEADER 1
 #define MPXPLAY_AVIDEBUG_INFO 1
 //#define MPXPLAY_AVIDEBUG_DEMUX 1
 #define MPXPLAY_AVIDEBUG_SEEK 1
#endif

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_AVI

#include <malloc.h>
#include <limits.h>

#define AVIFLAG_IDX1_KEYFRAME 0x10

#define AST_SEEKFLAG_KEYFRAME 0x01

typedef struct avi_stream_data_s{
 unsigned int streamtype;
 mpxp_uint32_t codec_tag;
 unsigned int need_parsing;
 mpxp_int32_t timemsec_stream;
 unsigned int stream_index;

 long start;
 long duration;
 long nb_frames;
 unsigned long bit_rate;
 int scale;
 int rate;
 unsigned long bufsize;

 mpxp_uint8_t *extradata;
 unsigned long extradata_size;

 // audio
 unsigned int channels;
 unsigned long sample_rate;
 unsigned int block_align;
 unsigned int bits_per_sample;
 int sample_size;

 //video
 unsigned long video_res_x;
 unsigned long video_res_y;
 unsigned int  video_bpp;

 //mpxp_filesize_t frame_offset; // current frame (video) or byte (audio) counter

 unsigned long seek_entries;
 mpxp_uint32_t *seek_table;
 mpxp_uint8_t  *seek_flags;
}avi_stream_data_s;

typedef struct avi_demuxer_data_s{
 mpxp_uint32_t riff_end;
 mpxp_uint32_t movi_end;
 mpxp_uint32_t movi_list;
 mpxp_int32_t microsec_per_frame;
 mpxp_int32_t total_frames;
 mpxp_int32_t timemsec_avih;
 int nb_streams;
 int is_odml;
 //int non_interleaved;
 mpxp_filesize_t frame_offset; // pos of current frame (now it's audio)

 struct avi_stream_data_s *avs;      // all streams
 struct avi_stream_data_s *ast_audio;
 struct avi_stream_data_s *ast_video;
 struct avi_stream_data_s *ast_subtitle;
}avi_demuxer_data_s;

static unsigned int avi_read_header(struct avi_demuxer_data_s *avii,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis);
static unsigned int avi_read_wavheader(avi_stream_data_s *ast, struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds, int size);
static unsigned int avi_load_index(struct avi_demuxer_data_s *avi,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);
static struct avi_stream_data_s *avi_get_stream(struct avi_demuxer_data_s *avi,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi);
static void avi_assign_audio(struct avi_demuxer_data_s *avi,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode);
static void avi_assign_video(struct avi_demuxer_data_s *avi,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode);

static int AVI_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct avi_demuxer_data_s *avii=NULL;
 avi_stream_data_s *asta,*astv;
 long diffha,diffhv,diffav;

 if(!fbfs->fopen(fbds,filename,O_RDONLY|O_BINARY,4096))
  return MPXPLAY_ERROR_INFILE_FILEOPEN;

 miis->filesize=fbfs->filelength(fbds);
 if(miis->filesize<127)
  goto err_out_chk;

 avii=(struct avi_demuxer_data_s *)calloc(1,sizeof(struct avi_demuxer_data_s));
 if(!avii)
  goto err_out_chk;
 miis->private_data=avii;

 if(!avi_read_header(avii,fbfs,fbds,miis))
  goto err_out_chk;

 avii->ast_audio=asta=avi_get_stream(avii,MPXPLAY_SPI_STREAMTYPE_AUDIO,miis->audio_stream);
 avii->ast_video=astv=avi_get_stream(avii,MPXPLAY_SPI_STREAMTYPE_VIDEO,miis->video_stream);

 if(!asta && !astv)
  goto err_out_chk;

 if(asta && (asta->scale>1) && (asta->rate>1) && asta->duration){ // ??? probably good values
  asta->timemsec_stream=miis->timemsec=((float)MPXPLAY_TIME_BASE*(float)asta->duration/(float)asta->rate*(float)asta->scale);
#ifdef MPXPLAY_LINK_VIDEO
  if(miis->timemsec<astv->timemsec_stream)
   miis->timemsec=astv->timemsec_stream;
  if(miis->timemsec<avii->timemsec_avih)
   miis->timemsec=avii->timemsec_avih;
#endif
 }else{
  diffha=diffhv=diffav=INT_MAX;
  if(avii->timemsec_avih){
   if(asta && asta->timemsec_stream){
    diffha=avii->timemsec_avih-asta->timemsec_stream;
    if(diffha<0)
     diffha=-diffha;
   }
   if(astv && astv->timemsec_stream){
    diffhv=avii->timemsec_avih-astv->timemsec_stream;
    if(diffhv<0)
     diffhv=-diffhv;
   }
  }
  if(asta && asta->timemsec_stream && astv && astv->timemsec_stream){
   diffav=asta->timemsec_stream-astv->timemsec_stream;
   if(diffav<0)
    diffav=-diffav;
  }
  if((diffha<diffhv) && (diffha<diffav))
   miis->timemsec=asta->timemsec_stream;
  else if((diffhv<diffha) && (diffhv<diffav))
   miis->timemsec=astv->timemsec_stream;
  else if(asta && asta->timemsec_stream)
   miis->timemsec=asta->timemsec_stream;
  else if(astv && astv->timemsec_stream)
   miis->timemsec=astv->timemsec_stream;
  else
   miis->timemsec=240000;
 }
 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%d %d %d %d %d %d",avii->timemsec_avih,
 // asta->timemsec_stream,astv->timemsec_stream,avii->is_odml,avii->riff_end,miis->filesize);

 if(openmode&MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB)
  if(!avi_load_index(avii,fbfs,fbds))
   goto err_out_chk;

 avi_assign_audio(avii,miis,openmode);
#ifdef MPXPLAY_LINK_VIDEO
 avi_assign_video(avii,miis,openmode);
#endif

 return MPXPLAY_ERROR_INFILE_OK;

err_out_chk:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void AVI_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct avi_demuxer_data_s *avii=(struct avi_demuxer_data_s *)miis->private_data;
 unsigned int i;
 if(avii){
  if(avii->avs){
   for(i=0;i<avii->nb_streams;i++){
    avi_stream_data_s *ast=&avii->avs[i];
    if(ast->extradata)
     free(ast->extradata);
    if(ast->seek_table)
     free(ast->seek_table);
    if(ast->seek_flags)
     free(ast->seek_flags);
   }
   free(avii->avs);
  }
  free(avii);
 }
 fbfs->fclose(fbds);
}

static unsigned int avi_get_stream_idx(mpxp_uint8_t *d)
{
 if(d[0]>='0' && d[0]<='9' && d[1]>='0' && d[1]<='9')
  return ((unsigned int)(d[0]-'0') * 10 + (d[1] - '0'));
 return 127; //invalid stream ID
}

static int AVI_infile_decode(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct avi_demuxer_data_s *avi=(struct avi_demuxer_data_s *)miis->private_data;
 mpxp_uint32_t framesize=0,streamnum;
 mpxp_filesize_t filesize=fbfs->filelength(fbds);
 mpxp_uint8_t d[10];

skip_frame:

 if(framesize){
  if(framesize&1) // word align
   framesize++;
  if(fbfs->fseek(fbds,framesize,SEEK_CUR)<0)
   return MPXPLAY_ERROR_INFILE_RESYNC;
 }

resync:

 avi->frame_offset=fbfs->ftell(fbds);
 if(fbfs->fread(fbds,&d[0],8)!=8){
  if(fbfs->fseek(fbds,avi->frame_offset,SEEK_SET)!=avi->frame_offset)
   return MPXPLAY_ERROR_INFILE_RESYNC;
  return MPXPLAY_ERROR_INFILE_NODATA;
 }
#ifdef MPXPLAY_AVIDEBUG_DEMUX
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%c%c%c%c %d",d[0],d[1],d[2],d[3],PDS_GETB_LE32(&d[4]));
#endif

 do{
  framesize=PDS_GETB_LE32(&d[4]);

#ifdef MPXPLAY_AVIDEBUG_DEMUX
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%c%c%c%c fp:%9d fs:%9d me:%9d re:%9d %d bs:%d",d[0],d[1],d[2],d[3],(unsigned long)avi->frame_offset,
   (unsigned long)(framesize),(unsigned long)avi->movi_end,(unsigned long)avi->riff_end,avi->is_odml,
   miis->audio_stream->bs_bufsize);
#endif

  if(avi->frame_offset>=avi->movi_end){
   if(!avi->is_odml)
    return MPXPLAY_ERROR_INFILE_NODATA;
   if(avi->frame_offset<avi->riff_end){
    if(fbfs->fseek(fbds,avi->riff_end,SEEK_SET)!=avi->riff_end)
     return MPXPLAY_ERROR_INFILE_RESYNC;
    goto resync;
   }
   if((avi->frame_offset+framesize)>filesize)
    goto sync_skip;
  }else{
   if(((avi->frame_offset+framesize)>avi->movi_end))
    goto sync_skip;
  }

  if(!PDS_GETB_LE32(&d[0])){
   if(!framesize)
    goto resync;
   goto sync_skip;
  }

  if(PDS_GETB_LE32(&d[0])==PDS_GET4C_LE32('J','U','N','K') || PDS_GETB_LE32(&d[0])==PDS_GET4C_LE32('i','d','x','1'))
   goto skip_frame;

  if((d[0]=='i') && (d[1]=='x')){
   streamnum=avi_get_stream_idx(&d[2]);
   if(streamnum<avi->nb_streams)
    goto skip_frame;
   goto sync_skip;
  }

  if(PDS_GETB_LE32(&d[0])==PDS_GET4C_LE32('L','I','S','T')){
   if(fbfs->fseek(fbds,4,SEEK_CUR)<0)
    return MPXPLAY_ERROR_INFILE_RESYNC;
   goto resync;
  }

  streamnum=avi_get_stream_idx(&d[0]);
  if(d[2]=='i' && d[3]=='x' && (streamnum<avi->nb_streams))
   goto skip_frame;

  if(streamnum<avi->nb_streams){
   avi_stream_data_s *ast=&avi->avs[streamnum];
   struct mpxplay_streampacket_info_s *spi;
#ifdef MPXPLAY_AVIDEBUG_DEMUX
   mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"sn:%d nbs:%d fs:%d abs:%d ast:%d %8.8X %8.8X",
    streamnum,avi->nb_streams,framesize,ast->bufsize,ast->streamtype,(long)avi->ast_audio,(long)ast);
#endif

   switch(ast->streamtype){
    case MPXPLAY_SPI_STREAMTYPE_AUDIO:
     if(d[2]!='w' || d[3]!='b')
      goto sync_skip;
     if(ast!=avi->ast_audio)
      goto skip_frame;
     spi=miis->audio_stream;
     break;
#ifdef MPXPLAY_LINK_VIDEO
    case MPXPLAY_SPI_STREAMTYPE_VIDEO:
     if(d[2]!='d' || (d[3]!='c' && d[3]!='b'))
      goto sync_skip;
     if(ast!=avi->ast_video)
      goto skip_frame;
     spi=miis->video_stream;
     break;
#endif
    default:
     if((d[2]>='a' && d[2]<='z' && d[3]>='a' && d[3]<='z'))
      goto skip_frame;
     goto sync_skip;
   }
   if(framesize>spi->bs_bufsize)
    goto skip_frame;
   spi->bs_leftbytes=fbfs->fread(fbds,spi->bitstreambuf,framesize);
   if(spi->bs_leftbytes!=framesize){
    spi->bs_leftbytes=0;
    if(fbfs->fseek(fbds,avi->frame_offset,SEEK_SET)!=avi->frame_offset)
     return MPXPLAY_ERROR_INFILE_RESYNC;
    return MPXPLAY_ERROR_INFILE_NODATA;
   }
   if(framesize&1)
    fbfs->get_byte(fbds); // word align
   if(miis->audio_stream->bs_leftbytes)
    break;
  }

sync_skip:
  PDS_PUTB_LE64(&d[0],PDS_GETB_LE64(&d[1]));
  if(fbfs->fread(fbds,&d[7],1)!=1)
   return MPXPLAY_ERROR_INFILE_NODATA;
  avi->frame_offset++;
 }while(1);

 return MPXPLAY_ERROR_INFILE_OK;
}

static long AVI_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
 struct avi_demuxer_data_s *avi=(struct avi_demuxer_data_s *)miis->private_data;
 avi_stream_data_s *ast;
 mpxp_filesize_t newfilepos=0,filesize=fbfs->filelength(fbds);
 mpxp_filesize_t riffmovi=(avi->riff_end>avi->movi_end)? (avi->riff_end-avi->movi_end):0;
 mpxp_filesize_t datasize=filesize-avi->movi_list-riffmovi;
 long aviframe;

#ifdef MPXPLAY_LINK_VIDEO
 if(miis->seektype&MPX_SEEKTYPE_VIDEO){
  ast=avi->ast_video;
  if(!ast)
   ast=avi->ast_audio;
 }else
#endif
 {
  ast=avi->ast_audio;
  if(!ast)
   ast=avi->ast_video;
 }

 if(!ast)
  return MPXPLAY_ERROR_INFILE_EOF;

 if((ast->seek_entries>1) && ast->seek_table){
  mpxp_filesize_t last_seek_point=ast->seek_table[ast->seek_entries-1];
  float avgbytes_per_aviframe=(last_seek_point-1-avi->movi_list+(ast->seek_entries-1)/2)/(ast->seek_entries-1); // audio + video block
  long nb_frames=datasize/avgbytes_per_aviframe;

  if(nb_frames<=ast->seek_entries)
   nb_frames=ast->seek_entries;
  else if((nb_frames/5)>ast->seek_entries)
   goto do_average_seek;

  aviframe=(long)(((float)nb_frames*(float)newmpxframenum+((miis->seektype&MPX_SEEKTYPE_BACKWARD)? -(miis->allframes/2):(miis->allframes/2)))/(float)miis->allframes);
  if(aviframe<0)
   aviframe=0;
  if(aviframe<ast->seek_entries)
   newfilepos=ast->seek_table[aviframe];
  else{
   if(miis->seektype&MPX_SEEKTYPE_RELATIVE){
    if(miis->seektype&MPX_SEEKTYPE_BACKWARD){
     aviframe-=15; // !!! needs a time based calculation
     if(aviframe<ast->seek_entries)
      aviframe=ast->seek_entries;
    }else
     aviframe+=15; // !!!
   }
   newfilepos=(mpxp_filesize_t)((float)(aviframe-ast->seek_entries)*avgbytes_per_aviframe)+last_seek_point;
   if(newfilepos>=avi->movi_end)
    newfilepos+=riffmovi;
  }
  newmpxframenum=(long)((float)miis->allframes*(float)aviframe/(float)nb_frames);
#ifdef MPXPLAY_AVIDEBUG_SEEK
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"i:%d n:%d e:%d f:%d",aviframe,nb_frames,ast->seek_entries,(long)newfilepos);
#endif
 }else{
do_average_seek:
  if(miis->seektype&MPX_SEEKTYPE_RELATIVE){
   if(miis->seektype&MPX_SEEKTYPE_BACKWARD){
    newmpxframenum-=64; // !!! needs a time based calculation
    if(newmpxframenum<0)
     newmpxframenum=0;
   }else
    newmpxframenum+=64; // !!!
  }
  newfilepos=(mpxp_filesize_t)((float)datasize*(float)newmpxframenum/(float)miis->allframes);
  newfilepos+=avi->movi_list;
  if(newfilepos>=avi->movi_end)
   newfilepos+=riffmovi;
#ifdef MPXPLAY_AVIDEBUG_SEEK
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"se:%d fp:%d re:%d me:%d",ast->seek_entries,(long)newfilepos,(long)avi->riff_end,(long)avi->movi_end);
#endif
 }

 if(fbfs->fseek(fbds,newfilepos,SEEK_SET)<0)
  return MPXPLAY_ERROR_INFILE_EOF;

 return newmpxframenum;
}

//----------------------------------------------------------------

static unsigned int avi_read_header(struct avi_demuxer_data_s *avi,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 avi_stream_data_s *ast;
 mpxp_uint32_t tag, tag1, size;
 int codec_type, stream_index;

 tag=fbfs->get_le32(fbds);
 if((tag!=PDS_GET4C_LE32('R','I','F','F')) && (tag!=PDS_GET4C_LE32('O','N','2',' ')))
  return 0;

 avi->riff_end  = fbfs->get_le32(fbds);  // RIFF chunk size
 avi->riff_end += fbfs->ftell(fbds);     // RIFF chunk end
 tag = fbfs->get_le32(fbds);
 if((tag!=PDS_GET4C_LE32('A','V','I',' ')) && (tag!=PDS_GET4C_LE32('A','V','I','X')) && (tag!=PDS_GET4C_LE32('O','N','2','f')))
  return 0;
 avi->movi_end = fbfs->filelength(fbds);

 stream_index = -1;
 codec_type = -1;

 do{
#ifdef MPXPLAY_AVIDEBUG_HEADER
  mpxp_filesize_t pos=fbfs->ftell(fbds);
#endif
  if(fbfs->eof(fbds))
   break;
  tag  = fbfs->get_le32(fbds);
  size = fbfs->get_le32(fbds);
  if(!tag)
   break;
  if(!size && (tag!=PDS_GET4C_LE32('R','I','F','F')) && (tag!=PDS_GET4C_LE32('L','I','S','T')))
   continue;
  size+=size&1;

#ifdef MPXPLAY_AVIDEBUG_HEADER
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"header tag:%4.4s %8.8X size:%d pos:%d",((char *)&tag),tag,size,(long)pos);
#endif

  switch(tag){
   case PDS_GET4C_LE32('R','I','F','F'):
    fbfs->get_le32(fbds);
    break;
   case PDS_GET4C_LE32('L','I','S','T'):
    tag1 = fbfs->get_le32(fbds);
#ifdef MPXPLAY_AVIDEBUG_HEADER
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"header tag1:%4.4s pos:%d",((char *)&tag1),(long)fbfs->ftell(fbds));
#endif
    if(tag1 == PDS_GET4C_LE32('m','o','v','i')) {
     avi->movi_list = fbfs->ftell(fbds) - 4;
     if(size)
      avi->movi_end = avi->movi_list + size;
     goto end_of_header;
    }
    break;
   case PDS_GET4C_LE32('d','m','l','h'):
    avi->is_odml = 1;
    if(fbfs->fseek(fbds, size, SEEK_CUR)<0)
     return 0;
    break;
   case PDS_GET4C_LE32('a','v','i','h'):
    avi->microsec_per_frame = fbfs->get_le32(fbds);
    fbfs->fseek(fbds, 3*4, SEEK_CUR); // BitRate, Reserved1, Flags
    avi->total_frames = fbfs->get_le32(fbds);
    fbfs->fseek(fbds, 4, SEEK_CUR);   // InitialFrames
    avi->nb_streams = fbfs->get_le32(fbds);
    avi->avs=(struct avi_stream_data_s *)calloc(avi->nb_streams,sizeof(struct avi_stream_data_s));
    if(!avi->avs)
     goto err_out_aviheader;
    fbfs->fseek(fbds, size - 7*4, SEEK_CUR);
    if(avi->total_frames && avi->microsec_per_frame)
     avi->timemsec_avih=(float)avi->total_frames*(float)avi->microsec_per_frame/1000.0; // /1000000*MPXPLAY_TIME_BASE
#ifdef MPXPLAY_AVIDEBUG_INFO
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"avih ms:%d tf:%d mpf:%d",(long)avi->timemsec_avih,avi->total_frames,avi->microsec_per_frame);
#endif
    break;
   case PDS_GET4C_LE32('s','t','r','h'):
    tag1 = fbfs->get_le32(fbds);

    stream_index++;
    if((stream_index>=avi->nb_streams) || (tag1==PDS_GET4C_LE32('p','a','d','s'))){
     fbfs->fseek(fbds, size - 4, SEEK_CUR);
     break;
    }
    ast = &avi->avs[stream_index];
    ast->stream_index=stream_index;

#ifdef MPXPLAY_AVIDEBUG_HEADER
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"header tag1:%4.4s pos:%d",((char *)&tag1),(long)fbfs->ftell(fbds));
#endif

    ast->codec_tag = fbfs->get_le32(fbds);
    if(tag1 == PDS_GET4C_LE32('i','a','v','s') || tag1 == PDS_GET4C_LE32('i','v','a','s')){
     fbfs->fseek(fbds, 3 * 4, SEEK_CUR);
     ast->scale = fbfs->get_le32(fbds);
     ast->rate  = fbfs->get_le32(fbds);
     fbfs->fseek(fbds, size - 7*4, SEEK_CUR);
     break;
    }

    fbfs->get_le32(fbds); // flags
    fbfs->get_le16(fbds); // priority
    fbfs->get_le16(fbds); // language
    fbfs->get_le32(fbds); // initial frame

    ast->scale = fbfs->get_le32(fbds);
    ast->rate  = fbfs->get_le32(fbds);

    ast->start    = fbfs->get_le32(fbds);
    ast->duration = fbfs->get_le32(fbds);
    ast->bufsize = fbfs->get_le32(fbds); // buffer size
                   fbfs->get_le32(fbds); // quality
    ast->sample_size = fbfs->get_le32(fbds); // sample ssize

    if(ast->rate)
     ast->timemsec_stream=((float)MPXPLAY_TIME_BASE*(float)ast->duration/(float)ast->rate*(float)ast->scale);

    switch(tag1) {
     case PDS_GET4C_LE32('v','i','d','s'):
      codec_type = MPXPLAY_SPI_STREAMTYPE_VIDEO;
      ast->sample_size = 0;
      break;
     case PDS_GET4C_LE32('a','u','d','s'):
      codec_type = MPXPLAY_SPI_STREAMTYPE_AUDIO;
      break;
     case PDS_GET4C_LE32('t','x','t','s'):
      codec_type = MPXPLAY_SPI_STREAMTYPE_DATA; //CODEC_TYPE_SUB ?  FIXME
      break;
     case PDS_GET4C_LE32('p','a','d','s'):
      codec_type = MPXPLAY_SPI_STREAMTYPE_UNKNOWN;
      stream_index--;
      break;
     default:
      goto err_out_aviheader;
    }

    if(fbfs->fseek(fbds, size - 12 * 4, SEEK_CUR)<0)
     return 0;
    break;
   case PDS_GET4C_LE32('s','t','r','f'):
#ifdef MPXPLAY_AVIDEBUG_HEADER
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"strf si:%d ct:%d",stream_index,codec_type);
#endif
    if((stream_index>=avi->nb_streams) || (stream_index<0)){
     if(fbfs->fseek(fbds, size, SEEK_CUR)<0)
      return 0;
    }else{
     ast = &avi->avs[stream_index];
     switch(codec_type) {
      case MPXPLAY_SPI_STREAMTYPE_VIDEO:
       ast->streamtype = MPXPLAY_SPI_STREAMTYPE_VIDEO;
#ifdef MPXPLAY_LINK_VIDEO
       fbfs->get_le32(fbds); // size
       ast->video_res_x = fbfs->get_le32(fbds);
       ast->video_res_y = fbfs->get_le32(fbds);
                          fbfs->get_le16(fbds); // panes
       ast->video_bpp   = fbfs->get_le16(fbds); // depth
       ast->codec_tag   = fbfs->get_le32(fbds);
       fbfs->get_le32(fbds); // ImageSize
       fbfs->get_le32(fbds); // XPelsPerMeter
       fbfs->get_le32(fbds); // YPelsPerMeter
       fbfs->get_le32(fbds); // ClrUsed
       fbfs->get_le32(fbds); // ClrImportant
       if((size>(10*4)) && (size<(1<<30))){
        ast->extradata_size= size - 10*4;
        ast->extradata = malloc(ast->extradata_size + MPXPLAY_SPI_EXTRADATA_PADDING);
        if(!ast->extradata)
         goto err_out_aviheader;
        if(fbfs->fread(fbds,ast->extradata,ast->extradata_size)!=ast->extradata_size)
         goto err_out_aviheader;
        pds_memset(ast->extradata+ast->extradata_size,0,MPXPLAY_SPI_EXTRADATA_PADDING);
        if(fbfs->fseek(fbds,ast->extradata_size,SEEK_CUR)<0)
         goto err_out_aviheader;
       }
       if(ast->extradata_size & 1)
        fbfs->get_byte(fbds);
#else
       if(fbfs->fseek(fbds, size, SEEK_CUR)<0)
        goto err_out_aviheader;
#endif
       break;
      case MPXPLAY_SPI_STREAMTYPE_AUDIO:
       if(!avi_read_wavheader(ast,fbfs,fbds,size))
        goto err_out_aviheader;
       ast->need_parsing = 1;
       break;
      default:
       ast->streamtype = MPXPLAY_SPI_STREAMTYPE_DATA;
       ast->codec_tag= 0;
       if(fbfs->fseek(fbds, size, SEEK_CUR)<0)
        goto err_out_aviheader;
       break;
     }
    }
    break;
   default:
    if(fbfs->fseek(fbds, size, SEEK_CUR)<0)
     return 0;
    break;
  }
 }while(1);

end_of_header:
 if(stream_index != (avi->nb_streams - 1)){
err_out_aviheader:
  return 0;
 }
 if(avi->riff_end<avi->movi_end)
  avi->riff_end=avi->movi_end;

 return 1;
}

static unsigned int avi_read_wavheader(avi_stream_data_s *ast, struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds, int size)
{
 ast->streamtype = MPXPLAY_SPI_STREAMTYPE_AUDIO;
 ast->codec_tag  = fbfs->get_le16(fbds);
 ast->channels   = fbfs->get_le16(fbds);
 ast->sample_rate= fbfs->get_le32(fbds);
 ast->bit_rate   = fbfs->get_le32(fbds) * 8;
 ast->block_align= fbfs->get_le16(fbds);
 if(size==14)  // plain vanilla WAVEFORMAT
  ast->bits_per_sample = 8;
 else
  ast->bits_per_sample = fbfs->get_le16(fbds);

 if(size>=18){  // WAVEFORMATEX
  int left=size-18;
  ast->extradata_size = fbfs->get_le16(fbds);
  if(ast->extradata_size){
   if(ast->extradata_size > left)
    ast->extradata_size = left;
   if(ast->extradata_size){
    ast->extradata = malloc(ast->extradata_size + MPXPLAY_SPI_EXTRADATA_PADDING);
    if(!ast->extradata)
     return 0;
    if(fbfs->fread(fbds, ast->extradata, ast->extradata_size)!=ast->extradata_size)
     return 0;
    pds_memset(ast->extradata+ast->extradata_size,0,MPXPLAY_SPI_EXTRADATA_PADDING);
   }
   left-=ast->extradata_size;
  }
  //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"ct:%4.4X left:%d",(long)ast->codec_tag,left);
  if(left>0)
   if(fbfs->fseek(fbds, left, SEEK_CUR)<0)
    return 0;
 }

 return 1;
}

typedef struct avi_idx1_tmp_s{
 unsigned int  streamnum;
 unsigned long flags;
 unsigned long pos;
 unsigned long len;
}avi_idx1_tmp_s;

static unsigned int avi_read_idx1(struct avi_demuxer_data_s *avi,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,long size)
{
 int nb_index_entries,i;
 unsigned long last_pos;
 struct avi_idx1_tmp_s *indx_tmp=NULL,*indxp;
 unsigned int *entrycounts=(unsigned int *)alloca(avi->nb_streams*sizeof(unsigned int));

 if(!entrycounts)
  return 0;

 nb_index_entries = size / 16;
 if(nb_index_entries <= 0)
  return 0;

 i=nb_index_entries*sizeof(struct avi_idx1_tmp_s);
#ifdef MPXPLAY_AVIDEBUG_HEADER
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"idx1 e:%d i:%d size:%d",nb_index_entries,i,size);
#endif
 indx_tmp=indxp=(struct avi_idx1_tmp_s *)malloc(i);
 if(!indx_tmp)
  return 0;

 if(fbfs->fread(fbds,indx_tmp,i)!=i)
  goto err_out_idx1;

 pds_memset(entrycounts,0,avi->nb_streams*sizeof(unsigned int));

 i=nb_index_entries;
 do{
  unsigned int streamnum,tag;
  tag  = indxp->streamnum;
  tag &= 0xffff;
  tag -= (((unsigned long)'0')<<8)|((unsigned long)'0');
  streamnum = (((unsigned char)tag)<<3)+(((unsigned char)tag)<<1); //streamnum  = ((tag & 0xff) - '0') * 10;
  streamnum+= ((unsigned char *)&tag)[1];                          //streamnum += ((tag >> 8) & 0xff) - '0';

  indxp->streamnum=streamnum;
  if(streamnum < avi->nb_streams)
   entrycounts[streamnum]++;

  indxp++;
 }while(--i);

#ifdef MPXPLAY_AVIDEBUG_HEADER
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"nbs:%d ec0:%d ec1:%d",avi->nb_streams,entrycounts[0],entrycounts[1]);
#endif

 for(i=0;i<avi->nb_streams;i++){
  if(entrycounts[i]){
   avi_stream_data_s *ast=&avi->avs[i];
   ast->seek_table=(mpxp_uint32_t *)malloc(entrycounts[i]*sizeof(mpxp_uint32_t));
   if(!ast->seek_table)
    goto err_out_idx1;
   if(ast->streamtype==MPXPLAY_SPI_STREAMTYPE_VIDEO){
    ast->seek_flags=(mpxp_uint8_t *)malloc(entrycounts[i]*sizeof(mpxp_uint8_t));
    if(!ast->seek_flags)
     goto err_out_idx1;
    pds_memset(ast->seek_flags,0,entrycounts[i]*sizeof(mpxp_uint8_t));
   }
  }
 }

 indxp=indx_tmp;
 if(indxp->pos > avi->movi_list)
  avi->movi_list= 0; //FIXME better check

 last_pos=0;
 i=nb_index_entries;
 do{
  if((indxp->pos>last_pos) && (indxp->streamnum<avi->nb_streams)){
   avi_stream_data_s *ast=&avi->avs[indxp->streamnum];
   ast->seek_table[ast->seek_entries]=indxp->pos;
   last_pos = indxp->pos;
#ifdef MPXPLAY_LINK_VIDEO
   if((ast->streamtype==MPXPLAY_SPI_STREAMTYPE_VIDEO) && (indxp->flags&AVIFLAG_IDX1_KEYFRAME))
    ast->seek_flags[ast->seek_entries]=AST_SEEKFLAG_KEYFRAME;
#endif
   ast->seek_entries++;
  }//else
   //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"index error pos:%d sn:%d",indxp->pos,indxp->streamnum);
  indxp++;
 }while(--i);

#ifdef MPXPLAY_AVIDEBUG_INFO
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"idx1 se0:%d s0p:%d se1:%d s1p:%d",avi->avs[0].seek_entries,
  avi->avs[0].seek_table[avi->avs[0].seek_entries-1],avi->avs[1].seek_entries,avi->avs[1].seek_table[avi->avs[1].seek_entries-1]);
#endif

 free(indx_tmp);

 return 1;

err_out_idx1:
 if(indx_tmp)
  free(indx_tmp);
 return 0;
}

static unsigned int avi_load_index(struct avi_demuxer_data_s *avi,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 mpxp_uint32_t tag, size;
 mpxp_filesize_t oldpos= fbfs->ftell(fbds);

 fbfs->fseek(fbds, avi->movi_end, SEEK_SET);

 for(;;){
  if(fbfs->eof(fbds))
   break;
  tag = fbfs->get_le32(fbds);
  size = fbfs->get_le32(fbds);
  switch(tag) {
   case PDS_GET4C_LE32('i', 'd', 'x', '1'):
    if(!avi_read_idx1(avi,fbfs,fbds,size))
     goto skip;
    else
     goto the_end;
    break;
   default:
    skip:
    size += (size & 1);
    fbfs->fseek(fbds, size, SEEK_CUR);
    break;
  }
 }
the_end:
 //avi->non_interleaved |= guess_ni_flag(s);
 fbfs->fseek(fbds, oldpos, SEEK_SET);
 return 1;
}

//------------------------------------------------------------------

static struct avi_stream_data_s *avi_get_stream(struct avi_demuxer_data_s *avi,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi)
{
 unsigned int i,streamtype_count=0;
 struct avi_stream_data_s *ast=avi->avs,*found_stream=NULL;

 for(i=0;i<avi->nb_streams;i++){
  if(ast->streamtype==streamtype){
   if(!spi || (streamtype_count<=spi->stream_select))
    found_stream=ast;
   streamtype_count++;
  }
  ast++;
 }
 if(spi)
  spi->nb_streams=streamtype_count;
 return found_stream;
}

static void avi_assign_audio(struct avi_demuxer_data_s *avi,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 struct mpxplay_streampacket_info_s  *spi=miis->audio_stream;
 struct avi_stream_data_s *ast=avi->ast_audio;

 if(!ast)
  return;

 spi->streamtype  =ast->streamtype;
 spi->wave_id     =ast->codec_tag;
 spi->block_align =ast->block_align;
 spi->bs_framesize=ast->bufsize;
 funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_CONTAINER);
 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_DECODER);
  if(ast->need_parsing)
   funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_PARSING);
 }
 spi->extradata=ast->extradata;
 spi->extradata_size=ast->extradata_size;

 adi->filechannels=adi->outchannels=ast->channels;
 adi->freq=ast->sample_rate;
 adi->bits=ast->bits_per_sample;
 if(spi->wave_id>7){ // > PCM_MULAW
  adi->bitrate=(ast->bit_rate+500)/1000;
  if((ast->scale>1) && ast->bit_rate) // ???
   ast->nb_frames=ast->duration;
 }

#ifdef MPXPLAY_AVIDEBUG_INFO
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"AUDIO wd:%4.4X fs:%d c:%d f:%d b:%d br:%d d:%d r:%d s:%d ss:%d ba:%d",
  spi->wave_id,ast->bufsize,adi->filechannels,adi->freq,adi->bits,
  ast->bit_rate,ast->duration,ast->rate,ast->scale,ast->sample_size,spi->block_align);
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"time:%d (%dm%2.2ds) odml:%d seek_entries:%d nbf:%d",ast->timemsec_stream,
  (ast->timemsec_stream/60000),(ast->timemsec_stream%60000)/1000,avi->is_odml,ast->seek_entries,ast->nb_frames);
 /*if(spi->extradata){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%8.8X %d %2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
   spi->extradata,spi->extradata_size,spi->extradata[0],spi->extradata[1],spi->extradata[2],spi->extradata[3],spi->extradata[4],spi->extradata[5]);
 }*/
#endif
}

#ifdef MPXPLAY_LINK_VIDEO
static void avi_assign_video(struct avi_demuxer_data_s *avi,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct mpxplay_video_decoder_info_s *vdi=miis->video_decoder_infos;
 struct mpxplay_streampacket_info_s  *spi=miis->video_stream;
 struct avi_stream_data_s *ast=avi->ast_video;

 if(!ast)
  return;

 spi->streamtype=ast->streamtype;
 spi->wave_id   =ast->codec_tag;
 spi->bs_framesize=ast->bufsize;
 funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_CONTAINER);
 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_DECODER);
  //if(ast->need_parsing)
  // funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_PARSING);
 }
 spi->extradata=ast->extradata;
 spi->extradata_size=ast->extradata_size;

 ast->nb_frames=ast->duration;
 vdi->video_res_x=ast->video_res_x;
 vdi->video_res_y=ast->video_res_y;
 vdi->video_bitrate=(ast->bit_rate+500)/1000;
 vdi->video_frames=ast->duration;
 vdi->video_fps=ast->rate/ast->scale;

#ifdef MPXPLAY_AVIDEBUG_INFO
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"VIDEO vt:%d x:%d y:%d fps:%.2f d:%d sc:%d ra:%d br:%d bs:%d",
  ast->timemsec_stream,ast->video_res_x,ast->video_res_y,(float)((float)ast->rate/(float)ast->scale),
  ast->duration,ast->scale,ast->rate,ast->bit_rate,ast->bufsize);
#endif
}
#endif

struct mpxplay_infile_func_s IN_AVI_funcs={
 0,
 NULL,
 NULL,
 &AVI_infile_open,
 &AVI_infile_close,
 &AVI_infile_decode,
 &AVI_infile_fseek,
 NULL,
 NULL,
 NULL,
 NULL,
 {"AVI",NULL}
};

#endif // MPXPLAY_LINK_INFILE_AVI
