//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2006 by PDSoft (Attila Padar)                *
//*                    http://mpxplay.cjb.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-Vorbis file handling
//requires the dec_ogg\oggdec.lib file (and include files)

#include "in_file.h"

#ifdef MPXPLAY_LINK_INFILE_OGG

#include "newfunc\newfunc.h"
#include "decoders\ad_vorbi\codec.h"
#include "decoders\ad_vorbi\backends.h"
#include "decoders\ad_vorbi\codecint.h"

#define VORBIS_OUT_T float

typedef struct ogg_decoder_data {
 unsigned int current_decoder_part;
 unsigned int pcmoutcount;

 ogg_sync_state   oys;
 ogg_stream_state oss;
 ogg_page         ogs;
 ogg_packet       ops;

 vorbis_info      vis;
 vorbis_comment   vcs;
 vorbis_dsp_state vds;
 vorbis_block     vbs;

}ogg_decoder_data;

static int read_ogg_comment_book(struct ogg_decoder_data *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);
static int read_ogg_frame(struct ogg_decoder_data *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);
static int decode_ogg_frame(struct ogg_decoder_data *omip);
static int get_ogg_outdata(struct ogg_decoder_data *omip,VORBIS_OUT_T *pcm_outdata,unsigned int samplenum_request,int flushdata);
static int refill_sync_buff(struct ogg_decoder_data *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);

static int ogg_assign_values(struct ogg_decoder_data *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 vorbis_info *vi=&omip->vis;
 long filelen;
 mpxp_int64_t pcmdatalen;

 if(!vi->rate)
  return 0;
 if(!vi->channels)
  return 0;
 filelen=fbfs->filelength(fbds);

 adi->freq=vi->rate;
 adi->filechannels=vi->channels;

 if(adi->channelcfg==CHM_LEFT)
  adi->outchannels=vi->outchannels=1;
 else
  adi->outchannels=vi->outchannels=vi->channels;

 if(miis->filesize!=filelen){
  miis->filesize=filelen;
  pcmdatalen=ogg_get_pcmlength(fbfs,fbds);
  if(!pcmdatalen && vi->bitrate_nominal)
   pds_fto64i((float)filelen*8.0*(float)vi->rate/(float)vi->bitrate_nominal,&pcmdatalen);
  if(!pcmdatalen)
   return 0;
  pds_ftoi((float)filelen*8.0/1000.0*(float)adi->freq/(float)pcmdatalen,(mpxp_int32_t *)&adi->bitrate);
  miis->timemsec=(float)pcmdatalen*1000.0/(float)adi->freq;
 }else
  pds_ftoi((float)filelen*8.0/miis->timemsec,(mpxp_int32_t *)&adi->bitrate);

 if(adi->filechannels==2){
  codec_setup_info *ci=vi->codec_setup;
  if(ci){
   vorbis_info_mapping0 *info=ci->map_param[0];
   if(info){
    if(info->coupling_steps){
     adi->channeltext="c-Stereo";
    }
   }
  }
 }

 adi->bits=16;
 adi->infobits|=ADI_FLAG_FLOATOUT;
#ifdef OGG_SPECTRUM_ANALISER
 adi->infobits|=ADI_FLAG_OWN_SPECTANAL;
#endif
 miis->longname="OgVorbis";
 return 1;
}

static void *ogg_check_header(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis)
{
 struct ogg_decoder_data *omip;

 if(!fbfs->fopen_read(fbds,filename,0))
  return NULL;

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

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

 ogg_sync_init(&(omip->oys));

 if(refill_sync_buff(omip,fbfs,fbds)<OGG_SYNC_BUFFER_SIZE) // too short file
  goto err_out_chkhead;

 if(ogg_sync_pageout(&(omip->oys),&(omip->ogs))!=1)  // Ogg header not found
  goto err_out_chkhead;

 if(ogg_stream_init(&(omip->oss),ogg_page_serialno(&(omip->ogs)))<0)
  goto err_out_chkhead;
 vorbis_info_init(&(omip->vis));
 vorbis_comment_init(&(omip->vcs));

 if(ogg_stream_pagein(&(omip->oss),&(omip->ogs))<0)
  goto err_out_chkhead;
 if(ogg_stream_packetout(&(omip->oss),&(omip->ops))!=1)
  goto err_out_chkhead;
 if(vorbis_synthesis_headerin(&(omip->vis),&(omip->vcs),&(omip->ops))<0)  // unpack_info
  goto err_out_chkhead;

 return omip;

err_out_chkhead:
 return NULL;
}

static int OGG_infile_check(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis)
{
 struct ogg_decoder_data *omip;
 omip=ogg_check_header(fbfs,fbds,filename,miis);
 if(!omip)
  return MPXPLAY_ERROR_INFILE_CANTOPEN;

 if(!ogg_assign_values(omip,fbfs,fbds,miis))
  goto err_out_inchk;

 return MPXPLAY_ERROR_INFILE_OK;

err_out_inchk:
 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)
{
 struct ogg_decoder_data *omip;

 omip=ogg_check_header(fbfs,fbds,filename,miis);   // unpack file info
 if(!omip)
  return MPXPLAY_ERROR_INFILE_CANTOPEN;

 if(read_ogg_comment_book(omip,fbfs,fbds)<0) // unpack comment (artist,title,etc.)
  goto err_out_inopn;
 if(read_ogg_comment_book(omip,fbfs,fbds)<0) // unpack codebook
  goto err_out_inopn;

 if(vorbis_synthesis_init(&(omip->vds),&(omip->vis))<0)
  goto err_out_inopn;
 if(vorbis_block_init(&(omip->vds),&(omip->vbs))<0)
  goto err_out_inopn;

 if(!ogg_assign_values(omip,fbfs,fbds,miis))
  goto err_out_inopn;

 return MPXPLAY_ERROR_INFILE_OK;

err_out_inopn:
 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_decoder_data *omip=miis->private_data;
 if(omip){
  vorbis_block_clear(&(omip->vbs));
  ogg_stream_clear(&(omip->oss));
  vorbis_dsp_clear(&(omip->vds));
  vorbis_info_clear(&(omip->vis));
  ogg_sync_clear(&(omip->oys));
  free(omip);
 }
 fbfs->fclose(fbds);
}

static int read_ogg_comment_book(struct ogg_decoder_data *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int result,retry=16;
 do{
  result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result==1){
   ogg_stream_pagein(&(omip->oss),&(omip->ogs));
   result=ogg_stream_packetout(&(omip->oss),&(omip->ops));
   if(result<0)
    return 0;
   if(result>0)
    return vorbis_synthesis_headerin(&(omip->vis),&(omip->vcs),&(omip->ops));
  }
  if(result==0)
   if(!refill_sync_buff(omip,fbfs,fbds))
    return 0;
 }while(--retry);
 return 1;
}

static int OGG_infile_decode(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct ogg_decoder_data *omip=miis->private_data;
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 int eos=0;

 do{
  switch(omip->current_decoder_part){
   case 0:if(read_ogg_frame(omip,fbfs,fbds)){
	   omip->current_decoder_part=1;
	  }else{
           adi->pcm_samplenum=get_ogg_outdata(omip,(VORBIS_OUT_T *)adi->pcm_bufptr,adi->pcm_framelen,1);
	   if(!adi->pcm_samplenum)
	    eos=1;
	   break;
	  }
   case 1:if(decode_ogg_frame(omip)){
	   omip->current_decoder_part=2;
	  }else{
	   omip->current_decoder_part=0;
	   break;
	  }
   case 2:adi->pcm_samplenum=get_ogg_outdata(omip,(VORBIS_OUT_T *)adi->pcm_bufptr,adi->pcm_framelen,0);
	  if(!adi->pcm_samplenum)
	   omip->current_decoder_part=1;
	  break;
  }
 }while(!eos && !adi->pcm_samplenum);

 if(eos)
  return MPXPLAY_ERROR_INFILE_NODATA;

 return MPXPLAY_ERROR_INFILE_OK;
}

static int read_ogg_frame(struct ogg_decoder_data *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int result,retry=16;
 do{
  result=ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result>0)
   ogg_stream_pagein(&(omip->oss),&(omip->ogs));
  if(result==0)
   if(!refill_sync_buff(omip,fbfs,fbds))
    return 0;
 }while(result<=0 && --retry);
 if(result<=0)
  return 0;
 return 1;
}

static int decode_ogg_frame(struct ogg_decoder_data *omip)
{
 if(ogg_stream_packetout(&(omip->oss),&(omip->ops))>0){
  if(vorbis_synthesis(&(omip->vbs),&(omip->ops))==0){
   vorbis_synthesis_blockin(&(omip->vds),&(omip->vbs));
   return 1;
  }
 }
 return 0;
}

void asm_ogg_conv(int samples,ogg_double_t *chdata,float *pcmout);

static int get_ogg_outdata(struct ogg_decoder_data *omip,VORBIS_OUT_T *pcm_outdata,unsigned int samplenum_request,int flushdata)
{
 ogg_double_t **pcm;
 int channel,samples,vich=omip->vis.outchannels;
#ifdef OGG_USE_ASM
 int pcmout_step=vich*sizeof(float);
#endif

 samples=vorbis_synthesis_pcmout(&(omip->vds),&pcm);

 if(samples){
  float *pcmout_begin;

  if(vich>PCM_MAX_CHANNELS)
   vich=PCM_MAX_CHANNELS;

  if((omip->pcmoutcount+samples)>samplenum_request)
   samples=samplenum_request-omip->pcmoutcount;

  pcmout_begin=pcm_outdata+(omip->pcmoutcount*vich);
  channel=vich;
  do{
   ogg_double_t *pcmdec_data=*pcm++;
#if defined(OGG_USE_ASM) && !defined(OGGDEC_DOUBLE_PRECISION)
 #ifdef __WATCOMC__
  #pragma aux asm_ogg_conv=\
   "mov ebx,4"\
   "mov ecx,pcmout_step"\
   "back1:mov eax,dword ptr [edi]"\
    "add edi,ebx"\
    "mov dword ptr [esi],eax"\
    "add esi,ecx"\
    "dec edx"\
   "jnz back1"\
   parm[edx][edi][esi] modify[eax ebx ecx edx edi esi];
   asm_ogg_conv(samples,pcmdec_data,pcmout_begin);
 #endif // __WATCOMC__
#else // !OGG_USE_ASM || OGGDEC_DOUBLE_PRECISION
   unsigned int j=samples;
   float *pcmout_data=pcmout_begin;
   do{
    float val=*pcmdec_data++;
    *pcmout_data=val;
    pcmout_data+=vich;
   }while(--j);
#endif
   pcmout_begin++;
  }while(--channel);

  vorbis_synthesis_read(&(omip->vds),samples);
  omip->pcmoutcount+=samples;
 }
 samples=0;
 if(omip->pcmoutcount>=samplenum_request || flushdata){
  samples=omip->pcmoutcount*vich;
  omip->pcmoutcount=0;
 }
 return samples;
}

static int refill_sync_buff(struct ogg_decoder_data *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 void OGG_infile_clearbuffs(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,unsigned int seektype)
{
 struct ogg_decoder_data *omip=miis->private_data;

 ogg_sync_reset(&(omip->oys));
 ogg_stream_reset(&(omip->oss));

 if(seektype&(MPX_SEEKTYPE_BOF|MPX_SEEKTYPE_PAUSE)){
  omip->current_decoder_part=0;
  vorbis_synthesis_restart(&(omip->vds),&(omip->vis));
  omip->pcmoutcount=0;
#ifdef OGG_SPECTRUM_ANALISER
  ogg_analiser_clear();
#endif
 }
}

static long OGG_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newframenum)
{
 long 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)
{
 static ogg_sync_state oy;
 static ogg_page og;
 ogg_int64_t pcmlen=0;
 char *ptr,*buffer;
 long newfilepos=0,oldfilepos=fbfs->ftell(fbds);
 long 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)
      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(--retry1);
 ogg_sync_clear(&oy);
 fbfs->fseek(fbds,oldfilepos,SEEK_SET);
 return pcmlen;
}

#define OGG_COMMENT_TYPES 8

//have to run check_header before get_id3tag
static char *OGG_infile_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,char **id3ip,char *id3p,struct mpxplay_textconv_func_s *mpxplay_textconv_funcs)
{
 static char *oggcommenttypes[OGG_COMMENT_TYPES]={"title","artist","album","date","comment","description","genre","tracknumber"};
 static unsigned int id3index[OGG_COMMENT_TYPES]={I3I_TITLE,I3I_ARTIST,I3I_ALBUM,I3I_YEAR,I3I_COMMENT,I3I_COMMENT,I3I_GENRE,I3I_TRACKNUM};
 struct ogg_decoder_data *omip=miis->private_data;
 char **ptr;
 unsigned int i;

 if(omip!=NULL){
  if(omip->vcs.user_comments==NULL)
   if(read_ogg_comment_book(omip,fbfs,fbds)<0)
    return id3p;
  if(omip->vcs.user_comments==NULL)
   return id3p;
  ptr=omip->vcs.user_comments;
  while(*ptr){
   char *p=pds_strchr(*ptr,'=');
   if(p!=NULL){
    p[0]=0;
    p++;
    for(i=0;i<OGG_COMMENT_TYPES;i++){
     if(pds_stricmp(*ptr,oggcommenttypes[i])==0){
      unsigned int len;
      len=pds_strcpy(id3p,p);
      if((*(mpxplay_textconv_funcs->control))&ID3TEXTCONV_UTF_AUTO)
       len=mpxplay_textconv_funcs->utf8_to_char(id3p,len);  // always from Ogg v1.0
      len=mpxplay_textconv_funcs->all_to_char(id3p,len,ID3TEXTCONV_UTF8);
      if(len){
       id3ip[id3index[i]]=id3p;
       id3p+=len+1;
      }
     }
    }
   }
   ptr++;
  }
 }
 return id3p;
}

struct mpxplay_infile_func_s IN_OGG_funcs={
 0,
 NULL,
 NULL,
 &OGG_infile_check,
 &OGG_infile_check,
 &OGG_infile_open,
 &OGG_infile_close,
 &OGG_infile_decode,
 &OGG_infile_fseek,
 &OGG_infile_clearbuffs,
 &OGG_infile_tag_get,
 NULL,
 NULL,
 {"OGG",NULL}
};

#endif // MPXPLAY_LINK_INFILE_OGG
