/*
	2004.02.01
		first released source code for IOMP
*/
/* 
 * Copyright (C) 2000-2003 the xine project
 * 
 * This file is part of xine, a free video player.
 * 
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * xine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 * $Id: audio_decoder.c,v 1.2 2003/11/25 04:26:01 georgedon Exp $
 *
 * thin layer to use real binary-only codecs in xine
 *
 * code inspired by work from Florian Schneider for the MPlayer Project 
 */


#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dlfcn.h>

#include "bswap.h"
#include "xine_internal.h"
#include "video_out.h"
#include "buffer.h"
#include "xineutils.h"

/*
#define LOG
*/

typedef struct {
  audio_decoder_class_t   decoder_class;

  /* empty so far */
} real_class_t;

typedef struct realdec_decoder_s {
  audio_decoder_t  audio_decoder;

  real_class_t    *cls;

  xine_stream_t   *stream;

  void            *ra_handle;

  unsigned long  (*raCloseCodec)(void*);
  unsigned long  (*raDecode)(void*, char*,unsigned long,char*,unsigned int*,long);
  unsigned long  (*raFlush)(unsigned long,unsigned long,unsigned long);
  unsigned long  (*raFreeDecoder)(void*);
  void*          (*raGetFlavorProperty)(void*,unsigned long,unsigned long,int*);
  unsigned long  (*raInitDecoder)(void*, void*);
  unsigned long  (*raOpenCodec2)(void*);
  unsigned long  (*raSetFlavor)(void*,unsigned long);
  void           (*raSetDLLAccessPath)(char*);
  void           (*raSetPwd)(char*,char*);

  void            *context;

  int              sps, w, h;
  int              block_align;

  uint8_t         *frame_buffer;
  uint8_t         *frame_reordered;
  int              frame_size;
  int              frame_num_bytes;

  int              sample_size;

  uint64_t         pts;

  int              output_open;
  
  int              decoder_ok;

} realdec_decoder_t;

typedef struct {
    int    samplerate;
    short  bits;
    short  channels;
    int    unk1;
    int    subpacket_size;
    int    coded_frame_size;
    int    codec_data_length;
    void  *extras;
} ra_init_t;

void *__builtin_new(unsigned long size);
void __builtin_delete (void *foo);
void *__builtin_vec_new(unsigned long size);
void __builtin_vec_delete(void *mem);
void __pure_virtual(void);


void *__builtin_new(unsigned long size) {
  return malloc(size);
}

void __builtin_delete (void *foo) {
  /* printf ("libareal: __builtin_delete called\n"); */
  free (foo);
}

static int load_syms_linux (realdec_decoder_t *this, char *codec_name) {

  cfg_entry_t* entry = this->stream->xine->config->lookup_entry(
			 this->stream->xine->config, "codec.real_codecs_path");
  char path[1024];

  sprintf (path, "%s/%s", entry->str_value, codec_name);

#ifdef LOG
  printf ("libareal: (audio) opening shared obj '%s'\n", path);

#endif

  this->ra_handle = dlopen (path, RTLD_LAZY);

  if (!this->ra_handle) {
    printf ("libareal: error: %s\n", dlerror());
    xine_message(this->stream, XINE_MSG_LIBRARY_LOAD_ERROR,
                 codec_name, NULL);
    return 0;
  }
  
  this->raCloseCodec        = dlsym (this->ra_handle, "RACloseCodec");
  this->raDecode            = dlsym (this->ra_handle, "RADecode");
  this->raFlush             = dlsym (this->ra_handle, "RAFlush");
  this->raFreeDecoder       = dlsym (this->ra_handle, "RAFreeDecoder");
  this->raGetFlavorProperty = dlsym (this->ra_handle, "RAGetFlavorProperty");
  this->raOpenCodec2        = dlsym (this->ra_handle, "RAOpenCodec2");
  this->raInitDecoder       = dlsym (this->ra_handle, "RAInitDecoder");
  this->raSetFlavor         = dlsym (this->ra_handle, "RASetFlavor");
  this->raSetDLLAccessPath  = dlsym (this->ra_handle, "SetDLLAccessPath");
  this->raSetPwd            = dlsym (this->ra_handle, "RASetPwd"); /* optional, used by SIPR */

  if (!this->raCloseCodec || !this->raDecode || !this->raFlush || !this->raFreeDecoder ||
      !this->raGetFlavorProperty || !this->raOpenCodec2 || !this->raSetFlavor ||
      /*!raSetDLLAccessPath ||*/ !this->raInitDecoder){
    printf ("libareal: (audio) Cannot resolve symbols - incompatible dll: %s\n", 
	    path);
    return 0;
  }

  if (this->raSetDLLAccessPath){

    char path[1024];

    sprintf(path, "DT_Codecs=%s", entry->str_value);
    if (path[strlen(path)-1]!='/'){
      path[strlen(path)+1]=0;
      path[strlen(path)]='/';
    }
    path[strlen(path)+1]=0;

    this->raSetDLLAccessPath(path);
  }

#ifdef LOG
  printf ("libareal: audio decoder loaded successfully\n");
#endif

  return 1;
}

static int init_codec (realdec_decoder_t *this, buf_element_t *buf) {

  int   version, result ;
  int   samples_per_sec, bits_per_sample, num_channels;
  int   subpacket_size, coded_frame_size, codec_data_length;
  int   coded_frame_size2, data_len, flavor;
  int   mode;
  void *extras;
  
  /*
   * extract header data
   */

  version = BE_16 (buf->content);

#ifdef LOG
  printf ("libareal: header buffer detected, header version %d\n", version);
  xine_hexdump (buf->content, buf->size);
#endif
    
  flavor           = BE_16 (buf->content+18);
  coded_frame_size = BE_32 (buf->content+20);
  codec_data_length= BE_16 (buf->content+36);
  coded_frame_size2= BE_16 (buf->content+38);
  subpacket_size   = BE_16 (buf->content+40);
    
  this->sps        = subpacket_size;
  this->w          = coded_frame_size2;
  this->h          = codec_data_length;

  if (version == 4) {
    samples_per_sec = BE_16 (buf->content+44);
    bits_per_sample = BE_16 (buf->content+48);
    num_channels    = BE_16 (buf->content+50);

    /* FIXME: */
    if (buf->type==BUF_AUDIO_COOK) {
      
      printf ("libareal: audio header version 4 for COOK audio not supported.\n");
      abort();
    }
    data_len        = 0; /* FIXME: COOK audio needs this */
    extras          = buf->content+0x43;

  } else {
    samples_per_sec = BE_16 (buf->content+50);
    bits_per_sample = BE_16 (buf->content+54);
    num_channels    = BE_16 (buf->content+56);
    data_len        = BE_32 (buf->content+0x46);
    extras          = buf->content+0x4a;
  }

  this->block_align= coded_frame_size2;

#ifdef LOG
  printf ("libareal: 0x%04x 0x%04x 0x%04x 0x%04x data_len 0x%04x\n",
	  subpacket_size, coded_frame_size, codec_data_length, 
	  coded_frame_size2, data_len);
  printf ("libareal: %d samples/sec, %d bits/sample, %d channels\n",
	  samples_per_sec, bits_per_sample, num_channels);
#endif

  /* load codec, resolv symbols */

  switch (buf->type) {
  case BUF_AUDIO_COOK:
    if (!load_syms_linux (this, "cook.so.6.0"))
      return 0;

    this->stream->meta_info[XINE_META_INFO_AUDIOCODEC] 
      = strdup ("Cook");
    break;
    
  case BUF_AUDIO_ATRK:
    if (!load_syms_linux (this, "atrc.so.6.0"))
      return 0;
    this->block_align = 384;
    this->stream->meta_info[XINE_META_INFO_AUDIOCODEC] 
      = strdup ("Atrac");
    break;

  case BUF_AUDIO_14_4:
    if (!load_syms_linux (this, "14_4.so.6.0"))
      return 0;
    this->stream->meta_info[XINE_META_INFO_AUDIOCODEC] 
      = strdup ("Real 14.4");
    break;

  case BUF_AUDIO_28_8:
    if (!load_syms_linux (this, "28_8.so.6.0"))
      return 0;
    this->stream->meta_info[XINE_META_INFO_AUDIOCODEC] 
      = strdup ("Real 28.8");
    break;

  case BUF_AUDIO_SIPRO:
    if (!load_syms_linux (this, "sipr.so.6.0"))
      return 0;
    /* this->block_align = 19; */
    this->stream->meta_info[XINE_META_INFO_AUDIOCODEC] 
      = strdup ("Sipro");
    break;

  default:
    printf ("libareal: error, i don't handle buf type 0x%08x\n",
	    buf->type);
    abort();
  }

  /*
   * init codec
   */

  result = this->raOpenCodec2 (&this->context);
  if (result) {
    printf ("libareal: error in raOpenCodec2: %d\n", result);
    return 0;
  }

  { 
    ra_init_t init_data;

    init_data.samplerate = samples_per_sec;
    init_data.bits = bits_per_sample;
    init_data.channels = num_channels;
    init_data.unk1 = 100; /* ??? */
    init_data.subpacket_size = subpacket_size; /* subpacket size */
    init_data.coded_frame_size = coded_frame_size; /* coded frame size */
    init_data.codec_data_length = data_len; /* codec data length */
    init_data.extras = extras; /* extras */

#ifdef LOG
    printf ("libareal: init_data:\n");
    xine_hexdump ((char *) &init_data, sizeof (ra_init_t));
    printf ("libareal: extras :\n");
    xine_hexdump (init_data.extras, data_len);
#endif
     
    result = this->raInitDecoder (this->context, &init_data);
    if(result){
      printf ("libareal: decoder init failed, error code: 0x%x\n",
	      result);
      return 0;
    }
  }

  if (this->raSetPwd){
    /* used by 'SIPR' */
    this->raSetPwd (this->context, "Ardubancel Quazanga"); /* set password... lol. */
#ifdef LOG    
    printf ("libareal: password set\n");
#endif
  }

  result = this->raSetFlavor (this->context, flavor);
  if (result){
    printf ("libareal: decoder flavor setup failed, error code: 0x%x\n",
	    result);
    return 0;
  }

  /*
   * alloc buffers for data reordering
   */

  if (this->sps) {

    this->frame_size      = this->w/this->sps*this->h*this->sps;
    this->frame_buffer    = xine_xmalloc (this->frame_size);
    this->frame_reordered = xine_xmalloc (this->frame_size);
    this->frame_num_bytes = 0;

  } else {

    this->frame_size      = this->w*this->h;
    this->frame_buffer    = xine_xmalloc (this->frame_size);
    this->frame_reordered = this->frame_buffer;
    this->frame_num_bytes = 0;

  }

  /*
   * open audio output
   */

  switch (num_channels) {
  case 1:
    mode = AO_CAP_MODE_MONO;
    break;
  case 2:
    mode = AO_CAP_MODE_STEREO;
    break;
  default:
    printf ("libareal: oups, real can do more than 2 channels ?\n");
    abort();
  }

  this->stream->audio_out->open(this->stream->audio_out, 
				this->stream,
				bits_per_sample,
				samples_per_sec,
				mode) ;

  this->output_open = 1;

  this->sample_size = num_channels * (bits_per_sample>>3);

  return 1;
}

static unsigned char sipr_swaps[38][2]={
    {0,63},{1,22},{2,44},{3,90},{5,81},{7,31},{8,86},{9,58},{10,36},{12,68},
    {13,39},{14,73},{15,53},{16,69},{17,57},{19,88},{20,34},{21,71},{24,46},
    {25,94},{26,54},{28,75},{29,50},{32,70},{33,92},{35,74},{38,85},{40,56},
    {42,87},{43,65},{45,59},{48,79},{49,93},{51,89},{55,95},{61,76},{67,83},
    {77,80} };

static void realdec_decode_data (audio_decoder_t *this_gen, buf_element_t *buf) {
  realdec_decoder_t *this = (realdec_decoder_t *) this_gen;

#ifdef LOG
  printf ("libareal: decode_data %d bytes, flags=0x%08x, pts=%lld ...\n", 
	  buf->size, buf->decoder_flags, buf->pts);
#endif

  if (buf->decoder_flags & BUF_FLAG_PREVIEW) {

    /* real_find_sequence_header (&this->real, buf->content, buf->content + buf->size);*/

  } else if (buf->decoder_flags & BUF_FLAG_HEADER) {

    this->decoder_ok = init_codec (this, buf) ;
    if( !this->decoder_ok )
      this->stream->stream_info[XINE_STREAM_INFO_AUDIO_HANDLED] = 0;

  } else if( this->decoder_ok ) {

    int size;

#ifdef LOG
    printf ("libareal: content buffer detected, %d bytes\n", buf->size);
#endif

    if (buf->pts && !this->pts)
      this->pts = buf->pts;

    size = buf->size;

    while (size) {

      int needed;

      needed = this->frame_size - this->frame_num_bytes;

      if (needed>size) {
	
	memcpy (this->frame_buffer+this->frame_num_bytes, buf->content, size);
	this->frame_num_bytes += size;

#ifdef LOG
	printf ("libareal: buffering %d/%d bytes\n", this->frame_num_bytes, this->frame_size);
#endif

	size = 0;

      } else {

	int result;
	int len     =-1;
	int n;
	int sps     = this->sps;
	int w       = this->w;
	int h       = this->h;
	audio_buffer_t *audio_buffer;

#ifdef LOG
	printf ("libareal: buffering %d bytes\n", needed);
#endif

	memcpy (this->frame_buffer+this->frame_num_bytes, buf->content, needed);

	size -= needed;
	this->frame_num_bytes = 0;

#ifdef LOG
	printf ("libareal: frame completed. reordering...\n");
	printf ("libareal: bs=%d  sps=%d  w=%d h=%d \n",/*sh->wf->nBlockAlign*/-1,sps,w,h);
#endif

	if (!sps) {

	  int            j,n;
	  int            bs=h*w*2/96; /* nibbles per subpacket */
	  unsigned char *p=this->frame_buffer;
	  
	  /* 'sipr' way */
	  /* demux_read_data(sh->ds, p, h*w); */
	  for (n=0;n<38;n++){
	    int i=bs*sipr_swaps[n][0];
	    int o=bs*sipr_swaps[n][1];
	    /* swap nibbles of block 'i' with 'o'      TODO: optimize */
	    for (j=0;j<bs;j++) {
	      int x=(i&1) ? (p[(i>>1)]>>4) : (p[(i>>1)]&15);
	      int y=(o&1) ? (p[(o>>1)]>>4) : (p[(o>>1)]&15);
	      if (o&1) 
		p[(o>>1)]=(p[(o>>1)]&0x0F)|(x<<4);
	      else  
		p[(o>>1)]=(p[(o>>1)]&0xF0)|x;

	      if (i&1) 
		p[(i>>1)]=(p[(i>>1)]&0x0F)|(y<<4);
	      else  
		p[(i>>1)]=(p[(i>>1)]&0xF0)|y;

	      ++i;
	      ++o;
	    }
	  }
	  /*
	  sh->a_in_buffer_size=
	    sh->a_in_buffer_len=w*h;
	  */

	} else {
	  int      x, y;
	  uint8_t *s;
	  
	  /*  'cook' way */
	  
	  w /= sps; s = this->frame_buffer;
	  
	  for (y=0; y<h; y++)
	    
	    for (x=0; x<w; x++) {
	      
#ifdef LOG
	      printf ("libareal: x=%d, y=%d, off %d\n",
		      x, y, sps*(h*x+((h+1)/2)*(y&1)+(y>>1)));
#endif
	      
	      memcpy (this->frame_reordered+sps*(h*x+((h+1)/2)*(y&1)+(y>>1)),
		      s, sps);
	      s+=sps;
	      
	      /* demux_read_data(sh->ds, sh->a_in_buffer+sps*(h*x+((h+1)/2)*(y&1)+(y>>1)), 
		 sps); */
	      
	    }
	  /*
	    sh->a_in_buffer_size=
	    sh->a_in_buffer_len=w*h*sps;
	  */
	}

#ifdef LOG
	xine_hexdump (this->frame_reordered, buf->size);
#endif
  
	n = 0;
	while (n<this->frame_size) {

	  audio_buffer = this->stream->audio_out->get_buffer (this->stream->audio_out);

	  result = this->raDecode (this->context, 
				   this->frame_reordered+n,
				   this->block_align,
				   (char *) audio_buffer->mem, &len, -1);

#ifdef LOG
	  printf ("libareal: raDecode result %d, len=%d\n", result, len);
#endif

	  audio_buffer->vpts       = this->pts; 

	  this->pts = 0;

	  audio_buffer->num_frames = len/this->sample_size;;
	  
	  this->stream->audio_out->put_buffer (this->stream->audio_out, 
					       audio_buffer, this->stream);
	  n+=this->block_align;
	}
      }
    }
  }


#ifdef LOG
  printf ("libareal: decode_data...done\n");
#endif
}

static void realdec_reset (audio_decoder_t *this_gen) {
  realdec_decoder_t *this = (realdec_decoder_t *) this_gen;
  
  this->frame_num_bytes = 0;
}

static void realdec_discontinuity (audio_decoder_t *this_gen) {
  realdec_decoder_t *this = (realdec_decoder_t *) this_gen;
  
  this->pts = 0;
}

static void realdec_dispose (audio_decoder_t *this_gen) {

  realdec_decoder_t *this = (realdec_decoder_t *) this_gen;

#ifdef LOG
  printf ("libareal: dispose\n");
#endif

  if (this->context)
    this->raCloseCodec (this->context);

#if 0
  printf ("libareal: FreeDecoder...\n");

  if (this->context)
    this->raFreeDecoder (this->context);
#endif

#ifdef LOG
  printf ("libareal: dlclose...\n");
#endif

  if (this->ra_handle)
    dlclose (this->ra_handle);

  if (this->output_open)
     this->stream->audio_out->close (this->stream->audio_out, this->stream);

  if (this->frame_buffer)
    free (this->frame_buffer);

  free (this);

#ifdef LOG
  printf ("libareal: dispose done\n");
#endif
}

static audio_decoder_t *open_plugin (audio_decoder_class_t *class_gen, 
				     xine_stream_t *stream) {

  real_class_t      *cls = (real_class_t *) class_gen;
  realdec_decoder_t *this ;

  this = (realdec_decoder_t *) malloc (sizeof (realdec_decoder_t));
  memset(this, 0, sizeof (realdec_decoder_t));

  this->audio_decoder.decode_data         = realdec_decode_data;
  this->audio_decoder.reset               = realdec_reset;
  this->audio_decoder.discontinuity       = realdec_discontinuity;
  this->audio_decoder.dispose             = realdec_dispose;
  this->stream                            = stream;
  this->cls                               = cls;

  this->output_open = 0;

  return &this->audio_decoder;
}

/*
 * real plugin class
 */

static char *get_identifier (audio_decoder_class_t *this) {
  return "realadec";
}

static char *get_description (audio_decoder_class_t *this) {
  return "real binary-only codec based audio decoder plugin";
}

static void dispose_class (audio_decoder_class_t *this) {
  free (this);
}

/*
 * some fake functions to make real codecs happy 
 */
void *__builtin_vec_new(unsigned long size) {
  return malloc(size);
}
void __builtin_vec_delete(void *mem) {
  free(mem);
}
void __pure_virtual(void) {
  printf("libareal: FATAL: __pure_virtual() called!\n");
  /*      exit(1); */
}

/*
 * real audio codec loader
 */

static void *init_class (xine_t *xine, void *data) {

  real_class_t       *this;
  config_values_t    *config = xine->config;
  char               *real_codec_path;

  this = (real_class_t *) xine_xmalloc (sizeof (real_class_t));

  this->decoder_class.open_plugin     = open_plugin;
  this->decoder_class.get_identifier  = get_identifier;
  this->decoder_class.get_description = get_description;
  this->decoder_class.dispose         = dispose_class;

  real_codec_path = config->register_string (config, "codec.real_codecs_path", 
					     "unknown",
					     _("path to real player codecs, if installed"),
					     NULL, 10, NULL, NULL);
  
  if (!strcmp (real_codec_path, "unknown")) {

    struct stat s;

    /* try some auto-detection */

    if (!stat ("/usr/local/RealPlayer8/Codecs/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/usr/local/RealPlayer8/Codecs");
    if (!stat ("/usr/RealPlayer8/Codecs/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/usr/RealPlayer8/Codecs");
    if (!stat ("/usr/lib/RealPlayer8/Codecs/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/usr/lib/RealPlayer8/Codecs");
    if (!stat ("/opt/RealPlayer8/Codecs/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/opt/RealPlayer8/Codecs");
    if (!stat ("/usr/lib/RealPlayer9/users/Real/Codecs/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/usr/lib/RealPlayer9/users/Real/Codecs");
    if (!stat ("/usr/lib/win32/drv3.so.6.0", &s)) 
      config->update_string (config, "codec.real_codecs_path", 
			     "/usr/lib/win32");
  }

#ifdef LOG
  printf ("libareal: real codec path : %s\n",  real_codec_path);
#endif

  return this;
}

/*
 * exported plugin catalog entry
 */

static uint32_t audio_types[] = { 
  BUF_AUDIO_COOK, BUF_AUDIO_ATRK, BUF_AUDIO_14_4, BUF_AUDIO_28_8, BUF_AUDIO_SIPRO, 0
 };

static decoder_info_t dec_info_audio = {
  audio_types,         /* supported types */
  5                    /* priority        */
};

plugin_info_t xine_plugin_info[] = {
  /* type, API, "name", version, special_info, init_function */  
  { PLUGIN_AUDIO_DECODER | PLUGIN_MUST_PRELOAD, 13, "realadec", XINE_VERSION_CODE, &dec_info_audio, init_class },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
