//**************************************************************************
//*                     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: OGG file demuxing
//requires the decoders\ad_vorbi\vorbis.lib file (and include files)
//(bitwise.c and framing.c is part of the vorbis.lib)

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_OGG

#include "..\decoders\ad_vorbi\ogg.h"
#include "deparser\tagging.h"
#include "display\display.h"
#include <string.h>

#define INOGG_READHEAD_DETECTSIZE 32768
#define INOGG_READHEAD_CHECKSIZE  65536

#define INOGG_MAX_STREAMS 8
#define MPXPLAY_CODECID_THEORA 0x00028000

typedef struct ogg_stream_data_s{
 struct ogg_parse_data_s *parsedatas;
 mpxp_uint8_t *extradata;
 long extradata_size;
 long audio_channels;
 long audio_freq;
 long audio_bits;
 long audio_bitrate;
 mpxp_int64_t first_granulepos;
 mpxp_int64_t pcmdatalen;
 ogg_stream_state os_in;
 ogg_packet       ops;
 ogg_stream_state os_out;
 char metaheader_save[8];
}ogg_stream_data_s;

typedef struct ogg_demuxer_data_s {
 unsigned int current_decoder_part;
 unsigned int nb_streams;
 struct ogg_stream_data_s *stream_audio;
 struct ogg_stream_data_s *stream_video;
 ogg_sync_state   oys;
 ogg_page         ogs;
 struct ogg_stream_data_s ogg_streams[INOGG_MAX_STREAMS];

 unsigned long metadata_frame_beginpos;
 unsigned long metadata_frame_cursize;
 unsigned char *metadata_frame_extradata; // codebook data
 unsigned long metadata_frame_extrasize;
 struct vorbiscomment_info_s vci;
 ogg_packet       metadata_op;
 ogg_page         metadata_og;
}ogg_demuxer_data_s;

typedef struct ogg_parse_data_s{
 char *longname;
 unsigned int streamtype;
 unsigned long waveid;
 unsigned int (*parse_func)(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes);
 unsigned int (*metaheadread_func)(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs);
 unsigned int (*metaheadwrite_func)(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size);
}ogg_parse_data_s;

static struct ogg_stream_data_s *ogg_find_stream_by_serialno(struct ogg_demuxer_data_s *omip,unsigned long serialno);
static struct ogg_stream_data_s *ogg_find_stream_by_streamtype(struct ogg_demuxer_data_s *omip,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi);
static void   ogg_reset_streams(struct ogg_demuxer_data_s *omip);

static unsigned int ogg_parse_vorbis(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes);
static unsigned int ogg_parse_flac(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes);
static unsigned int ogg_parse_speex(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes);
//static unsigned int ogg_parse_theora(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes);

static unsigned int ogg_metaheadread_vorbis(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs);
static unsigned int ogg_metaheadread_flac(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs);
//static unsigned int ogg_metaheadread_theora(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs);

static unsigned int ogg_metaheadwrite_vorbis(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long);
static unsigned int ogg_metaheadwrite_flac(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long);
//static unsigned int ogg_metaheadwrite_theora(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long);

static ogg_parse_data_s ogg_parse_datas[]={
 {"OgVorbis",MPXPLAY_SPI_STREAMTYPE_AUDIO,MPXPLAY_WAVEID_VORBIS,&ogg_parse_vorbis,&ogg_metaheadread_vorbis,&ogg_metaheadwrite_vorbis},
 {"Ogg-Flac",MPXPLAY_SPI_STREAMTYPE_AUDIO,MPXPLAY_WAVEID_FLAC  ,&ogg_parse_flac,&ogg_metaheadread_flac,&ogg_metaheadwrite_flac},
 {"OggSpeex",MPXPLAY_SPI_STREAMTYPE_AUDIO,MPXPLAY_WAVEID_SPEEX ,&ogg_parse_speex,NULL,NULL},
 //{"OgTheora",MPXPLAY_SPI_STREAMTYPE_VIDEO,MPXPLAY_CODECID_THEORA,&ogg_parse_theora,&ogg_metaheadread_theora,&metaheadwrite_theora},
 {NULL,0}
};

static int refill_sync_buff(struct ogg_demuxer_data_s *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);
static ogg_int64_t ogg_get_pcmlength(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,unsigned long stream_serialno);

static int ogg_assign_values_audio(struct ogg_demuxer_data_s *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct ogg_stream_data_s *osds=omip->stream_audio;
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 mpxp_filesize_t filelen=fbfs->filelength(fbds);
 long bytes_per_sample;

 if(!osds->audio_channels || !osds->audio_freq || !osds->audio_bits)
  return 0;

 if(osds->audio_channels) adi->filechannels=adi->outchannels=osds->audio_channels;
 if(osds->audio_freq)     adi->freq=osds->audio_freq;
 if(osds->audio_bits)     adi->bits=osds->audio_bits;

 if((miis->filesize!=filelen) || !miis->timemsec){
  miis->filesize=filelen;
  if(!osds->pcmdatalen)
   osds->pcmdatalen=ogg_get_pcmlength(fbfs,fbds,osds->os_in.serialno)-osds->first_granulepos;
  if(!osds->pcmdatalen)
   return 0;
  if(osds->audio_freq)
   miis->timemsec=(float)osds->pcmdatalen*1000.0/(float)osds->audio_freq;
 }

 if(osds->parsedatas->waveid==MPXPLAY_WAVEID_FLAC){
  if(!osds->pcmdatalen)
   osds->pcmdatalen=(float)miis->timemsec/1000.0*(float)osds->audio_freq;
  if(!osds->pcmdatalen)
   return 0;
  adi->bitratetext=malloc(MPXPLAY_ADITEXTSIZE_BITRATE+8);
  if(!adi->bitratetext)
   return 0;
  bytes_per_sample=(adi->bits+7)/8;
  sprintf(adi->bitratetext,"%2d/%2.1f%%",adi->bits,100.0*(float)miis->filesize/(float)osds->pcmdatalen/(float)bytes_per_sample/(float)adi->filechannels);
  adi->bitratetext[MPXPLAY_ADITEXTSIZE_BITRATE]=0;
 }else{
  if((omip->nb_streams>1) && osds->audio_bitrate)
   adi->bitrate=osds->audio_bitrate/1000;
  else if(miis->timemsec)
   adi->bitrate=(long)((float)filelen*8.0/(float)miis->timemsec);
 }

 return 1;
}

static int inogg_header_check(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,unsigned long headchecksize)
{
 struct ogg_demuxer_data_s *omip;
 struct ogg_stream_data_s *osds;
 unsigned int sync_retry=headchecksize/OGG_SYNC_BUFFER_SIZE+1;

 if(fbfs->filelength(fbds)<128) // ???
  goto err_out_chkhead;

 omip=(struct ogg_demuxer_data_s *)calloc(1,sizeof(struct ogg_demuxer_data_s));
 if(!omip)
  goto err_out_chkhead;
 miis->private_data=omip;

 ogg_sync_init(&(omip->oys));
 refill_sync_buff(omip,fbfs,fbds);

 do{
  int result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result<0)
   goto err_out_chkhead;
  if(result==0){
   if(!(--sync_retry))
    goto err_out_chkhead;
   if(!refill_sync_buff(omip,fbfs,fbds))
    goto err_out_chkhead;
  }else{
   struct ogg_parse_data_s *parsedatas;

   osds=ogg_find_stream_by_serialno(omip,ogg_page_serialno(&(omip->ogs)));
   if(osds) // no new frame
    break;
   if(omip->nb_streams>=INOGG_MAX_STREAMS)
    goto err_out_chkhead;

   osds=&omip->ogg_streams[omip->nb_streams++];
   osds->first_granulepos=ogg_page_granulepos(&(omip->ogs));
   if(ogg_stream_init(&(osds->os_in),ogg_page_serialno(&(omip->ogs)))<0)
    goto err_out_chkhead;
   if(ogg_stream_pagein(&(osds->os_in),&(omip->ogs))<0)
    goto err_out_chkhead;
   if(ogg_stream_packetout(&(osds->os_in),&(osds->ops))!=1)
    goto err_out_chkhead;

   parsedatas=&ogg_parse_datas[0];
   do{
    if(parsedatas->parse_func(osds,osds->ops.packet,osds->ops.bytes)){
     osds->parsedatas=parsedatas;
     break;
    }
    parsedatas++;
   }while(parsedatas->longname);
  }
 }while(1);

 return MPXPLAY_ERROR_INFILE_OK;

err_out_chkhead:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static int OGG_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 int retcode;
 struct ogg_demuxer_data_s *omip;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 struct ogg_stream_data_s *osds;
 unsigned long headchecksize=(openmode&MPXPLAY_INFILE_OPENMODE_AUTODETECT)? INOGG_READHEAD_DETECTSIZE:INOGG_READHEAD_CHECKSIZE;

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

 if((retcode=inogg_header_check(fbfs,fbds,filename,miis,headchecksize))!=MPXPLAY_ERROR_OK)
  return retcode;

 omip=miis->private_data;

 osds=ogg_find_stream_by_streamtype(omip,MPXPLAY_SPI_STREAMTYPE_AUDIO,spi);
 if(!osds || !osds->parsedatas)
  goto err_out_open;

 omip->stream_audio=osds;

 spi->wave_id=osds->parsedatas->waveid;

 miis->longname=osds->parsedatas->longname;

 if(!ogg_assign_values_audio(omip,fbfs,fbds,miis))
  goto err_out_open;

 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  spi->extradata = osds->extradata;
  spi->extradata_size = osds->extradata_size;
  spi->streamtype = MPXPLAY_SPI_STREAMTYPE_AUDIO;
  funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_DECODER|MPXPLAY_SPI_FLAG_NEED_PARSING|MPXPLAY_SPI_FLAG_CONTAINER);
 }

 return MPXPLAY_ERROR_INFILE_OK;

err_out_open:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void OGG_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct ogg_demuxer_data_s *omip=miis->private_data;
 struct ogg_stream_data_s *osds;
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 unsigned int i;
 if(omip){
  if(adi && adi->bitratetext)
   free(adi->bitratetext);
  osds=&omip->ogg_streams[0];
  for(i=0;i<omip->nb_streams;i++,osds++){
   if(osds->extradata)
    free(osds->extradata);
   ogg_stream_clear(&(osds->os_in));
   ogg_stream_clear(&(osds->os_out));
  }
  ogg_sync_clear(&(omip->oys));
  free(omip);
 }
 fbfs->fclose(fbds);
}

static int refill_sync_buff(struct ogg_demuxer_data_s *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 char *buffer;
 unsigned long bytes;

 buffer=ogg_sync_buffer(&(omip->oys),OGG_SYNC_BUFFER_SIZE);
 bytes=fbfs->fread(fbds,buffer,OGG_SYNC_BUFFER_SIZE);
 ogg_sync_wrote(&(omip->oys),bytes);

 return bytes;
}

static int read_ogg_frame(struct ogg_demuxer_data_s *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int result,retry=32;
 do{
  result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result>0){
   struct ogg_stream_data_s *osds=ogg_find_stream_by_serialno(omip,ogg_page_serialno(&(omip->ogs)));
   if(osds==omip->stream_audio)
    ogg_stream_pagein(&(osds->os_in),&(omip->ogs));
   else
    result=-1; // new ogg_sync_pageout()
  }else if(result==0){
   if(!refill_sync_buff(omip,fbfs,fbds))
    break;
  }else if(!--retry) // result<0
   break;
 }while(result<=0);
 if(result<=0)
  return 0;
 return 1;
}

static int decode_ogg_frame(struct ogg_demuxer_data_s *omip,struct mpxplay_infile_info_s *miis)
{
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 struct ogg_stream_data_s *osds=omip->stream_audio;
 if(ogg_stream_packetout(&(osds->os_in),&(osds->ops))>0){
  spi->bs_leftbytes=osds->ops.bytes;
  if(!spi->bs_leftbytes)
   return 0;
  pds_memcpy(spi->bitstreambuf,osds->ops.packet,osds->ops.bytes);
  return 1;
 }
 return 0;
}

static int OGG_infile_decode(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct ogg_demuxer_data_s *omip=miis->private_data;
 int eos;

 do{
  eos=0;
  switch(omip->current_decoder_part){
   case 0:if(read_ogg_frame(omip,fbfs,fbds)){
	   omip->current_decoder_part=1;
	  }else{
	   eos=1;
	   break;
	  }
   case 1:if(!decode_ogg_frame(omip,miis)){
	   omip->current_decoder_part=0;
	   break;
	  }else
           eos=2;
  }
 }while(!eos);

 if(eos==1)
  return MPXPLAY_ERROR_INFILE_NODATA;

 return MPXPLAY_ERROR_INFILE_OK;
}

static void OGG_infile_clearbuffs(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,unsigned int seektype)
{
 struct ogg_demuxer_data_s *omip=miis->private_data;

 ogg_sync_reset(&(omip->oys));
 ogg_reset_streams(omip);

 if(seektype&(MPX_SEEKTYPE_BOF|MPX_SEEKTYPE_PAUSE))
  omip->current_decoder_part=0;
}

static long OGG_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newframenum)
{
 mpxp_filesize_t newfilepos=(float)newframenum*(float)miis->filesize/(float)miis->allframes;
 if(fbfs->fseek(fbds,newfilepos,SEEK_SET)<0)
  return MPXPLAY_ERROR_INFILE_EOF;
 return newframenum;
}

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

#define OGG_CHUNKSIZE 8192
#define OGG_CHUNKNUM  8

static ogg_int64_t ogg_get_pcmlength(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,unsigned long stream_serialno)
{
 static ogg_sync_state oy;
 static ogg_page og;
 char *ptr,*buffer;
 ogg_int64_t pcmlen=0;
 mpxp_filesize_t newfilepos=0,oldfilepos=fbfs->ftell(fbds);
 mpxp_filesize_t filelen=fbfs->filelength(fbds);
 long bytes,oyret;
 int retry1=20,retry2;

 if(!filelen)
  return 0;
 ogg_sync_init(&oy);
 ogg_sync_buffer(&oy,OGG_CHUNKSIZE*OGG_CHUNKNUM);
 if(oy.data==NULL)
  return 0;
 do{
  retry2=30;
  pds_memset((void *)(&og),0,sizeof(ogg_page));
  oy.returned=oy.fill=OGG_CHUNKSIZE*OGG_CHUNKNUM;
  newfilepos=0;
  do{
   if((newfilepos-OGG_CHUNKSIZE)>(-filelen)){
    bytes=OGG_CHUNKSIZE;
    newfilepos-=OGG_CHUNKSIZE;
   }else{
    bytes=filelen+newfilepos;
    newfilepos=-(filelen);
   }
   if(fbfs->fseek(fbds,filelen+newfilepos,SEEK_SET)<0)
    break;
   ptr=oy.data+oy.returned;
   buffer=ptr-bytes;
   bytes=fbfs->fread(fbds,buffer,bytes);
   if(bytes<1)
    break;
   while((bytes>0) && (pcmlen<1)){
    while((bytes>0) && (PDS_GETB_LE32(ptr)!=PDS_GET4C_LE32('O','g','g','S'))){
     ptr--;
     bytes--;
     oy.returned--;
    }
    oyret=oy.returned;
    if(PDS_GETB_LE32(ptr)==PDS_GET4C_LE32('O','g','g','S')){
     if((ogg_sync_pageout(&oy,&og)>0) && (ogg_page_serialno(&og)==stream_serialno))
      pcmlen=ogg_page_granulepos(&og);
     if(pcmlen<1)
      if(bytes>0){
       ptr--;
       bytes--;
       oyret--;
      }
    }
    oy.headerbytes=0;
    oy.bodybytes=0;
    oy.returned=oyret;
   }
  }while((pcmlen<1) && (oy.returned>0) && (retry2--));
 }while((pcmlen<1) && (--retry1));
 ogg_sync_clear(&oy);
 fbfs->fseek(fbds,oldfilepos,SEEK_SET);
 return pcmlen;
}

//--------------------------------------------------------------------------
static struct ogg_stream_data_s *ogg_find_stream_by_serialno(struct ogg_demuxer_data_s *omip,unsigned long serialno)
{
 ogg_stream_data_s *osds=&omip->ogg_streams[0];
 unsigned int i;

 for(i=0;i<omip->nb_streams;i++){
  if(osds->os_in.serialno==serialno)
   return osds;
  osds++;
 }

 return NULL;
}

static struct ogg_stream_data_s *ogg_find_stream_by_streamtype(struct ogg_demuxer_data_s *omip,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi)
{
 ogg_stream_data_s *osds=&omip->ogg_streams[0],*osd_found=NULL;
 unsigned int i,typecount=0;

 for(i=0;i<omip->nb_streams;i++){
  if(osds->parsedatas && (osds->parsedatas->streamtype==streamtype)){
   if(!spi || (typecount<=spi->stream_select)){
    osd_found=osds;
    if(!spi)
     break;
   }
   typecount++;
  }
  osds++;
 }

 return osd_found;
}

static void ogg_reset_streams(struct ogg_demuxer_data_s *omip)
{
 ogg_stream_data_s *osds=&omip->ogg_streams[0];
 unsigned int i;

 for(i=0;i<omip->nb_streams;i++,osds++)
  ogg_stream_reset(&(osds->os_in));
}

//------------------------------------------------------------------------
static unsigned int ogg_parse_vorbis(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes)
{
 long bitrate_upper;
 struct mpxplay_bitstreambuf_s bs;

 mpxplay_bitstream_init(&bs,packet,bytes);

 if(mpxplay_bitstream_get_byte(&bs)!=0x01) // packtype
  return 0;
 if(memcmp(mpxplay_bitstream_getbufpos(&bs),"vorbis",6)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs,6);

 if(mpxplay_bitstream_get_le32(&bs)!=0) // version
  return 0;

 osds->audio_channels=mpxplay_bitstream_get_byte(&bs);
 osds->audio_freq    =mpxplay_bitstream_get_le32(&bs);
 osds->audio_bits    =16;
 bitrate_upper       =mpxplay_bitstream_get_le32(&bs);
 osds->audio_bitrate =mpxplay_bitstream_get_le32(&bs);
 if(!osds->audio_bitrate)
  osds->audio_bitrate=(mpxplay_bitstream_get_le32(&bs)+bitrate_upper)/2;

 return 1;
}

static unsigned int ogg_metaheadread_vorbis(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs)
{
 if(mpxplay_bitstream_get_byte(bs)!=0x03) // packtype
  return 0;
 if(memcmp(mpxplay_bitstream_getbufpos(bs),"vorbis",6)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(bs,6);
 return 1;
}

static unsigned int ogg_metaheadwrite_vorbis(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size)
{
 unsigned char data[4];
 data[0]=0x03;
 if(!mpxplay_bitstream_putbytes(bs,data,1)) // packtype
  return 0;
 if(!mpxplay_bitstream_putbytes(bs,"vorbis",6))
  return 0;
 return 1;
}

#define FLAC_METADATA_TYPE_VORBISCOMMENT 4
#define FLAC_METADATA_SIZE_STREAMINFO   34

static unsigned int ogg_parse_flac(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes)
{
 struct mpxplay_bitstreambuf_s bs;

 mpxplay_bitstream_init(&bs,packet,bytes);

 if(mpxplay_bitstream_get_byte(&bs)!=0x7f)
  return 0;
 if(memcmp(mpxplay_bitstream_getbufpos(&bs),"FLAC",4)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs,4+1+1); // skip FLAC and version numbers
 mpxplay_bitstream_get_be16(&bs); // header_packets
 if(memcmp(mpxplay_bitstream_getbufpos(&bs),"fLaC",4)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs,4+4); // skip fLaC and metadata last(1),type(7),size(24 bits)
 if(!osds->extradata){
  osds->extradata=malloc(FLAC_METADATA_SIZE_STREAMINFO+MPXPLAY_SPI_EXTRADATA_PADDING);
  if(osds->extradata){
   pds_memcpy(osds->extradata,mpxplay_bitstream_getbufpos(&bs),FLAC_METADATA_SIZE_STREAMINFO);
   pds_memset(osds->extradata+FLAC_METADATA_SIZE_STREAMINFO,0,MPXPLAY_SPI_EXTRADATA_PADDING);
   osds->extradata_size=FLAC_METADATA_SIZE_STREAMINFO;
  }
 }
                       mpxplay_bitstream_skipbits(&bs, 16+16+24+24);
 osds->audio_freq    = mpxplay_bitstream_getbits_be24(&bs, 20);
 osds->audio_channels= mpxplay_bitstream_getbits_be24(&bs,  3) + 1;
 osds->audio_bits    = mpxplay_bitstream_getbits_be24(&bs,  5) + 1;
 osds->pcmdatalen    = mpxplay_bitstream_getbits_be64(&bs, 36);

 return 1;
}

static unsigned int ogg_metaheadread_flac(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs)
{
 PDS_PUTB_LE32(osds->metaheader_save,PDS_GETB_LE32(mpxplay_bitstream_getbufpos(bs)));
 mpxplay_bitstream_skipbits(bs,1); // metadata_last
 if(mpxplay_bitstream_getbits_be24(bs,7)!=FLAC_METADATA_TYPE_VORBISCOMMENT)
  return 0;
 mpxplay_bitstream_skipbits(bs,24); // metadata_size
 return 1;
}

static unsigned int ogg_metaheadwrite_flac(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size)
{
 unsigned long val;
 unsigned char data[8];

 if(metadata_size>0x00ffffff)
  return 0;
 val=PDS_GETB_BE32(osds->metaheader_save);
 if(val)
  val&=(1<<31);
 else
  val=1<<31;
 val|=FLAC_METADATA_TYPE_VORBISCOMMENT<<24;
 val|=metadata_size;
 PDS_PUTB_BEU32(data,val);
 if(!mpxplay_bitstream_putbytes(bs,data,4))
  return 0;
 return 1;
}

#define SPEEX_HEADER_STRING_LENGTH   8
#define SPEEX_HEADER_VERSION_LENGTH 20

static unsigned int ogg_parse_speex(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes)
{
 struct mpxplay_bitstreambuf_s bs;

 mpxplay_bitstream_init(&bs,packet,bytes);

 if(memcmp(mpxplay_bitstream_getbufpos(&bs),"Speex   ",SPEEX_HEADER_STRING_LENGTH)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs,SPEEX_HEADER_STRING_LENGTH+SPEEX_HEADER_VERSION_LENGTH);
 mpxplay_bitstream_skipbytes(&bs,4+4);
 osds->audio_freq=mpxplay_bitstream_get_le32(&bs);
 mpxplay_bitstream_skipbytes(&bs,4+4);
 osds->audio_channels=mpxplay_bitstream_get_le32(&bs);
 osds->audio_bits=16;

 return 1;
}

#if 0
static unsigned int ogg_parse_theora(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes)
{
 unsigned int version;
 struct mpxplay_bitstreambuf_s bs;

 mpxplay_bitstream_init(&bs,packet,bytes);

 if(mpxplay_bitstream_get_byte(&bs)!=0x80) // packtype
  return 0;
 if(memcmp(mpxplay_bitstream_getbufpos(&bs),"theora",6)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs,6);

 version=mpxplay_bitstream_getbits_be24(&bs,24);
 if(version<0x030100)
  return 0;
/*
 video_width  = mpxplay_bitstream_getbits_be24(&bs, 16) << 4;
 video_height = mpxplay_bitstream_getbits_be24(&bs, 16) << 4;

 if(version >= 0x030400)
  mpxplay_bitstream_skipbits(&bs, 164);
 else if (version >= 0x030200)
  mpxplay_bitstream_skipbits(&bs, 64);

 time_base_den = mpxplay_bitstream_getbits_be24(&bs, 32);
 time_base_num = mpxplay_bitstream_getbits_be24(&bs, 32);

 sample_aspect_ratio_num = mpxplay_bitstream_getbits_be24(&bs, 24);
 sample_aspect_ratio_den = mpxplay_bitstream_getbits_be24(&bs, 24);

 if(version >= 0x030200)
  mpxplay_bitstream_skipbits(&bs, 38);
 if(version >= 0x304000)
  mpxplay_bitstream_skipbits(&bs, 2);

 gpshift = mpxplay_bitstream_getbits_be24(&bs, 5);
 gpmask = (1 << gpshift) - 1;*/

 return 1;
}

static unsigned int ogg_metaheadread_theora(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs)
{
 if(mpxplay_bitstream_get_byte(bs)!=0x83) // packtype
  return 0;
 if(memcmp(mpxplay_bitstream_getbufpos(bs),"theora",6)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(bs,6);
 return 1;
}

static unsigned int ogg_metaheadwrite_theora(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size)
{
 unsigned char data[4];
 data[0]=0x83;
 if(!mpxplay_bitstream_putbytes(bs,data,1)) // packtype
  return 0;
 if(!mpxplay_bitstream_putbytes(bs,"theora",6))
  return 0;
 return 1;
}

#endif // 0 (disable theora)

//--------------------------------------------------------------------------
static int ogg_read_vorbiscomment(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,struct vorbiscomment_info_s *vci)
{
 struct ogg_demuxer_data_s *omip=miis->private_data;
 struct ogg_stream_data_s *osds=NULL,*osd_search;
 long metadata_size,allframecounter=0,streamframecounter=0;
 int retcode=MPXPLAY_ERROR_INFILE_CANTOPEN;
 char *readbuf;

 struct mpxplay_bitstreambuf_s bs;

 if(!omip->nb_streams)
  return retcode;

 osd_search=omip->stream_audio;
 if(!osd_search)
  osd_search=omip->stream_video;
 if(!osd_search)
  return retcode;

 fbfs->fseek(fbds,0,SEEK_SET);
 ogg_sync_reset(&(omip->oys));
 ogg_reset_streams(omip);

 do{
  int result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result<0)
   break;
  if(result==0){
   if(!refill_sync_buff(omip,fbfs,fbds))
    break;
   continue;
  }
  if(osds)
   ogg_stream_reset(&(osds->os_in));

  osds=ogg_find_stream_by_serialno(omip,ogg_page_serialno(&(omip->ogs)));
  if(osds!=osd_search)
   continue;

  streamframecounter++;
  if(streamframecounter<2) // the vorbiscomment is in the 2. frame
   continue;
  if(streamframecounter>2)
   break;

  if(ogg_stream_pagein(&(osds->os_in),&(omip->ogs))<0)
   break;
  if(ogg_stream_packetout(&(osds->os_in),&(osds->ops))!=1)
   break;

  omip->metadata_frame_cursize=omip->ogs.header_len+omip->ogs.body_len;
  omip->metadata_frame_beginpos=fbfs->ftell(fbds)-omip->oys.fill+omip->oys.returned-omip->metadata_frame_cursize;

  mpxplay_bitstream_init(&bs,osds->ops.packet,osds->ops.bytes-((osds->parsedatas->waveid==MPXPLAY_WAVEID_VORBIS)? 1:0)); // -1: the trailing 0x01

  if(osds->parsedatas->metaheadread_func)
   if(!osds->parsedatas->metaheadread_func(osds,&bs))
    continue;

  metadata_size=mpxplay_bitstream_leftbytes(&bs);
  readbuf=mpxplay_bitstream_getbufpos(&bs);
  if(vci){
   retcode=mpxplay_tagging_vorbiscomment_load_from_field(vci,readbuf,metadata_size);
   if(omip->ogs.body_len>osds->ops.bytes){
    omip->metadata_frame_extrasize=omip->ogs.body_len-osds->ops.bytes;
    omip->metadata_frame_extradata=malloc(omip->metadata_frame_extrasize);
    if(omip->metadata_frame_extradata)
     pds_memcpy(omip->metadata_frame_extradata,osds->os_in.body_data+osds->os_in.body_returned,omip->metadata_frame_extrasize);
    else
     retcode=MPXPLAY_ERROR_INFILE_MEMORY;
   }
  }else
   retcode=mpxplay_tagging_vorbiscomment_read_to_mpxplay(miis,fbds,readbuf,metadata_size);
  break;
 }while((++allframecounter)<=(omip->nb_streams*3));

 return retcode;
}

static int OGG_infile_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct ogg_demuxer_data_s *omip=miis->private_data;
 mpxp_filesize_t filepos=fbfs->ftell(fbds);
 int retcode=MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 if(!omip)
  return retcode;
 retcode=ogg_read_vorbiscomment(fbfs,fbds,miis,NULL);
 fbfs->fseek(fbds,filepos,SEEK_SET);
 ogg_sync_reset(&(omip->oys));
 ogg_reset_streams(omip);
 return retcode;
}

static int OGG_infile_tag_put(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,char *filename,unsigned long writetag_control)
{
 struct ogg_demuxer_data_s *omip=NULL;
 struct ogg_stream_data_s *osds;
 struct vorbiscomment_info_s *vci=NULL;
 void *tmpfile_filehand=NULL;
 char *metadata_field=NULL;
 int retcode=MPXPLAY_ERROR_INFILE_CANTOPEN;
 unsigned long i;
 mpxp_int64_t len;
 struct mpxplay_bitstreambuf_s bs;
 ogg_packet op;
 char writebuf[64];
 //char sout[128];

 // open file, search for the first audio stream
 if(!fbfs->fopen(fbds,filename,O_RDWR|O_BINARY,0))
  return retcode;
 retcode=inogg_header_check(fbfs,fbds,filename,miis,INOGG_READHEAD_CHECKSIZE);
 omip=miis->private_data;
 if(retcode!=MPXPLAY_ERROR_OK)
  goto err_out_otp;

 osds=ogg_find_stream_by_streamtype(omip,MPXPLAY_SPI_STREAMTYPE_AUDIO,NULL);
 if(!osds || !osds->parsedatas)
  goto err_out_otp;
 omip->stream_audio=osds;

 vci=&omip->vci;

 // read vorbiscomment from the file, create the new comment field from Mpxplay's database
 if((retcode=ogg_read_vorbiscomment(fbfs,fbds,miis,vci))!=MPXPLAY_ERROR_OK)
  goto err_out_otp;
 if((retcode=mpxplay_tagging_vorbiscomment_update_from_mpxplay(miis,fbds,vci))!=MPXPLAY_ERROR_OK)
  goto err_out_otp;
 if((retcode=mpxplay_tagging_vorbiscomment_write_to_field(vci,&metadata_field,4000,writetag_control))!=MPXPLAY_ERROR_OK)
  goto err_out_otp;

 // build new metadata frame
 mpxplay_bitstream_init(&bs,writebuf,sizeof(writebuf));
 mpxplay_bitstream_reset(&bs);
 if(osds->parsedatas->metaheadwrite_func)
  if(!osds->parsedatas->metaheadwrite_func(osds,&bs,vci->metadata_size_new))
   goto err_out_otp;

 for(i=0;i<omip->nb_streams;i++){
  struct ogg_stream_data_s *osd=&omip->ogg_streams[i];
  if(ogg_stream_init(&(osd->os_out),osd->os_in.serialno)<0)
   goto err_out_otp;
 }

 i=mpxplay_bitstream_leftbytes(&bs);
 omip->metadata_op.bytes=i+vci->metadata_size_new+((osds->parsedatas->waveid==MPXPLAY_WAVEID_VORBIS)? 1:0);
 omip->metadata_op.packet=malloc(omip->metadata_op.bytes);
 omip->metadata_op.packetno=osds->ops.packetno;
 if(!omip->metadata_op.packet)
  goto err_out_otp;
 if(i)
  pds_memcpy(omip->metadata_op.packet,mpxplay_bitstream_getbufpos(&bs),i);
 pds_memcpy(omip->metadata_op.packet+i,metadata_field,vci->metadata_size_new);
 if(osds->parsedatas->waveid==MPXPLAY_WAVEID_VORBIS)
  omip->metadata_op.packet[i+vci->metadata_size_new]=0x01;

 // !!! non-remuxing method is valid for Vorbis streams only
 if((vci->metadata_size_new==vci->metadata_size_cur) || ((omip->metadata_op.bytes+omip->metadata_frame_extrasize)<=4090)){
  // new frame without re-mux
  osds->os_out.b_o_s=1;
  osds->os_out.pageno=1;
  ogg_stream_packetin(&osds->os_out,&omip->metadata_op);
  // merge metadata and codebook into one frame
  if(omip->metadata_frame_extradata){
   pds_memset(&op,0,sizeof(op));
   op.bytes=omip->metadata_frame_extrasize;
   op.packet=omip->metadata_frame_extradata;
   op.packetno=omip->metadata_op.packetno+1;
   ogg_stream_packetin(&osds->os_out,&op);
  }
  if(ogg_stream_flush(&osds->os_out,&omip->metadata_og)==0)
   goto err_out_otp;
 }

 if(omip->metadata_frame_cursize==(omip->metadata_og.header_len+omip->metadata_og.body_len)){
  // write the new meta-frame into the original file
  if(fbfs->fseek(fbds,omip->metadata_frame_beginpos,SEEK_SET)!=omip->metadata_frame_beginpos){
   retcode=MPXPLAY_ERROR_MPXINBUF_SEEK_LOW;
   goto err_out_otp;
  }
  if( fbfs->fwrite(fbds,omip->metadata_og.header,omip->metadata_og.header_len)!=omip->metadata_og.header_len
   || fbfs->fwrite(fbds,omip->metadata_og.body,omip->metadata_og.body_len)!=omip->metadata_og.body_len){
   retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   goto err_out_otp;
  }
 }else{
  if(!(writetag_control&(MPXPLAY_WRITETAG_CNTRL_TRIMTAGS|MPXPLAY_WRITETAG_CNTRL_DUPFILE))){
   retcode=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPACE;
   goto err_out_otp;
  }
  // open tmp file
  if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPOPEN,&tmpfile_filehand,NULL))!=MPXPLAY_ERROR_OK)
   goto err_out_otp;
  if((retcode=fbfs->fseek(fbds,0,SEEK_SET))<0)
   goto err_out_otp;
  if((omip->metadata_op.bytes+omip->metadata_frame_extrasize)<=4090){ // number of pages/frames doesn't change
   // copy frame(s) before metadata frame
   len=omip->metadata_frame_beginpos;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
    goto err_out_otp;
   // write the new meta frame
   if( fbfs->fwrite(tmpfile_filehand,omip->metadata_og.header,omip->metadata_og.header_len)!=omip->metadata_og.header_len
    || fbfs->fwrite(tmpfile_filehand,omip->metadata_og.body,omip->metadata_og.body_len)!=omip->metadata_og.body_len){
    retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
    goto err_out_otp;
   }
   // (simply) copy the rest of the file
   if((retcode=fbfs->fseek(fbds,omip->metadata_frame_beginpos+omip->metadata_frame_cursize,SEEK_SET))<0)
    goto err_out_otp;
   len=-1;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
    goto err_out_otp;
  }else{ // number of pages/frames is changed, re-mux the complete file
   int retry=32,result;
   display_message(0,0,"Duplicating file ...");
   display_message(1,0,pds_getfilename_from_fullname(filename));
   ogg_sync_reset(&(omip->oys));
   ogg_reset_streams(omip);
   //i=0;
   do{
    if(osds && ogg_stream_packetout(&(osds->os_in),&(osds->ops))==1){
     /*if(i<50){
      sprintf(sout,"po i:%2d b:%d gp:%lld pn:%lld pg:%d bf:%d br:%d lf:%d",i,osds->ops.bytes,
        osds->ops.granulepos,osds->ops.packetno,osds->os_out.pageno,osds->os_out.body_fill,
        osds->os_out.body_returned,osds->os_out.lacing_fill);
      pds_textdisplay_printf(sout);
      i++;
     }*/
     if((osds==omip->stream_audio) && (osds->os_out.packetno==1)){ // insert new metadata
      osds->ops.packet=omip->metadata_op.packet;
      osds->ops.bytes=omip->metadata_op.bytes;
     }
     if(osds->ops.granulepos<0)
      osds->ops.granulepos=ogg_page_granulepos(&omip->ogs);
     ogg_stream_packetin(&(osds->os_out),&(osds->ops));
     do{
      if((osds->os_in.packetno==1) || (osds->os_in.packetno==3) || ((osds->os_in.packetno==4) && (osds->os_in.granulepos>0)))
       result=ogg_stream_flush(&(osds->os_out),&(omip->metadata_og));
      else
       result=ogg_stream_pageout(&(osds->os_out),&(omip->metadata_og)); // normal stream data
      if(result<=0)
       break;
      /*if(i<50){
       sprintf(sout,"pageout i:%2d pg:%d h:%d b:%d gpo:%lld gpi:%lld fp:%X",i,osds->os_out.pageno,omip->metadata_og.header_len,omip->metadata_og.body_len,
        osds->ops.granulepos,ogg_page_granulepos(&omip->ogs),(long)fbfs->ftell(fbds));
       pds_textdisplay_printf(sout);
       i++;
      }*/
      if( fbfs->fwrite(tmpfile_filehand,omip->metadata_og.header,omip->metadata_og.header_len)!=omip->metadata_og.header_len
       || fbfs->fwrite(tmpfile_filehand,omip->metadata_og.body,omip->metadata_og.body_len)!=omip->metadata_og.body_len){
       retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
       goto err_out_otp;
      }
     }while(1);
     retry=32;
    }else{
     osds=NULL;
     result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
     if(result>0){
      osds=ogg_find_stream_by_serialno(omip,ogg_page_serialno(&(omip->ogs)));
      /*if(i<50){
        sprintf(sout,"pagein%c i:%2d pg:%d h:%d b:%d gpi:%lld fp:%X",((osds==omip->stream_audio)? 'A':' '),
         i,ogg_page_pageno(&omip->ogs),omip->ogs.header_len,omip->ogs.body_len,
         ogg_page_granulepos(&omip->ogs),
         (long)fbfs->ftell(fbds)-omip->oys.fill+omip->oys.returned-(omip->ogs.header_len+omip->ogs.body_len)
        );
        pds_textdisplay_printf(sout);
        i++;
      }*/
      if(osds!=omip->stream_audio){
       osds=omip->stream_audio;
       result=ogg_stream_flush(&(osds->os_out),&(omip->metadata_og));
       if(result>0){
        if( fbfs->fwrite(tmpfile_filehand,omip->metadata_og.header,omip->metadata_og.header_len)!=omip->metadata_og.header_len
         || fbfs->fwrite(tmpfile_filehand,omip->metadata_og.body,omip->metadata_og.body_len)!=omip->metadata_og.body_len){
         retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
         goto err_out_otp;
        }
       }
       if( fbfs->fwrite(tmpfile_filehand,omip->ogs.header,omip->ogs.header_len)!=omip->ogs.header_len
        || fbfs->fwrite(tmpfile_filehand,omip->ogs.body,omip->ogs.body_len)!=omip->ogs.body_len){
        retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
        goto err_out_otp;
       }
       osds=NULL;
      }else
       ogg_stream_pagein(&(osds->os_in),&(omip->ogs));
     }else if(result==0){
      if(!refill_sync_buff(omip,fbfs,fbds))
       break;
     }else if(!(--retry)) // result<0
      break;
    }
   }while(1);
   clear_message();
  }
  if(!fbfs->eof(fbds)){ // ???
   retcode=MPXPLAY_ERROR_INFILE_WRITETAG_UNKNOWN;
   goto err_out_otp;
  }
  retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPXCHCLOSE,&tmpfile_filehand,NULL);
 }
 if(writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS){ // !!! update to real tags
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAGS_CLEAR,NULL,NULL); // bullshit
  mpxplay_tagging_vorbiscomment_read_to_mpxplay(miis,fbds,metadata_field,vci->metadata_size_new);
 }
err_out_otp:
 if(omip){
  if(metadata_field)
   free(metadata_field);
  mpxplay_tagging_vorbiscomment_free(vci);
  if(omip->metadata_frame_extradata)
   free(omip->metadata_frame_extradata);
  if(omip->metadata_op.packet)
   free(omip->metadata_op.packet);
 }
 if(tmpfile_filehand)
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_CLOSE,&tmpfile_filehand,NULL);
 OGG_infile_close(fbfs,fbds,miis);
 miis->private_data=NULL;
 return retcode;
}

struct mpxplay_infile_func_s IN_OGG_funcs={
 0,
 NULL,
 NULL,
 &OGG_infile_open,
 &OGG_infile_close,
 &OGG_infile_decode,
 &OGG_infile_fseek,
 &OGG_infile_clearbuffs,
 &OGG_infile_tag_get,
 &OGG_infile_tag_put,
 NULL,
 {"OGG","OGA","OGV","SPX",NULL}
};

#endif // MPXPLAY_LINK_INFILE_OGG
