//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2014 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)

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT stdout

#include "mpxplay.h"

#if defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#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_FRAMESIZE_PCM_MAX   65536 // FIXME: ???
//#define INOGG_FIRST_DATA_FRAME        2

#define INOGG_MAX_STREAMS 8
#define MPXPLAY_CODECID_THEORA 0x00028000

#define INOGG_NEWSTREAM_FLAG_LIVESTREAM     (1 << 0)
#define INOGG_NEWSTREAM_FLAG_CAMEFROMHEADER (1 << 1)

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;
 long pre_skip;
 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 newstream_flags;
 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_parse_opus(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_metaheadread_opus(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 unsigned int ogg_metaheadwrite_opus(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size);

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},
 {"Ogg-Opus",MPXPLAY_SPI_STREAMTYPE_AUDIO,MPXPLAY_WAVEID_OPUS ,&ogg_parse_opus,&ogg_metaheadread_opus,&ogg_metaheadwrite_opus},
 {NULL,0}
};

static int refill_sync_buff(struct ogg_demuxer_data_s *omip,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds);

#endif // defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#ifdef MPXPLAY_LINK_INFILE_OGG

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);
 int is_seekable;
 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;

 is_seekable = miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_ISSEEKABLE,NULL,NULL);

 if((miis->filesize!=filelen) || !miis->timemsec){
  miis->filesize = filelen;
  if(is_seekable != 0){
   if(!osds->pcmdatalen){
    osds->pcmdatalen = ogg_get_pcmlength(fbfs,fbds,osds->os_in.serialno);
    if(osds->pcmdatalen > (mpxp_int64_t)osds->pre_skip)
     osds->pcmdatalen -= (mpxp_int64_t)osds->pre_skip;
   }
   miis->timemsec = (float)osds->pcmdatalen * 1000.0 / (float)osds->audio_freq;
   //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "fg:%"PRIi64" ts:%d ", osds->pcmdatalen, miis->timemsec);
  }
 }

 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;
  if(!adi->bitratetext)
   adi->bitratetext = pds_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) || (omip->newstream_flags & INOGG_NEWSTREAM_FLAG_LIVESTREAM) || !miis->timemsec){
  if(!osds->audio_bitrate){ // then assume a bitrate
   switch(osds->parsedatas->waveid){
    case MPXPLAY_WAVEID_VORBIS: osds->audio_bitrate = 128000; break;
    default: osds->audio_bitrate = 80000; break; // Opus & Speex
   }
  }
  adi->bitrate = osds->audio_bitrate;
  if(adi->bitrate >= 1000)
   adi->bitrate /= 1000;

 }else{
  adi->bitrate = (long)((float)filelen*8.0/(float)miis->timemsec);
 }

 if(!miis->timemsec || !osds->pcmdatalen){
  if(!miis->timemsec)
   miis->timemsec = (long)((float)filelen*8.0/(float)adi->bitrate);
  if(!osds->pcmdatalen)
   osds->pcmdatalen = (float)miis->timemsec/1000.0*(float)osds->audio_freq;
 }

 return 1;
}

#endif // MPXPLAY_LINK_INFILE_OGG

#if defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

static int inogg_add_new_stream(struct ogg_demuxer_data_s *omip, unsigned int newstream_flags)
{
 struct ogg_stream_data_s *osds;
 struct ogg_parse_data_s *parsedatas;
 unsigned long i, serialnum = ogg_page_serialno(&(omip->ogs));

 if((omip->nb_streams || (omip->newstream_flags & INOGG_NEWSTREAM_FLAG_LIVESTREAM)) && !serialnum) // multiply file-streams or live-stream must have stream-serial(s)
  goto err_out_addstream;

 osds = ogg_find_stream_by_serialno(omip,serialnum);
 if(osds) // the stream is already initialized (found in the existent streams)
  return MPXPLAY_ERROR_INFILE_OK;

 for(i=0; i<INOGG_MAX_STREAMS; i++){ // search for free stream entry
  osds = &omip->ogg_streams[i];
  if(!osds->os_in.serialno) // found
   break;
 }
 if(i >= INOGG_MAX_STREAMS)
  goto err_out_addstream;
 if(i >= omip->nb_streams)
  omip->nb_streams = i+1;

 ogg_stream_reset(&(osds->os_in));

 if(osds->os_in.body_storage) // !!! re-use/update only (don't realloc buffers)
  osds->os_in.serialno = serialnum;
 else{
#ifdef __DOS__
  if(!(newstream_flags & INOGG_NEWSTREAM_FLAG_CAMEFROMHEADER))
   goto err_out_addstream;
#endif
  if(ogg_stream_init(&(osds->os_in),serialnum)<0)
   goto err_out_addstream;
 }
 if(ogg_stream_pagein(&(osds->os_in),&(omip->ogs))<0)
  goto err_out_addstream;
 if(ogg_stream_packetout(&(osds->os_in),&(osds->ops))!=1)
  goto err_out_addstream;

 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);

 return 1;

err_out_addstream:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

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;
 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 *)pds_calloc(1,sizeof(struct ogg_demuxer_data_s));
 if(!omip)
  goto err_out_chkhead;
 miis->private_data=omip;

 if(miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_ISSTREAM,NULL,NULL) == 1)
  omip->newstream_flags = INOGG_NEWSTREAM_FLAG_LIVESTREAM;

 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{
   int retcode = inogg_add_new_stream(omip, INOGG_NEWSTREAM_FLAG_CAMEFROMHEADER);
   if(retcode == MPXPLAY_ERROR_INFILE_OK) // no new stream
    break;
   if(retcode == MPXPLAY_ERROR_INFILE_CANTOPEN)  // error
    return retcode;
  }
 }while(1);

 return MPXPLAY_ERROR_INFILE_OK;

err_out_chkhead:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

#endif // defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#ifdef MPXPLAY_LINK_INFILE_OGG

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;
}

#endif // MPXPLAY_LINK_INFILE_OGG

#if defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

static void ogg_infile_streams_close(struct ogg_demuxer_data_s *omip)
{
 struct ogg_stream_data_s *osds;
 unsigned int i;
 if(omip){
  osds=&omip->ogg_streams[0];
  for(i=0;i<omip->nb_streams;i++,osds++){
   if(osds->extradata)
    pds_free(osds->extradata);
   ogg_stream_clear(&(osds->os_in));
   ogg_stream_clear(&(osds->os_out));
  }
 }
}

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;
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 if(omip){
  if(adi && adi->bitratetext){
   pds_free(adi->bitratetext);
   adi->bitratetext = NULL;
  }
  ogg_infile_streams_close(omip);
  ogg_sync_clear(&(omip->oys));
  pds_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;
}

#endif // defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#ifdef MPXPLAY_LINK_INFILE_OGG

static int read_ogg_frame(struct ogg_demuxer_data_s *omip, struct mpxplay_infile_info_s *miis,
                struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int result, retry = 32, retcode = MPXPLAY_ERROR_INFILE_SYNC_IN;
 do{
  result = ogg_sync_pageout(&(omip->oys),&(omip->ogs));
  if(result>0){
   struct ogg_stream_data_s *osds;

   if(ogg_page_bos(&(omip->ogs))){ // begin of stream (new stream)
    unsigned long asn = (omip->stream_audio)? omip->stream_audio->os_in.serialno : 0; // is valid/selected audio stream?
    inogg_add_new_stream(omip, 0);
    if(!asn)
     omip->stream_audio = ogg_find_stream_by_streamtype(omip, MPXPLAY_SPI_STREAMTYPE_AUDIO, miis->audio_stream);
#ifdef MPXPLAY_WIN32
    //miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAGS_RELOAD,0,0); // will not work on this way (OGG_infile_tag_get direct file read)
#endif
    //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "BOF1 %"PRIi64" ", ogg_page_granulepos(&(omip->ogs)));
   }

   //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "ROF %"PRIi64" ", ogg_page_granulepos(&(omip->ogs)));

   osds = ogg_find_stream_by_serialno(omip,ogg_page_serialno(&(omip->ogs)));

   if(osds && (osds == omip->stream_audio)){
    ogg_stream_pagein(&(osds->os_in),&(omip->ogs));
    retcode = MPXPLAY_ERROR_INFILE_SYNC_IN;
   }else
    result=-1; // new ogg_sync_pageout()

   if(ogg_page_eos(&(omip->ogs))){ // end of stream
    if(osds)
     osds->os_in.serialno = 0;   // 0 means free stream
    else if(omip->stream_audio)
     omip->stream_audio->os_in.serialno = 0; // !!! ???
   }

  }else if(result == 0){
   if(!refill_sync_buff(omip,fbfs,fbds)) {
    retcode = (retry < 30)? MPXPLAY_ERROR_INFILE_SYNCLOST : MPXPLAY_ERROR_INFILE_NODATA;
    break;
   } else if(retry)
    retry--;
  }else if(!(--retry)) { // result<0
    retcode = MPXPLAY_ERROR_INFILE_SYNCLOST;
    break;
  }
 }while(result<=0);

 return retcode;
}

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(osds && (ogg_stream_packetout(&(osds->os_in),&(osds->ops))>0)){
  spi->bs_leftbytes=osds->ops.bytes;
  if(!spi->bs_leftbytes)
   return MPXPLAY_ERROR_INFILE_SYNC_IN;
  pds_memcpy(spi->bitstreambuf,osds->ops.packet,osds->ops.bytes);
  return MPXPLAY_ERROR_INFILE_OK;
 }
 return MPXPLAY_ERROR_INFILE_SYNC_IN;
}

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 retcode = MPXPLAY_ERROR_INFILE_OK;

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

 return retcode;
}

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 pcmbegin=0,pcmlen=0;
 mpxp_filesize_t newfilepos=0,oldfilepos=fbfs->ftell(fbds);
 mpxp_filesize_t filelen=fbfs->filelength(fbds);
 long bytes,oyret;
 int retry1,retry2;

 if(!filelen)
  return 0;
 ogg_sync_init(&oy);
 ogg_sync_buffer(&oy,OGG_CHUNKSIZE*OGG_CHUNKNUM);
 if(oy.data==NULL)
  return 0;

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

 retry1 = retry2 = 0;
 do{ // search for begin
  int result;
  pds_memset((void *)(&og), 0, sizeof(ogg_page));
  result = ogg_sync_pageout(&oy, &og);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "AAA r1: %d res:%d pos:%d ", retry1, result, oy.returned);
  if(result > 0){
   if((ogg_page_serialno(&og) == stream_serialno)){// && (++retry1 >= INOGG_FIRST_DATA_FRAME)){
    ogg_int64_t granule_pos = ogg_page_granulepos(&og);
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "BBB r2: %d pos:%d ", retry2, (long)granule_pos);
    if(granule_pos > 0) {
     if(!retry2 || (granule_pos > INOGG_FRAMESIZE_PCM_MAX)) // FIXME: ??? usually the granulepos=0 is valid (but sometimes invalid)
      pcmbegin = granule_pos;
     break;
    }
    retry2++;
    oy.returned++;
    oy.headerbytes = oy.bodybytes = 0;
   }
  }else if(result == 0){
   ptr = oy.data  + oy.fill;
   bytes = oy.storage - oy.fill;
   if(bytes > OGG_CHUNKSIZE)
    bytes = OGG_CHUNKSIZE;
   if(bytes < 8)
    break;
   bytes = fbfs->fread(fbds, ptr, bytes);
   if(bytes < 1)
    break;
   oy.fill += bytes;
  }
 }while(1);

 ogg_sync_reset(&oy);
 retry1=20;
 do{ // search for end
  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);
 if(pcmlen > pcmbegin)
  pcmlen -= pcmbegin;
 return pcmlen;
}

#endif // MPXPLAY_LINK_INFILE_OGG

#if defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

//--------------------------------------------------------------------------
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;

 //if(!serialno)  // FIXME: ???
 // return NULL;

 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,(unsigned char *)"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=pds_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 unsigned int ogg_parse_opus(struct ogg_stream_data_s *osds,unsigned char *packet,unsigned int bytes)
{
 struct mpxplay_bitstreambuf_s bs;
 unsigned char version;

 mpxplay_bitstream_init(&bs,packet,bytes);

 if(memcmp(mpxplay_bitstream_getbufpos(&bs), "OpusHead", 8) != 0)
  return 0;
 mpxplay_bitstream_skipbytes(&bs, 8); // OpusHead
 version = mpxplay_bitstream_get_byte(&bs);
 if(version & 240) // only major version 0 supported
  return 0;
 osds->audio_channels = mpxplay_bitstream_get_byte(&bs);
 osds->pre_skip = mpxplay_bitstream_get_le16(&bs);
 osds->audio_freq = 48000;
 //mpxplay_bitstream_skipbytes(&bs,4); // original freq
 //mpxplay_bitstream_skipbytes(&bs,2); // output gain
 //mpxplay_bitstream_get_byte(&bs);    // channel map (should be 0 or 1)
 osds->audio_bits = 16;
 osds->extradata = pds_malloc(bytes + MPXPLAY_SPI_EXTRADATA_PADDING);
 if(!osds->extradata)
  return 0;
 pds_memcpy(osds->extradata, packet, bytes);
 pds_memset(osds->extradata + bytes, 0, MPXPLAY_SPI_EXTRADATA_PADDING);
 osds->extradata_size = bytes;
 return 1;
}

static unsigned int ogg_metaheadread_opus(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs)
{
 if(memcmp(mpxplay_bitstream_getbufpos(bs),"OpusTags",8)!=0)
  return 0;
 mpxplay_bitstream_skipbytes(bs,8);
 return 1;
}

static unsigned int ogg_metaheadwrite_opus(struct ogg_stream_data_s *osds,struct mpxplay_bitstreambuf_s *bs,unsigned long metadata_size)
{
 if(!mpxplay_bitstream_putbytes(bs,(unsigned char *)"OpusTags",8))
  return 0;
 return 1;
}

//--------------------------------------------------------------------------
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;
    if(omip->metadata_frame_extradata)
     pds_free(omip->metadata_frame_extradata);
    omip->metadata_frame_extradata=pds_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;
}

#endif // defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#ifdef MPXPLAY_LINK_INFILE_OGG

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;
}

#endif // MPXPLAY_LINK_INFILE_OGG

#if defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

int mpxplay_INOGG_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;
 unsigned 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=pds_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)
   pds_free(metadata_field);
  mpxplay_tagging_vorbiscomment_free(vci);
  if(omip->metadata_frame_extradata)
   pds_free(omip->metadata_frame_extradata);
  if(omip->metadata_op.packet)
   pds_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;
}

#endif // defined(MPXPLAY_LINK_INFILE_OGG) || defined(MPXPLAY_LINK_INFILE_FF_MPEG)

#ifdef MPXPLAY_LINK_INFILE_OGG

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,
 &mpxplay_INOGG_tag_put,
 NULL,
 {"OGG","OGA","SPX","OPUS",
#ifdef __DOS__
 "OPU",
#endif
 NULL}
};

struct mpxplay_infile_func_s IN_OGV_funcs={
 (MPXPLAY_INFILEFUNC_FLAG_NOADETECT | MPXPLAY_INFILEFUNC_FLAG_VIDEO),
 NULL,
 NULL,
 &OGG_infile_open,
 &OGG_infile_close,
 &OGG_infile_decode,
 &OGG_infile_fseek,
 &OGG_infile_clearbuffs,
 &OGG_infile_tag_get,
 &mpxplay_INOGG_tag_put,
 NULL,
 {"OGM","OGV",NULL}
};

#endif // MPXPLAY_LINK_INFILE_OGG
