//**************************************************************************
//*                     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: ASF demuxing (based on the FFMPEG lib) and tag read/write

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_ASF

#include <malloc.h>
#include <string.h>
#include <stdarg.h>

//#define DEBUG_HEADER 1
//#define DEBUG_FILEINFO 1
//#define DEBUG_DECODE 1

static void asf_debugf(const char *format, ...);

#define ASF_MAX_STREAMS 16
#define ASF_MAX_HEADOBJECTS 32

typedef mpxp_uint8_t ASF_GUID_T[16];

typedef struct {
 mpxp_uint64_t file_size;	// in bytes // invalid if broadcasting
 mpxp_uint64_t create_time;	// time of creation, in 100-nanosecond units since 1.1.1601 // invalid if broadcasting
 mpxp_uint64_t packets_count;   // how many packets are there in the file // invalid if broadcasting
 mpxp_uint64_t play_time;	// play time, in 100-nanosecond units // invalid if broadcasting
 mpxp_uint64_t send_time;	// time to send file, in 100-nanosecond units // invalid if broadcasting (could be ignored)
 mpxp_uint32_t preroll;	        // timestamp of the first packet, in milliseconds // if nonzero - substract from time
 mpxp_uint32_t ignore;          // preroll is 64bit - but let's just ignore it
 mpxp_uint32_t flags;	        // 0x01 - broadcast // 0x02 - seekable
                                // rest is reserved should be 0
 mpxp_uint32_t min_pktsize;	// size of a data packet // invalid if broadcasting
 mpxp_uint32_t max_pktsize;	// shall be the same as for min_pktsize // invalid if broadcasting
 mpxp_uint32_t max_bitrate;	// bandwith of stream in bps // should be the sum of bitrates of the individual media streams

 ASF_GUID_T guid;		        // generated by client computer
}ASFMainHeader;

typedef struct asf_comment_data_s{
 unsigned int typelen;
 char *typestr;
 unsigned int datalen;
 char *datastr;
 mpxp_uint64_t data_number;
 unsigned short data_type; // 0:unicode_str 1:char_str; 2-5:number
 unsigned short hdr_index;
 unsigned char i3i_index;
}asf_comment_data_s;

typedef struct asf_stream_data_s{
 unsigned int streamtype;
 mpxp_uint32_t codec_tag;
 unsigned int need_parsing;

 unsigned int stream_index;

 int id;
 int num;
 unsigned char seq;

 int frag_offset;
 int timestamp;

 int ds_span;		// descrambling
 int ds_packet_size;
 int ds_chunk_size;
 int ds_data_size;
 int ds_silence_data;

 int packet_pos;

 long start_time;
 mpxp_int64_t duration;
 unsigned long bit_rate;

 mpxp_uint8_t *extradata;
 unsigned long extradata_size;

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

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

 //unsigned long seek_entries;
 //mpxp_uint32_t *seek_table;
 //mpxp_uint8_t  *seek_flags;

}asf_stream_data_s;

typedef struct asf_demuxer_data_s{
 struct mpxplay_bitstreambuf_s *bs_packet;
 unsigned int nb_streams;
 unsigned int packet_size;

 mpxp_int64_t nb_packets;
 mpxp_int64_t duration; // in 100ns units

 mpxp_uint8_t *descrambling_buffer;

 mpxp_uint64_t headobjects_size;
 unsigned long headobjects_num;
 unsigned long headobjects_reserved;
 mpxp_filesize_t data_head_offset;
 mpxp_filesize_t comment_head_offset;
 mpxp_filesize_t extcontent_head_offset;
 long comment_head_cursize;
 long extcontent_head_cursize;
 long paddingsize_cur;
 mpxp_filesize_t data_offset; // begining of the first data packet

 int packet_size_left;
 int packet_flags;
 int packet_property;
 int packet_timestamp;
 int packet_segsizetype;
 int packet_segments;
 int packet_seq;
 int packet_replic_size;
 int packet_key_frame;
 int packet_padsize;
 int packet_frag_offset;
 int packet_frag_size;
 int packet_frag_timestamp;
 int packet_multi_size;
 int packet_obj_size;
 int packet_time_delta;
 int packet_time_start;
 int packet_pos;

 int stream_index;

 asf_stream_data_s *ast_audio;
 asf_stream_data_s *ast_video;
 asf_stream_data_s *ast_curr; // currently decoded stream

 ASFMainHeader hdr;
 int asfid2avid[128];           // conversion table from asf ID 2 AVStream ID
 asf_stream_data_s streams[ASF_MAX_STREAMS];

 unsigned int comments_allocated;
 unsigned int comments_loaded;
 struct asf_comment_data_s *comment_datas;

 unsigned long headobjects_allocated;
 unsigned long headobjects_loaded; // till data object
 unsigned long *headobject_sizes;
 char **headobject_fields;
 int fieldindex_commentheader;
 int fieldindex_extcontentheader;

}asf_demuxer_data_s;

static const ASF_GUID_T asf_object_main_header = {
 0x30,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
};
static const ASF_GUID_T asf_object_file_header = {
 0xA1,0xDC,0xAB,0x8C,0x47,0xA9,0xCF,0x11,0x8E,0xE4,0x00,0xC0,0x0C,0x20,0x53,0x65
};
static const ASF_GUID_T asf_object_stream_header = {
 0x91,0x07,0xDC,0xB7,0xB7,0xA9,0xCF,0x11,0x8E,0xE6,0x00,0xC0,0x0C,0x20,0x53,0x65
};
static const ASF_GUID_T asf_object_audio_stream = {
 0x40,0x9E,0x69,0xF8,0x4D,0x5B,0xCF,0x11,0xA8,0xFD,0x00,0x80,0x5F,0x5C,0x44,0x2B
};
#ifdef MPXPLAY_LINK_VIDEO
static const ASF_GUID_T asf_object_video_stream = {
 0xC0,0xEF,0x19,0xBC,0x4D,0x5B,0xCF,0x11,0xA8,0xFD,0x00,0x80,0x5F,0x5C,0x44,0x2B
};
#endif
static const ASF_GUID_T asf_object_comment_header = {
 0x33,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
};
static const ASF_GUID_T asf_object_data_header = {
 0x36,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
};
static const ASF_GUID_T asf_object_extended_content = {
 0x40,0xA4,0xD0,0xD2,0x07,0xE3,0xD2,0x11,0x97,0xF0,0x00,0xA0,0xC9,0x5E,0xA8,0x50
};
/*static const ASF_GUID_T simple_index_header = {
 0x90,0x08,0x00,0x33,0xB1,0xE5,0xCF,0x11,0x89,0xF4,0x00,0xA0,0xC9,0x03,0x49,0xCB
};*/
static const ASF_GUID_T asf_object_header_extension = {
 0xb5, 0x03, 0xbf, 0x5f, 0x2E, 0xA9, 0xCF, 0x11, 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65
};
static const ASF_GUID_T asf_object_padding = {
 0x74, 0xd4, 0x06, 0x18, 0xdf, 0xca, 0x09, 0x45, 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8
};

static struct asf_stream_data_s *asf_get_stream(struct asf_demuxer_data_s *asf,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi);
static void asf_assign_audio(struct asf_demuxer_data_s *asf,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode);

static unsigned int asf_read_header(struct asf_demuxer_data_s *asf,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,mpxp_uint32_t openmode);
static unsigned int asf_read_wavheader(asf_stream_data_s *ast, struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds, int size);
static void asf_read_str16_nolen(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds, int len, unsigned short *buf, int buf_size);
static int asf_alloc_and_read_tag(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct asf_comment_data_s *cd,unsigned int typelen,unsigned short *typestr,int datalen,unsigned int datatype,unsigned int i3i,unsigned int hdr_index,mpxp_uint32_t openmode);
static struct asf_comment_data_s *asf_comments_expand(struct asf_demuxer_data_s *asf,unsigned int expsize);
#define ASF_READ_GUID(fbfs,fbds,g) fbfs->fread(fbds,g,sizeof(ASF_GUID_T))

static int ASF_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct asf_demuxer_data_s *asfi=NULL;

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

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

 asfi=(struct asf_demuxer_data_s *)calloc(1,sizeof(struct asf_demuxer_data_s));
 if(!asfi)
  goto err_out_open;
 miis->private_data=asfi;

 if(!asf_read_header(asfi,fbfs,fbds,openmode))
  goto err_out_open;

 asfi->ast_audio=asf_get_stream(asfi,MPXPLAY_SPI_STREAMTYPE_AUDIO,miis->audio_stream);
#ifdef MPXPLAY_LINK_VIDEO
 asfi->ast_video=asf_get_stream(asfi,MPXPLAY_SPI_STREAMTYPE_VIDEO,miis->video_stream);
#endif

 if(!asfi->ast_audio && !asfi->ast_video)
  goto err_out_open;

 asf_assign_audio(asfi,miis,openmode);

 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  asfi->descrambling_buffer=(mpxp_uint8_t *)malloc(asfi->hdr.max_pktsize);
  if(!asfi->descrambling_buffer)
   goto err_out_open;
  asfi->bs_packet=mpxplay_bitstream_alloc(asfi->hdr.max_pktsize);
  if(!asfi->bs_packet)
   goto err_out_open;
 }

 return MPXPLAY_ERROR_INFILE_OK;

err_out_open:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void ASF_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct asf_demuxer_data_s *asfi=(struct asf_demuxer_data_s *)miis->private_data;
 unsigned int i;
 if(asfi){
  for(i=0;i<asfi->nb_streams;i++){
   asf_stream_data_s *ast=&asfi->streams[i];
   if(ast->extradata)
    free(ast->extradata);
   //if(ast->seek_table)
   // free(ast->seek_table);
   //if(ast->seek_flags)
   // free(ast->seek_flags);
  }
  if(asfi->descrambling_buffer)
   free(asfi->descrambling_buffer);
  mpxplay_bitstream_free(asfi->bs_packet);
  if(asfi->comment_datas){
   struct asf_comment_data_s *cd=asfi->comment_datas;
   for(i=0;i<asfi->comments_allocated;i++,cd++){
    if(cd->typestr)
     free(cd->typestr);
    if(cd->datastr)
     free(cd->datastr);
   }
   free(asfi->comment_datas);
  }
  free(asfi);
 }
 fbfs->fclose(fbds);
}

static struct asf_stream_data_s *asf_get_stream(struct asf_demuxer_data_s *asf,unsigned int streamtype,struct mpxplay_streampacket_info_s *spi)
{
 unsigned int i,streamtype_count=0;
 struct asf_stream_data_s *ast=&asf->streams[0],*found_stream=NULL;

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

static void asf_assign_audio(struct asf_demuxer_data_s *asf,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 struct mpxplay_streampacket_info_s  *spi=miis->audio_stream;
 struct asf_stream_data_s *ast=asf->ast_audio;

 if(!ast)
  return;

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

 adi->filechannels=adi->outchannels=ast->channels;
 adi->freq=ast->sample_rate;
 adi->bits=ast->bits_per_sample;
 if((spi->wave_id!=MPXPLAY_WAVEID_PCM_SLE) && (spi->wave_id!=MPXPLAY_WAVEID_PCM_FLOAT))
  adi->bitrate=(ast->bit_rate+500)/1000;

 miis->timemsec=ast->duration;

#ifdef DEBUG_FILEINFO
 asf_debugf("st:%d wd:%4.4X fs:%d c:%d f:%d b:%d br:%d ba:%d d:%d mps:%d\n",
  spi->streamtype,spi->wave_id,spi->bs_framesize,adi->filechannels,adi->freq,
  adi->bits,adi->bitrate,spi->block_align,(long)ast->duration,(long)asf->hdr.max_pktsize);
 {
  unsigned int i;
  char sout[128];
  sprintf(sout,"%8.8X %d ",spi->extradata,spi->extradata_size);
  for(i=0;i<spi->extradata_size;i++)
   sprintf(sout,"%s%2.2X",sout,spi->extradata[i]);
  asf_debugf(sout);
 }
#endif
}

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

#define FRAME_HEADER_SIZE 17 // Fix Me! FRAME_HEADER_SIZE may be different.

static unsigned int asf_read_header(struct asf_demuxer_data_s *asf,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,mpxp_uint32_t openmode)
{
 asf_stream_data_s *ast;
#ifdef MPXPLAY_LINK_VIDEO
 long size;
#endif
 mpxp_filesize_t filepos;
 mpxp_uint64_t gsize;
 ASF_GUID_T g;
 unsigned short name[128];

 ASF_READ_GUID(fbfs,fbds, &g);
 if(memcmp(&g, &asf_object_main_header, sizeof(ASF_GUID_T)))
  return 0;
 asf->headobjects_size=fbfs->get_le64(fbds);
 asf->headobjects_num=fbfs->get_le32(fbds);
 fbfs->get_byte(fbds);
 fbfs->get_byte(fbds);
 memset(&asf->asfid2avid, -1, sizeof(asf->asfid2avid));
 for(;;) {
  ASF_READ_GUID(fbfs,fbds, &g);
  gsize = fbfs->get_le64(fbds);
#ifdef DEBUG_HEADER
  asf_debugf(" pos:%d size=%lld %8.8X %8.8X", (long)fbfs->ftell(fbds) - 24, gsize, PDS_GETB_BE32(&g),PDS_GETB_LE32(&g));
#endif
  if(gsize < 24)
   return 0;
  filepos=fbfs->ftell(fbds);
  if(memcmp(&g, &asf_object_file_header, sizeof(ASF_GUID_T))==0) {
   ASF_READ_GUID(fbfs,fbds, &asf->hdr.guid);
   asf->hdr.file_size	 = fbfs->get_le64(fbds);
   asf->hdr.create_time	 = fbfs->get_le64(fbds);
   asf->hdr.packets_count= fbfs->get_le64(fbds);
   asf->hdr.play_time	 = fbfs->get_le64(fbds);
   asf->hdr.send_time	 = fbfs->get_le64(fbds);
   asf->hdr.preroll	 = fbfs->get_le32(fbds);
   asf->hdr.ignore	 = fbfs->get_le32(fbds);
   asf->hdr.flags	 = fbfs->get_le32(fbds);
   asf->hdr.min_pktsize	 = fbfs->get_le32(fbds);
   asf->hdr.max_pktsize	 = fbfs->get_le32(fbds);
   asf->hdr.max_bitrate	 = fbfs->get_le32(fbds);
   asf->packet_size      = asf->hdr.max_pktsize;
   asf->nb_packets       = asf->hdr.packets_count;

  }else if(memcmp(&g, &asf_object_stream_header, sizeof(ASF_GUID_T))==0) {
   int type, total_size, type_specific_size;
#ifdef MPXPLAY_LINK_VIDEO
   int sizeX;
   unsigned int tag1;
#endif
   mpxp_filesize_t pos1, pos2;

   if(asf->nb_streams>=ASF_MAX_STREAMS)
    goto skip_header;

   pos1 = filepos;

   ast = &asf->streams[asf->nb_streams];
   ast->start_time = asf->hdr.preroll;
   ast->duration = asf->hdr.send_time / (10000000 / 1000) - ast->start_time;
   ASF_READ_GUID(fbfs,fbds, &g);
   if(!memcmp(&g, &asf_object_audio_stream, sizeof(ASF_GUID_T))) {
    type = MPXPLAY_SPI_STREAMTYPE_AUDIO;
   }else
#ifdef MPXPLAY_LINK_VIDEO
    if(!memcmp(&g, &asf_object_video_stream, sizeof(ASF_GUID_T))) {
     type = MPXPLAY_SPI_STREAMTYPE_VIDEO;
    }else
#endif
    {
#ifdef DEBUG_FILEINFO
     asf_debugf("bad streamtype\n");
#endif
     goto skip_header;
    }

   ASF_READ_GUID(fbfs,fbds, &g);
   total_size = fbfs->get_le64(fbds);
   type_specific_size = fbfs->get_le32(fbds);
   fbfs->get_le32(fbds);
   ast->id = fbfs->get_le16(fbds) & 0x7f; // stream id
   asf->asfid2avid[ast->id] = asf->nb_streams; // mapping of asf ID to AV stream ID
#ifdef DEBUG_FILEINFO
   asf_debugf("ast_id:%d \n",ast->id);
#endif

   fbfs->get_le32(fbds);
   ast->streamtype = type;
   if(type == MPXPLAY_SPI_STREAMTYPE_AUDIO) {
    if(!asf_read_wavheader(ast,fbfs,fbds,type_specific_size))
     return 0;
    ast->need_parsing = 1;
    pos2 = fbfs->ftell(fbds);
    if (gsize > (pos2 + 8 - pos1 + 24)) {  // descrambling
     ast->ds_span = fbfs->get_byte(fbds);
     ast->ds_packet_size = fbfs->get_le16(fbds);
     ast->ds_chunk_size = fbfs->get_le16(fbds);
     ast->ds_data_size = fbfs->get_le16(fbds);
     ast->ds_silence_data = fbfs->get_byte(fbds);
    }
    if (ast->ds_span > 1) {
     if (!ast->ds_chunk_size || (ast->ds_packet_size/ast->ds_chunk_size <= 1))
      ast->ds_span = 0; // disable descrambling
    }
   }else{
#ifdef MPXPLAY_LINK_VIDEO
    fbfs->get_le32(fbds);
    fbfs->get_le32(fbds);
    fbfs->get_byte(fbds);
    size = fbfs->get_le16(fbds); /* size */
    sizeX= fbfs->get_le32(fbds); /* size */
    ast->video_res_x = fbfs->get_le32(fbds);
    ast->video_res_y = fbfs->get_le32(fbds);
    // not available for asf
    fbfs->get_le16(fbds); // panes
    ast->video_bpp = fbfs->get_le16(fbds); // depth
    tag1 = fbfs->get_le32(fbds);
    fbfs->fseek(fbds,20,SEEK_CUR);
    size= sizeX;
    if (size > 40) {
     ast->extradata_size = size - 40;
     ast->extradata = malloc(ast->extradata_size + MPXPLAY_SPI_EXTRADATA_PADDING);
     if(!ast->extradata)
      return 0;
     if(fbfs->fread(fbds, ast->extradata, ast->extradata_size)!=ast->extradata_size)
      return 0;
     pds_memset(ast->extradata+ast->extradata_size,0,MPXPLAY_SPI_EXTRADATA_PADDING);
    }

    ast->codec_tag = tag1;
    if(tag1 == PDS_GET4C_LE32('D','V','R',' '))
     ast->need_parsing = 1;
#endif
   }
   pos2 = fbfs->ftell(fbds);
   fbfs->fseek(fbds,(gsize - (pos2 - pos1 + 24)),SEEK_CUR);
   asf->nb_streams++;

  }else if (!memcmp(&g, &asf_object_comment_header, sizeof(ASF_GUID_T))) {
   struct asf_comment_data_s *newdatas;
   unsigned int i,hsize;
   int lens[5];
   static unsigned char ch_i3is[5]={I3I_TITLE,I3I_ARTIST,I3I_INVALID,I3I_COMMENT,I3I_INVALID};

   if(asf->comment_head_cursize || !funcbit_test(openmode,(MPXPLAY_INFILE_OPENMODE_INFO_ID3|MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD)))
    goto skip_header;

   asf->comment_head_offset=filepos-24;
   asf->comment_head_cursize=gsize;
   if(asf->comment_head_cursize<=(24+10))
    goto skip_header;

   newdatas=asf_comments_expand(asf,5);
   if(!newdatas)
    return 0;

   for(i=0;i<5;i++)
    lens[i]=fbfs->get_le16(fbds);
   hsize=gsize-24-10;
   for(i=0;i<5;i++,newdatas++){
    if(lens[i]>hsize)
     break;
    hsize-=lens[i];
    if(asf_alloc_and_read_tag(fbfs,fbds,newdatas,0,NULL,lens[i],0,ch_i3is[i],i,openmode)<0)
     return 0;
   }
   goto skip_header;
  }else if (!memcmp(&g, &asf_object_extended_content, sizeof(ASF_GUID_T))) {
   int desc_count, i, hsize;
   struct asf_comment_data_s *newdatas;

   if(asf->extcontent_head_cursize || !funcbit_test(openmode,(MPXPLAY_INFILE_OPENMODE_INFO_ID3|MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD)))
    goto skip_header;
   asf->extcontent_head_offset=filepos-24;
   asf->extcontent_head_cursize=gsize;
   if(asf->extcontent_head_cursize<=(24+2))
    goto skip_header;

   desc_count = fbfs->get_le16(fbds);
   if(!desc_count)
    goto skip_header;

   newdatas=asf_comments_expand(asf,desc_count);
   if(!newdatas)
    goto skip_header;

   hsize=gsize-24-2;

   for(i=0;i<desc_count;i++,newdatas++){
    int name_len,value_type,value_len;
    if(hsize<=2)
     break;
    hsize-=2;
    name_len = fbfs->get_le16(fbds);
    if(hsize<=(name_len+2+2))
     break;
    hsize-=name_len+2+2;
    asf_read_str16_nolen(fbfs,fbds, name_len, name, sizeof(name));
    value_type = fbfs->get_le16(fbds);
    value_len  = fbfs->get_le16(fbds);
    //fprintf(stdout,"i:%d/%d hs:%3d nl:%2d (%4.4x) t:%d dl:%d (%4.4x)\n",i,desc_count,hsize,name_len,name_len,value_type,value_len,value_len);
    if(hsize<value_len)
     break;
    if(asf_alloc_and_read_tag(fbfs,fbds,newdatas,name_len,name,value_len,value_type,I3I_INVALID,i+5,openmode)<0)
     return 0;
   }
   goto skip_header;
  }else if (!memcmp(&g, &asf_object_data_header, sizeof(ASF_GUID_T))){
   break;
  }else if(fbfs->eof(fbds)) {
   return 0;
  }else{
skip_header:
   if(fbfs->fseek(fbds, filepos+gsize - 24, SEEK_SET)<0)
    return 0;
  }
 }

 ASF_READ_GUID(fbfs,fbds, &g);
 fbfs->get_le64(fbds);
 fbfs->get_byte(fbds);
 fbfs->get_byte(fbds);
 if(fbfs->eof(fbds))
  return 0;

 asf->data_offset = fbfs->ftell(fbds);
 asf->packet_size_left = 0;

#ifdef DEBUG_HEADER
 asf_debugf("read head ok\n");
#endif

 return 1;
}

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

 if(size > 16){  // WAVEFORMATEX
  ast->extradata_size = fbfs->get_le16(fbds);
  if(ast->extradata_size){
   if(ast->extradata_size > (size - 18))
    ast->extradata_size = size - 18;
   ast->extradata = malloc(ast->extradata_size + MPXPLAY_SPI_EXTRADATA_PADDING);
   if(!ast->extradata)
    return 0;
   if(fbfs->fread(fbds, ast->extradata, ast->extradata_size)!=ast->extradata_size)
    return 0;
   pds_memset(ast->extradata+ast->extradata_size,0,MPXPLAY_SPI_EXTRADATA_PADDING);
#ifdef DEBUG_FILEINFO
   asf_debugf("wav: %8.8X %d %2.2X%2.2X%2.2X%2.2X%2.2X%2.2X \n",ast->extradata,ast->extradata_size,
    ast->extradata[0],ast->extradata[1],ast->extradata[2],ast->extradata[3],ast->extradata[4],ast->extradata[5]);
#endif
  }
  if((size - ast->extradata_size - 18) > 0) // skip garbage at the end
   fbfs->fseek(fbds, (size - ast->extradata_size - 18), SEEK_CUR);
 }
 return 1;
}

static void asf_read_str16_nolen(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds, int len, unsigned short *buf, int buf_size)
{
 unsigned int rl=(min(len,buf_size))&(~1);
 fbfs->fread(fbds,buf,rl);
 PDS_PUTB_LE16(&buf[rl],0);
 if(rl<len)
  fbfs->fseek(fbds,SEEK_CUR,(len-rl));
}

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

#define DO_2BITS(bits, var, defval) \
 switch ((bits) & 3){ \
  case 3: var = mpxplay_bitstream_get_le32(asf->bs_packet); rsize += 4; break; \
  case 2: var = mpxplay_bitstream_get_le16(asf->bs_packet); rsize += 2; break; \
  case 1: var = mpxplay_bitstream_get_byte(asf->bs_packet); rsize++; break; \
  default: var = defval; break; \
 }

static int asf_get_packet(asf_demuxer_data_s *asf,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 mpxp_uint32_t packet_length, padsize;
 mpxp_filesize_t datapos;
 int rsize = 9;
 int c;

resync:
#if DEBUG_DECODE
 asf_debugf("get_packet: resync");
 if(kbhit()){
  getch();
  return MPXPLAY_ERROR_INFILE_EOF;
 }
#endif

 datapos=fbfs->ftell(fbds) - asf->data_offset;

 if(datapos%asf->packet_size){
#ifdef DEBUG_DECODE
  asf_debugf("get_packet: bad align %d\n",datapos);
#endif
  datapos+=asf->packet_size;
  datapos-=datapos%asf->packet_size;
  if((datapos+asf->data_offset)>=fbfs->filelength(fbds))
   return MPXPLAY_ERROR_INFILE_EOF;
  if(fbfs->fseek(fbds,(datapos+asf->data_offset),SEEK_SET)<0){
   #ifdef DEBUG_DECODE
    asf_debugf("get_packet: seek failed %d\n",datapos+asf->data_offset);
   #endif
   return MPXPLAY_ERROR_INFILE_NODATA;
  }
 }

 asf->packet_pos=datapos+asf->data_offset;

 mpxplay_bitstream_reset(asf->bs_packet);
 if(mpxplay_bitstream_fill(asf->bs_packet,fbfs,fbds,asf->packet_size)<0){
  fbfs->fseek(fbds,(datapos+asf->data_offset),SEEK_SET);
  return MPXPLAY_ERROR_INFILE_NODATA;
 }

 c = mpxplay_bitstream_get_byte(asf->bs_packet);

#ifdef DEBUG_DECODE
 asf_debugf("get_packet: data read ok %2.2X %2.2X %5d %d",c,PDS_GETB_LE32(mpxplay_bitstream_getbufpos(asf->bs_packet)-1),mpxplay_bitstream_leftbytes(asf->bs_packet),datapos);
#endif

 if(c!=0x82)
  goto resync;

 if(mpxplay_bitstream_get_le16(asf->bs_packet)!=0)
  goto resync;

 rsize+=2;

 asf->packet_flags = mpxplay_bitstream_get_byte(asf->bs_packet);
 asf->packet_property = mpxplay_bitstream_get_byte(asf->bs_packet);

 DO_2BITS(asf->packet_flags >> 5, packet_length, asf->packet_size);
 DO_2BITS(asf->packet_flags >> 1, padsize, 0); // sequence ignored
 DO_2BITS(asf->packet_flags >> 3, padsize, 0); // padding length

 asf->packet_timestamp = mpxplay_bitstream_get_le32(asf->bs_packet);
 mpxplay_bitstream_get_le16(asf->bs_packet); // duration
 // rsize has at least 11 bytes which have to be present

 if(asf->packet_flags & 0x01) {
  asf->packet_segsizetype = mpxplay_bitstream_get_byte(asf->bs_packet); rsize++;
  asf->packet_segments = asf->packet_segsizetype & 0x3f;
 }else{
  asf->packet_segments = 1;
  asf->packet_segsizetype = 0x80;
 }

 asf->packet_size_left = packet_length - padsize - rsize;
 if(packet_length < asf->hdr.min_pktsize)
  padsize += asf->hdr.min_pktsize - packet_length;
 asf->packet_padsize = padsize;


#ifdef DEBUG_DECODE
 asf_debugf("packet: size=%d padsize=%d  left=%d\n", asf->packet_size, asf->packet_padsize, asf->packet_size_left);
#endif
 return MPXPLAY_ERROR_INFILE_OK;
}

static int ASF_infile_decode(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct asf_demuxer_data_s *asf=(struct asf_demuxer_data_s *)miis->private_data;
 struct mpxplay_streampacket_info_s *spi=NULL;
 asf_stream_data_s *ast = 0;
#ifdef DEBUG_DECODE
 static unsigned int pc=0;
#endif

 for(;;){
  int rsize = 0;
  if((asf->packet_size_left<FRAME_HEADER_SIZE) || (asf->packet_segments<1)){
   int ret;
#ifdef DEBUG_DECODE
   asf_debugf("PacketLeftSize:%d  Pad:%d Pos:%d\n", (long)asf->packet_size_left, (long)asf->packet_padsize, (long)fbfs->ftell(fbds));
#endif
   ret = asf_get_packet(asf,fbfs,fbds);
#ifdef DEBUG_DECODE
   asf_debugf("READ ASF PACKET  %d   r:%d   c:%d\n", ret, asf->packet_size_left, pc++);
#endif
   if(ret!=MPXPLAY_ERROR_INFILE_OK)
    return ret;
   asf->packet_time_start = 0;
   continue;
  }
  if(asf->packet_time_start == 0) {
   // read frame header
   int num = mpxplay_bitstream_get_byte(asf->bs_packet);
   asf->packet_segments--;
   rsize++;
   asf->packet_key_frame = (num & 0x80) >> 7;
   asf->stream_index = asf->asfid2avid[num & 0x7f];
   // sequence should be ignored!
   DO_2BITS(asf->packet_property >> 4, asf->packet_seq, 0);
   DO_2BITS(asf->packet_property >> 2, asf->packet_frag_offset, 0);
   DO_2BITS(asf->packet_property     , asf->packet_replic_size, 0);
#ifdef DEBUG_DECODE
   asf_debugf("key:%d num:%d stream:%d seq:%d offset:%d replic_size:%d\n",
    asf->packet_key_frame, num, asf->stream_index, asf->packet_seq, asf->packet_frag_offset, asf->packet_replic_size);
#endif
   if(asf->packet_replic_size > 1){
    if(asf->packet_replic_size < 8)
     goto fail;
    // it should be always at least 8 bytes - FIXME validate
    asf->packet_obj_size = mpxplay_bitstream_get_le32(asf->bs_packet);
    asf->packet_frag_timestamp = mpxplay_bitstream_get_le32(asf->bs_packet); // timestamp
    if(asf->packet_replic_size > 8)
     mpxplay_bitstream_skipbytes(asf->bs_packet,(asf->packet_replic_size-8));
    rsize += asf->packet_replic_size; // FIXME - check validity
   }else
    if(asf->packet_replic_size==1){
     // multipacket - frag_offset is begining timestamp
     asf->packet_time_start = asf->packet_frag_offset;
     asf->packet_frag_offset = 0;
     asf->packet_frag_timestamp = asf->packet_timestamp;
     asf->packet_time_delta = mpxplay_bitstream_get_byte(asf->bs_packet);
     rsize++;
    }else{ // asf->packet_replic_size==0
     goto fail;
    }
   if(asf->packet_flags & 0x01) {
    DO_2BITS(asf->packet_segsizetype >> 6, asf->packet_frag_size, 0); // 0 is illegal
    //printf("Fragsize %d\n", asf->packet_frag_size);
   }else{
    asf->packet_frag_size = asf->packet_size_left - rsize;
#ifdef DEBUG_DECODE
    asf_debugf("Using rest  %d %d %d\n", asf->packet_frag_size, asf->packet_size_left, rsize);
#endif
   }
   if(asf->packet_replic_size == 1) {
    asf->packet_multi_size = asf->packet_frag_size;
    if (asf->packet_multi_size > asf->packet_size_left)
     goto fail;
   }
   asf->packet_size_left -= rsize;
#ifdef DEBUG_DECODE
   asf_debugf("___objsize____  %d   %d    rs:%d\n", asf->packet_obj_size, asf->packet_frag_offset, rsize);
#endif

   if(asf->stream_index < 0
    //|| asf->streams[asf->stream_index]->discard >= AVDISCARD_ALL
    //|| (!asf->packet_key_frame && s->streams[asf->stream_index]->discard >= AVDISCARD_NONKEY)
   ){
    asf->packet_time_start = 0;
    // unhandled packet (should not happen)
    mpxplay_bitstream_skipbytes(asf->bs_packet,asf->packet_frag_size);
    asf->packet_size_left -= asf->packet_frag_size;
    continue;
   }
   asf->ast_curr = &asf->streams[asf->stream_index];
  }

  ast = asf->ast_curr;

  if((asf->packet_frag_offset!=ast->frag_offset || (asf->packet_frag_offset && asf->packet_seq!=ast->seq))){ // seq should be ignored
   // cannot continue current packet
   if(spi)
    spi->bs_leftbytes= 0;
   ast->frag_offset = 0;
   if(asf->packet_frag_offset != 0) {
    mpxplay_bitstream_skipbytes(asf->bs_packet,asf->packet_frag_size);
    asf->packet_size_left -= asf->packet_frag_size;
    continue;
   }
  }

  if(asf->packet_replic_size == 1) {
   // frag_offset is here used as the begining timestamp
   asf->packet_frag_timestamp = asf->packet_time_start;
   asf->packet_time_start += asf->packet_time_delta;
   asf->packet_obj_size = asf->packet_frag_size = mpxplay_bitstream_get_byte(asf->bs_packet);
   asf->packet_size_left--;
   asf->packet_multi_size--;
   if(asf->packet_multi_size < asf->packet_obj_size){
    asf->packet_time_start = 0;
    mpxplay_bitstream_skipbytes(asf->bs_packet,asf->packet_multi_size);
    asf->packet_size_left -= asf->packet_multi_size;
    continue;
   }
   asf->packet_multi_size -= asf->packet_obj_size;
   //printf("COMPRESS size  %d  %d  %d   ms:%d\n", asf->packet_obj_size, asf->packet_frag_timestamp, asf->packet_size_left, asf->packet_multi_size);
  }

  switch(asf->streams[asf->stream_index].streamtype){
   case MPXPLAY_SPI_STREAMTYPE_AUDIO:spi=miis->audio_stream;break;
#ifdef MPXPLAY_LINK_VIDEO
   case MPXPLAY_SPI_STREAMTYPE_VIDEO:spi=miis->video_stream;break;
#endif
   default:asf->packet_time_start = 0;                              // ???
           mpxplay_bitstream_skipbytes(asf->bs_packet,asf->packet_frag_size);
           asf->packet_size_left -= asf->packet_frag_size;          //
           continue;                                                //
  }

  if(ast->frag_offset == 0) { // new packet
   ast->seq = asf->packet_seq;
   //ast->pkt.pts = asf->packet_frag_timestamp;
   ast->stream_index = asf->stream_index;
   ast->packet_pos= asf->packet_pos;
   if(asf->streams[asf->stream_index].streamtype == MPXPLAY_SPI_STREAMTYPE_AUDIO)
    asf->packet_key_frame = 1;
  }

  asf->packet_size_left -= asf->packet_frag_size;
  if(asf->packet_size_left < 0)
   continue;
#ifdef DEBUG_DECODE
  asf_debugf("read data: lb:%d fs:%d",mpxplay_bitstream_leftbytes(asf->bs_packet),asf->packet_frag_size);
#endif

  if(mpxplay_bitstream_readbytes(asf->bs_packet,spi->bitstreambuf+asf->packet_frag_offset,asf->packet_frag_size)!=asf->packet_frag_size)
   goto fail;
  ast->frag_offset += asf->packet_frag_size;
  spi->bs_leftbytes+= asf->packet_frag_size;

  if(ast->frag_offset >= spi->bs_leftbytes) {
   ast->frag_offset = 0;
   if(ast->ds_span > 1){ // packet descrambling
    int offset;
#ifdef DEBUG_DECODE
    asf_debugf("descrambling: bsb:%d dsp:%d ",spi->bs_leftbytes,ast->ds_span);
#endif
    if(spi->bs_leftbytes>=asf->hdr.max_pktsize){ // paket size is greater than allocated descrambling buffer
     spi->bs_leftbytes=0;
     continue;
    }
    offset=0;
    while(offset < spi->bs_leftbytes){
     int off = offset / ast->ds_chunk_size;
     int row = off / ast->ds_span;
     int col = off % ast->ds_span;
     int idx = row + col * ast->ds_packet_size / ast->ds_chunk_size;
     //asf_debugf("off:%d  row:%d  col:%d  idx:%d\n", off, row, col, idx);
     memcpy(asf->descrambling_buffer + offset,spi->bitstreambuf + idx * ast->ds_chunk_size,ast->ds_chunk_size);
     offset += ast->ds_chunk_size;
    }
    memcpy(spi->bitstreambuf,asf->descrambling_buffer,spi->bs_leftbytes);
   }
#ifdef DEBUG_DECODE
   asf_debugf("packet %d %d\n", spi->bs_leftbytes, asf->packet_frag_size);
#endif
   break; // packet completed
  }
  continue;
fail:
  asf->packet_segments=0;
  asf->packet_size_left=0;
  asf->packet_time_start=0;
 }
 return MPXPLAY_ERROR_INFILE_OK;
}

static long ASF_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
 asf_demuxer_data_s *asf = miis->private_data;
 long packetnum;
 mpxp_filesize_t newfilepos;

 packetnum=(long)((float)asf->nb_packets*(float)newmpxframenum/(float)miis->allframes);
 newfilepos=packetnum*asf->packet_size+asf->data_offset;

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

 return newmpxframenum;
}

static void ASF_infile_clearbuff(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,unsigned int mpx_seektype)
{
 asf_demuxer_data_s *asf = miis->private_data;
 asf_stream_data_s *ast;
 int i;

 asf->packet_size_left = 0;
 asf->packet_segments = 0;
 asf->packet_flags = 0;
 asf->packet_property = 0;
 asf->packet_timestamp = 0;
 asf->packet_segsizetype = 0;
 asf->packet_segments = 0;
 asf->packet_seq = 0;
 asf->packet_replic_size = 0;
 asf->packet_key_frame = 0;
 asf->packet_padsize = 0;
 asf->packet_frag_offset = 0;
 asf->packet_frag_size = 0;
 asf->packet_frag_timestamp = 0;
 asf->packet_multi_size = 0;
 asf->packet_obj_size = 0;
 asf->packet_time_delta = 0;
 asf->packet_time_start = 0;

 for(i=0; i<asf->nb_streams; i++){
  ast= &asf->streams[i];
  ast->frag_offset=0;
  ast->seq=0;
 }
}

//-------------------------------------------------------------------------
//static char *asf_typestrings[I3I_MAX+1]={NULL,NULL,"WM/AlbumTitle","WM/Year",NULL,"WM/Genre","WM/TrackNumber"};
static char *asf_typestrings[I3I_MAX+1]={NULL,NULL,"WM/AlbumTitle","WM/Year",NULL,"WM/Genre","WM/Track"};
static signed char asf_commentheader_indexes[I3I_MAX+1]={0,1,-1,-1,3,-1,-1};

static struct asf_comment_data_s *asf_comments_expand(struct asf_demuxer_data_s *asf,unsigned int expsize)
{
 struct asf_comment_data_s *newdatas;
 if(asf->comment_datas){
  newdatas=calloc(asf->comments_allocated+expsize,sizeof(struct asf_comment_data_s));
  if(!newdatas)
   return NULL;
  pds_memcpy(newdatas,asf->comment_datas,(asf->comments_allocated*sizeof(struct asf_comment_data_s)));
  free(asf->comment_datas);
  asf->comment_datas=newdatas;
  newdatas+=asf->comments_allocated;
 }else{
  asf->comment_datas=newdatas=calloc(expsize,sizeof(struct asf_comment_data_s));
  if(!newdatas)
   return NULL;
 }
 asf->comments_allocated+=expsize;
 return newdatas;
}

static int asf_alloc_and_read_tag(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,
    struct asf_comment_data_s *cd,unsigned int typelen,unsigned short *typestr,
    int datalen,unsigned int datatype,unsigned int i3i,unsigned int hdr_index,mpxp_uint32_t openmode)
{
 char *dataptr=NULL,*typeptr=NULL;
 mpxp_uint64_t data_number=0;
 char strtmp[256];

 //fprintf(stdout,"hdr:%2d ",hdr_index);
 cd->hdr_index=hdr_index;
 if((typelen>=2) && typestr && PDS_GETB_LE16(typestr)){
  typeptr=(char *)malloc(typelen+2);
  if(!typeptr)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  pds_memcpy(typeptr,typestr,typelen);
  PDS_PUTB_LE16(&typeptr[typelen],0);
  if(i3i==I3I_INVALID){
   if(mpxplay_playlist_textconv_by_texttypes(MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_UTF16LE,MPXPLAY_TEXTCONV_TYPE_CHAR),
       (char *)typestr,typelen,strtmp,sizeof(strtmp)-2)>0){
    if(strcmp(strtmp,asf_typestrings[I3I_ALBUM])==0)
     i3i=I3I_ALBUM;
    else if(strcmp(strtmp,asf_typestrings[I3I_YEAR])==0)
     i3i=I3I_YEAR;
    else if(strcmp(strtmp,asf_typestrings[I3I_GENRE])==0)
     i3i=I3I_GENRE;
    else if((strcmp(strtmp,asf_typestrings[I3I_TRACKNUM])==0) || (strcmp(strtmp,"WM/TrackNumber")==0))
     i3i=I3I_TRACKNUM;
    //fprintf(stdout,"tn:%s ",strtmp);
   }
  }
 }
 cd->i3i_index=i3i;
 //fprintf(stdout,"i3i:%2d dt:%2d dl:%d\n",i3i,datatype,datalen);

 if(datalen && (funcbit_test(openmode,MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD) || (i3i!=I3I_INVALID))){
  switch(datatype){
   case 2:
   case 3:if(datalen!=4) goto err_out_data; data_number=fbfs->get_le32(fbds);break;
   case 4:if(datalen!=8) goto err_out_data; data_number=fbfs->get_le64(fbds);break;
   case 5:if(datalen!=2) goto err_out_data; data_number=fbfs->get_le16(fbds);break;
   case 0:if(datalen<2) goto err_out_data; // unicode
   case 1: // char (binary?)
   default: // binary store
    dataptr=(char *)malloc(datalen+2);
    if(!dataptr)
     goto err_out_data;
    if(fbfs->fread(fbds,dataptr,datalen)!=datalen)
     goto err_out_data;
    PDS_PUTB_LE16(&dataptr[datalen],0);
    break;
  }
  cd->data_type=datatype;
  cd->datalen=datalen;
  cd->datastr=dataptr;
  cd->data_number=data_number;
  if(typeptr){
   cd->typestr=typeptr;
   cd->typelen=typelen;
  }
 }else{
  if(typeptr)
   free(typeptr);
  if(datalen)
   if(fbfs->fseek(fbds,datalen,SEEK_CUR)<0)
    return MPXPLAY_ERROR_MPXINBUF_SEEK_LOW;
 }
 return datalen;

err_out_data:
 if(dataptr)
  free(dataptr);
 if(typeptr)
  free(typeptr);
 return MPXPLAY_ERROR_INFILE_NODATA;
}

static int asf_tags_to_mpxplay(struct mpxplay_infile_info_s *miis,void *fbds)
{
 asf_demuxer_data_s *asf = (asf_demuxer_data_s *)miis->private_data;
 struct asf_comment_data_s *cd;
 unsigned int i,tt,len;
 char numstr[64];

 if(!asf || !asf->comments_allocated || !asf->comment_datas)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 cd=asf->comment_datas;

 for(i=0;i<asf->comments_allocated;i++,cd++){
  char *datastr;
  if((cd->i3i_index==I3I_INVALID) || (cd->data_type>5) || (!cd->datastr && !cd->data_number))
   continue;
  tt=(cd->data_type==0)? MPXPLAY_TEXTCONV_TYPE_UTF16LE:MPXPLAY_TEXTCONV_TYPE_CHAR;
  if((cd->data_type==0) || (cd->data_type==1)){
   len=cd->datalen;
   datastr=cd->datastr;
  }else{
   len=snprintf(numstr,sizeof(numstr),"%lld",cd->data_number);
   datastr=&numstr[0];
  }
  if(datastr && len)
   miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PUT,tt,cd->i3i_index),
                       datastr,&len);
 }
 return MPXPLAY_ERROR_OK;
}

static int asf_tags_from_mpxplay(struct mpxplay_infile_info_s *miis,void *fbds)
{
 asf_demuxer_data_s *asf = (asf_demuxer_data_s *)miis->private_data;
 struct asf_comment_data_s *cd;
 unsigned int i,c,tt,len;

 if(!asf || !asf->comments_allocated || !asf->comment_datas)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;

 asf->comments_loaded=asf->comments_allocated;
 asf_comments_expand(asf,I3I_MAX+1);

 for(i=0;i<=I3I_MAX;i++){
  cd=asf->comment_datas;
  for(c=0;c<asf->comments_loaded;c++,cd++)
   if(cd->i3i_index==i)
    break;
  if(c<asf->comments_loaded){ // update tag
   if(cd->datastr){
    free(cd->datastr);
    cd->datastr=NULL;
    cd->datalen=0;
   }
   len=0;
   tt=(cd->data_type==0)? MPXPLAY_TEXTCONV_TYPE_UTF16LE:MPXPLAY_TEXTCONV_TYPE_CHAR;
   miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_GET,tt,i),
                     &cd->datastr,&len);
   if(len>0){
    if(cd->data_type<2){
     len+=(cd->data_type==0)? 2:1; // inc. leading zero
     cd->datalen=len;
    }else if(cd->data_type<6){
     cd->data_number=pds_atoi64(cd->datastr);
     free(cd->datastr);
     cd->datastr=NULL;
    } // else ???
   }else{
    if(cd->typestr)
     free(cd->typestr);
    if(cd->datastr)
     free(cd->datastr);
    cd->typelen=0;
    cd->datalen=0;
    cd->i3i_index=I3I_INVALID;
   }
  }else{ // add new tag
   len=0;
   miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_GET,MPXPLAY_TEXTCONV_TYPE_UTF16LE,i),
                     &cd->datastr,&len);
   if(len>0){
    len+=2; // inc leading zero
    cd->datalen=len;
    cd->data_type=0; // default new type is UTF16LE
    if(asf_typestrings[i]){
     cd->typelen=pds_strlen(asf_typestrings[i])*2+2; // inc. leading zero
     cd->typestr=malloc(cd->typelen+2);
     if(!cd->typestr)
      return MPXPLAY_ERROR_INFILE_MEMORY;
     miis->textconv_func(MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_CHAR,MPXPLAY_TEXTCONV_TYPE_UTF16LE),
       asf_typestrings[i],-1,cd->typestr,cd->typelen);
    }
    cd->i3i_index=i;
    cd->hdr_index=(asf_commentheader_indexes[i]>=0)? asf_commentheader_indexes[i]:(5+asf->comments_loaded);
    asf->comments_loaded++;
   }else{
    if(cd->datastr){
     free(cd->datastr);
     cd->datastr=NULL;
    }
   }
  }
 }
 return MPXPLAY_ERROR_OK;
}

//-----------------------------------------------------------------------
static int asf_header_objectfield_search(struct asf_demuxer_data_s *asf,const ASF_GUID_T *guid)
{
 unsigned int i=0;
 do{
  if(memcmp(asf->headobject_fields[i],(char *)guid,sizeof(ASF_GUID_T))==0)
   return i;
 }while(++i<asf->headobjects_loaded);
 return -1;
}

static int asf_header_objectfield_realloc(struct asf_demuxer_data_s *asf,const ASF_GUID_T *guid,unsigned long newlen)
{
 int i;
 if(newlen<24)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 i=asf_header_objectfield_search(asf,guid);
 if(i<0){
  i=asf->headobjects_loaded;
  if(i>=asf->headobjects_allocated) // should not happen
   return MPXPLAY_ERROR_INFILE_MEMORY;
  if(!(asf->headobject_fields[i]=malloc(newlen)))
   return MPXPLAY_ERROR_INFILE_MEMORY;
  asf->headobject_sizes[i]=newlen;
  asf->headobjects_loaded++;
  asf->headobjects_num++;
 }else{
  if(newlen>asf->headobject_sizes[i]){
   free(asf->headobject_fields[i]);
   if(!(asf->headobject_fields[i]=malloc(newlen))) // !!! doesn't copy the content
    return MPXPLAY_ERROR_INFILE_MEMORY;
  }
  asf->headobject_sizes[i]=newlen;
 }
 return i;
}

static int asf_objectfields_update_file_header(struct asf_demuxer_data_s *asf)
{
 int field_index,newheadsize,i;
 char *p;
 if((field_index=asf_header_objectfield_search(asf,&asf_object_file_header))<0)
  return field_index;
 newheadsize=30;
 i=0;
 do{
  newheadsize+=asf->headobject_sizes[i];
 }while(++i<asf->headobjects_loaded);
 asf->hdr.file_size+=newheadsize-asf->headobjects_size;
 p=asf->headobject_fields[field_index]+24+16;
 PDS_PUTB_LE64(p,asf->hdr.file_size);
 return MPXPLAY_ERROR_OK;
}

static int asf_objectfields_make_comment_header(struct asf_demuxer_data_s *asf)
{
 struct asf_comment_data_s *cd;
 char *p;
 unsigned int c,i;
 int field_index,totlen,lens[5];

 memset(lens,0,sizeof(lens));
 totlen=16+8+5*2;

 cd=asf->comment_datas;

 for(i=0;i<asf->comments_loaded;i++,cd++)
  if(cd->hdr_index<5)
   totlen+=(lens[cd->hdr_index]=cd->datalen);

 if(!totlen && !asf->comment_head_cursize)
  return totlen;
 if((field_index=asf_header_objectfield_realloc(asf,&asf_object_comment_header,totlen))<0)
  return field_index;
 asf->fieldindex_commentheader=field_index;
 p=asf->headobject_fields[field_index];

 pds_memcpy(p,(char *)&asf_object_comment_header,sizeof(ASF_GUID_T)); p+=sizeof(ASF_GUID_T);
 PDS_PUTB_LE64(p,totlen); p+=8;
 for(i=0;i<5;i++){
  PDS_PUTB_LE16(p,lens[i]);
  p+=2;
 }

 for(c=0;c<5;c++){
  cd=asf->comment_datas;
  for(i=0;i<asf->comments_loaded;i++,cd++){
   if((cd->hdr_index==c) && cd->datalen){
    pds_memcpy(p,cd->datastr,cd->datalen);
    p+=cd->datalen;
    break;
   }
  }
 }

 return totlen;
}

static int asf_objectfields_make_extcontent_header(struct asf_demuxer_data_s *asf)
{
 struct asf_comment_data_s *cd;
 char *p;
 unsigned int i,desc_count=0;
 int field_index,totlen=16+8+2;

 cd=asf->comment_datas;
 for(i=0;i<asf->comments_loaded;i++,cd++){
  if((cd->hdr_index>=5) && cd->typelen){
   totlen+=3*2+cd->typelen+cd->datalen;
   desc_count++;
  }
 }

 if(!totlen && !asf->extcontent_head_cursize)
  return totlen;
 if((field_index=asf_header_objectfield_realloc(asf,&asf_object_extended_content,totlen))<0)
  return field_index;
 asf->fieldindex_extcontentheader=field_index;
 p=asf->headobject_fields[field_index];

 pds_memcpy(p,(char *)asf_object_extended_content,sizeof(ASF_GUID_T)); p+=sizeof(ASF_GUID_T);
 PDS_PUTB_LE64(p,totlen); p+=8;
 PDS_PUTB_LE16(p,desc_count); p+=2;
 cd=asf->comment_datas;
 for(i=0;i<asf->comments_loaded;i++,cd++){
  if((cd->hdr_index<5) || !cd->typelen)
   continue;
  PDS_PUTB_LE16(p,cd->typelen); p+=2;
  pds_memcpy(p,cd->typestr,cd->typelen); p+=cd->typelen;
  PDS_PUTB_LE16(p,cd->data_type); p+=2;
  PDS_PUTB_LE16(p,cd->datalen); p+=2;
  switch(cd->data_type){
   case 2:
   case 3:PDS_PUTB_LE32(p,cd->data_number);p+=4;break;
   case 4:PDS_PUTB_LE64(p,cd->data_number);p+=8;break;
   case 5:PDS_PUTB_LE16(p,cd->data_number);p+=2;break;
   case 0: // unicode
   case 1: // char
   default: // binary
    if(cd->datalen){
     pds_memcpy(p,cd->datastr,cd->datalen); p+=cd->datalen;
    }
    break;
  }
 }

 return totlen;
}

static int asf_objectfields_make_padding(struct asf_demuxer_data_s *asf,long newpaddingsize)
{
 int field_index;
 char *p,*n;
 //char sout[128];

 field_index=asf_header_objectfield_search(asf,&asf_object_header_extension);
 if((field_index>=0) && (asf->headobject_sizes[field_index]>=46)){
  mpxp_uint64_t gs1=asf->headobject_sizes[field_index];
  mpxp_uint64_t newohesize=gs1+newpaddingsize-asf->paddingsize_cur;
  char *oldp=p=asf->headobject_fields[field_index];
  n=malloc(newohesize);
  if(!n)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  asf->headobject_fields[field_index]=n;
  asf->headobject_sizes[field_index]=newohesize;
  pds_memcpy(n,p,46); //=16+8+16+2+4;
  PDS_PUTB_LE64(n+16,newohesize);
  PDS_PUTB_LE32(n+42,newohesize-46);
  n+=46;
  p+=46;
  gs1-=46;
  while(gs1){
   mpxp_uint64_t gs2=PDS_GETB_LE64(p+sizeof(ASF_GUID_T));
   //sprintf(sout,"%8.8X %lld",PDS_GETB_BE32(p),gs2);
   //pds_textdisplay_printf(sout);
   if((gs2<24) || (gs2>gs1))
    break;
   if(memcmp(p,&asf_object_padding,sizeof(ASF_GUID_T))==0){
    pds_memcpy(n,(char *)&asf_object_padding,sizeof(ASF_GUID_T));
    PDS_PUTB_LE64(n+sizeof(ASF_GUID_T),newpaddingsize);
    if(newpaddingsize>24)
     pds_memset(n+24,0,newpaddingsize-24);
    n+=newpaddingsize;
   }else{
    pds_memcpy(n,p,gs2);
    n+=gs2;
   }
   p+=gs2;
   gs1-=gs2;
  }
  free(oldp);
  if(!asf->paddingsize_cur && (newpaddingsize>=24) && ((n+24)<=(asf->headobject_fields[field_index]+newohesize))){
   pds_memcpy(n,(char *)&asf_object_padding,sizeof(ASF_GUID_T));
   PDS_PUTB_LE64(n+sizeof(ASF_GUID_T),newpaddingsize);
   if(newpaddingsize>24)
    pds_memset(n+24,0,newpaddingsize-24);
  }
 }else{
  if((field_index=asf_header_objectfield_realloc(asf,&asf_object_padding,newpaddingsize))<0)
   return field_index;
  p=asf->headobject_fields[field_index];
  pds_memcpy(p,(char *)&asf_object_padding,sizeof(ASF_GUID_T));
  PDS_PUTB_LE64(p+sizeof(ASF_GUID_T),newpaddingsize);
  if(newpaddingsize>24)
   pds_memset(p+24,0,newpaddingsize-24);
 }
 return MPXPLAY_ERROR_OK;
}

static int asf_objectfields_read_paddingsize(struct asf_demuxer_data_s *asf)
{
 int field_index=asf_header_objectfield_search(asf,&asf_object_padding);
 //char sout[100];
 if(field_index>=0){
  asf->paddingsize_cur=asf->headobject_sizes[field_index];
 }else{
  field_index=asf_header_objectfield_search(asf,&asf_object_header_extension);
  if((field_index>=0) && (asf->headobject_sizes[field_index]>46)){
   mpxp_uint64_t gs1=asf->headobject_sizes[field_index]-46;
   char *p=asf->headobject_fields[field_index]+46;
   while(gs1){
    ASF_GUID_T *g=(ASF_GUID_T *)p;
    mpxp_uint64_t gs2=PDS_GETB_LE64(p+sizeof(ASF_GUID_T));
    //sprintf(sout,"g:%8.8X s:%lld",PDS_GETB_BE32(g),gs2);
    //pds_textdisplay_printf(sout);
    if((gs2<24) || (gs2>gs1))
     break;
    if(!memcmp(g, &asf_object_padding, sizeof(ASF_GUID_T))){
     asf->paddingsize_cur=gs2;
     break;
    }
    p+=gs2;
    gs1-=gs2;
   }
  }
 }
 return field_index;
}

static int asf_header_objectfields_read(struct asf_demuxer_data_s *asf,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int i,retcode=MPXPLAY_ERROR_INFILE_CANTOPEN;
 mpxp_uint64_t gsize;
 ASF_GUID_T g;

 if(fbfs->fseek(fbds,0,SEEK_SET)!=0)
  return retcode;
 ASF_READ_GUID(fbfs,fbds, &g);
 if(memcmp(&g, &asf_object_main_header, sizeof(ASF_GUID_T)))
  return retcode;
 asf->headobjects_size=fbfs->get_le64(fbds);
 asf->headobjects_num=fbfs->get_le32(fbds);
 asf->headobjects_reserved=fbfs->get_le16(fbds);

 asf->headobjects_allocated=asf->headobjects_num+5;
 asf->headobject_sizes=(unsigned long *)calloc(asf->headobjects_allocated,sizeof(unsigned long));
 asf->headobject_fields=(char **)calloc(asf->headobjects_allocated,sizeof(char *));
 if(!asf->headobject_sizes || !asf->headobject_fields)
  return MPXPLAY_ERROR_INFILE_MEMORY;
 asf->headobjects_loaded=0;
 i=0;
 do{
  char *p;
  ASF_READ_GUID(fbfs,fbds, &g);
  gsize = fbfs->get_le64(fbds);
  if(gsize<24)
   break;
  if(!memcmp(&g, &asf_object_data_header, sizeof(ASF_GUID_T))){
   asf->data_head_offset=fbfs->ftell(fbds)-24;
   retcode=MPXPLAY_ERROR_OK;
   break;
  }
  p=(char *)malloc(gsize+32);
  if(!p)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  asf->headobject_fields[i]=p;
  asf->headobject_sizes[i]=gsize;
  pds_memcpy(p,&g,sizeof(ASF_GUID_T)); p+=sizeof(ASF_GUID_T);
  PDS_PUTB_LE64(p,gsize); p+=sizeof(mpxp_uint64_t);
  gsize-=(sizeof(ASF_GUID_T)+sizeof(mpxp_uint64_t));
  if(fbfs->fread(fbds,p,(unsigned long)gsize)!=gsize)
   break;
 }while(++i<asf->headobjects_allocated);
 asf->headobjects_loaded=i;
 return retcode;
}

static int asf_header_objectfields_write(struct asf_demuxer_data_s *asf,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds)
{
 int i,retcode;
 if((retcode=fbfs->fseek(fbds,0,SEEK_SET))!=0)
  goto err_out_write;
 if((retcode=fbfs->fwrite(fbds,(char*)&asf_object_main_header,sizeof(ASF_GUID_T)))!=sizeof(ASF_GUID_T))
  goto err_out_write;
 asf->headobjects_size=30;
 i=0;
 do{
  asf->headobjects_size+=asf->headobject_sizes[i];
 }while(++i<asf->headobjects_loaded);
 if((retcode=fbfs->put_le64(fbds,asf->headobjects_size))!=8)
  goto err_out_write;
 if((retcode=fbfs->put_le32(fbds,asf->headobjects_loaded))!=4)
  goto err_out_write;
 if((retcode=fbfs->put_le16(fbds,asf->headobjects_reserved))!=2)
  goto err_out_write;
 i=0;
 do{
  if((retcode=fbfs->fwrite(fbds,asf->headobject_fields[i],asf->headobject_sizes[i]))!=asf->headobject_sizes[i])
   goto err_out_write;
 }while(++i<asf->headobjects_loaded);
 return MPXPLAY_ERROR_OK;
err_out_write:
 if(retcode>=0)
  retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
 return retcode;
}

//-------------------------------------------------------------------------
static int ASF_infile_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 return asf_tags_to_mpxplay(miis,fbds);
}

static int ASF_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 asf_demuxer_data_s *asf=NULL;
 void *tmpfile_filehand=NULL;
 int retcode=MPXPLAY_ERROR_INFILE_CANTOPEN;
 int comment_head_newsize,extcontent_head_newsize;
 long comments_size_change;
 mpxp_filesize_t len;
 //char sout[128];

 if(!fbfs->fopen(fbds,filename,O_RDWR|O_BINARY,0))
  goto err_out_atp;

 miis->filesize=fbfs->filelength(fbds);
 if(miis->filesize<16)
  goto err_out_atp;

 asf=(struct asf_demuxer_data_s *)calloc(1,sizeof(struct asf_demuxer_data_s));
 if(!asf){
  retcode=MPXPLAY_ERROR_INFILE_MEMORY;
  goto err_out_atp;
 }
 miis->private_data=asf;

 if(!asf_read_header(asf,fbfs,fbds,(MPXPLAY_INFILE_OPENMODE_INFO_ID3|MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD)))
  goto err_out_atp;
 if((retcode=asf_header_objectfields_read(asf,fbfs,fbds))!=MPXPLAY_ERROR_OK)
  goto err_out_atp;
 if((retcode=asf_tags_from_mpxplay(miis,fbds))!=MPXPLAY_ERROR_OK)
  goto err_out_atp;

 comment_head_newsize=asf_objectfields_make_comment_header(asf);
 extcontent_head_newsize=asf_objectfields_make_extcontent_header(asf);
 if((comment_head_newsize<0) || (extcontent_head_newsize<0))
  goto err_out_atp;

 /*sprintf(sout,"hc:%d hn:%d ec:%d en:%d ha:%d hl:%d",asf->comment_head_cursize,comment_head_newsize,
   asf->extcontent_head_cursize,extcontent_head_newsize,
   asf->headobjects_allocated,asf->headobjects_loaded);
 display_message(0,0,sout);
 //getch();*/

 if((comment_head_newsize==asf->comment_head_cursize) && (extcontent_head_newsize==asf->extcontent_head_cursize)){
  if(comment_head_newsize){
   if((retcode=fbfs->fseek(fbds,asf->comment_head_offset,SEEK_SET))!=asf->comment_head_offset)
    goto err_out_write;
   if((retcode=fbfs->fwrite(fbds,asf->headobject_fields[asf->fieldindex_commentheader],asf->headobject_sizes[asf->fieldindex_commentheader]))!=asf->headobject_sizes[asf->fieldindex_commentheader])
    goto err_out_write;
  }
  if(extcontent_head_newsize){
   if((retcode=fbfs->fseek(fbds,asf->extcontent_head_offset,SEEK_SET))!=asf->extcontent_head_offset)
    goto err_out_write;
   if((retcode=fbfs->fwrite(fbds,asf->headobject_fields[asf->fieldindex_extcontentheader],asf->headobject_sizes[asf->fieldindex_extcontentheader]))!=asf->headobject_sizes[asf->fieldindex_extcontentheader])
    goto err_out_write;
  }
 }else{
  comments_size_change=comment_head_newsize-asf->comment_head_cursize
                     +extcontent_head_newsize-asf->extcontent_head_cursize;
  asf_objectfields_read_paddingsize(asf);
  /*sprintf(sout,"c:%d p:%d",comments_size_change,asf->paddingsize_cur);
  display_message(1,0,sout);
  getch();*/
  if(comments_size_change<=(asf->paddingsize_cur-24)){
   if((retcode=asf_objectfields_make_padding(asf,asf->paddingsize_cur-comments_size_change))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   if((retcode=asf_header_objectfields_write(asf,fbfs,fbds))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
  }else{
   if(!(writetag_control&MPXPLAY_WRITETAG_CNTRL_DUPFILE)){
    retcode=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPACE;
    goto err_out_atp;
   }
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPOPEN,&tmpfile_filehand,NULL))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   if((retcode=asf_objectfields_make_padding(asf,1024))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   if((retcode=asf_objectfields_update_file_header(asf))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   if((retcode=asf_header_objectfields_write(asf,fbfs,tmpfile_filehand))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   if((retcode=fbfs->fseek(fbds,asf->data_head_offset,SEEK_SET))!=asf->data_head_offset)
    goto err_out_write;
   len=-1;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
    goto err_out_atp;
   retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPXCHCLOSE,&tmpfile_filehand,NULL);
  }
 }
 retcode=MPXPLAY_ERROR_OK;
 goto err_out_atp;

err_out_write:
 if(retcode>=0)
  retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
err_out_atp:
 if(tmpfile_filehand)
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_CLOSE,&tmpfile_filehand,NULL);
 if(asf->headobject_sizes)
  free(asf->headobject_sizes);
 if(asf->headobject_fields)
  free(asf->headobject_fields);
 ASF_infile_close(fbfs,fbds,miis);
 miis->private_data=NULL;
 return retcode;
}

#if defined(DEBUG_FILEINFO) || defined(DEBUG_HEADER) || defined(DEBUG_DECODE)
static void asf_debugf(const char *format, ...)
{
 va_list ap;
 char sout[500];

 va_start(ap,format);
 vsprintf(sout, format, ap );
 va_end(ap);

 //pds_textdisplay_printf(sout);
 fprintf(stdout,"%s\n",sout);
}
#endif

struct mpxplay_infile_func_s IN_ASF_funcs={
 0,
 NULL,
 NULL,
 &ASF_infile_open,
 &ASF_infile_close,
 &ASF_infile_decode,
 &ASF_infile_fseek,
 &ASF_infile_clearbuff,
 &ASF_infile_tag_get,
 &ASF_infile_tag_put,
 NULL,
 {"ASF","WMA","WMV","ASX",NULL}
};

#endif // MPXPLAY_LINK_INFILE_ASF
