//**************************************************************************
//*                     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: MP4 demuxing
//based on the mp4ff and ffmpeg libraries (but the 70-80% of the functions are new)

//#define MPXPLAY_USE_DEBUGF 1
//#define MPXPLAY_USE_DEBUGMSG
#define MPXPLAY_DEBUG_OUTPUT stdout

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_MP4

//#define MP4LIB_ATOM_DEBUG 1

#if defined(MP4LIB_ATOM_DEBUG) || defined(MPXPLAY_USE_DEBUGF)
#include <inttypes.h>
#endif

#define mp4lib_read_byte(f)  f->fbfs->get_byte(f->fbds)
#define mp4lib_read_int16(f) f->fbfs->get_be16(f->fbds)
#define mp4lib_read_int24(f) f->fbfs->get_be24(f->fbds)
#define mp4lib_read_int32(f) f->fbfs->get_be32(f->fbds)
#define mp4lib_read_int64(f) f->fbfs->get_be64(f->fbds)
#define mp4lib_read_data(f,data,size) f->fbfs->fread(f->fbds,data,size)

#define mp4lib_write_int32(f,num) f->fbfs->put_be32(f->fbds,num)
#define mp4lib_write_data(f,data,size) f->fbfs->fwrite(f->fbds,data,size)

#define MP4LIB_ATOMPATH_MAXLEVELS  16
#define MP4LIB_ATOMPATH_INITPATHES 32
#define MP4LIB_ATOMPATH_MAXPATHES  1024
#define MP4LIB_ATOMPATHINDEX_META 0
#define MP4LIB_ATOMPATHINDEX_UDTA 1
#define MP4LIB_ATOMPATHINDEX_MOOV 2
#define MP4LIB_ATOMPATHINDEX_NUM 3
#define MP4LIB_ATOMPATHINDEX_STCO 3 // for every tracks from here (3,4,5,6,...)

// track types
#define MP4LIB_MAX_TRACKS 1024
#define TRACK_UNKNOWN 0
#define TRACK_AUDIO   1
#define TRACK_VIDEO   2
#define TRACK_SYSTEM  3

// MP4 Audio track types
#define MP4_INVALID_AUDIO_TYPE		   0x00
#define MP4_MPEG1_AUDIO_TYPE		   0x6B
#define MP4_MPEG2_AUDIO_TYPE		   0x69
#define MP4_MP3_AUDIO_TYPE		   MP4_MPEG2_AUDIO_TYPE
#define MP4_MPEG2_AAC_MAIN_AUDIO_TYPE	   0x66
#define MP4_MPEG2_AAC_LC_AUDIO_TYPE	   0x67
#define MP4_MPEG2_AAC_SSR_AUDIO_TYPE	   0x68
#define MP4_MPEG2_AAC_AUDIO_TYPE	   MP4_MPEG2_AAC_MAIN_AUDIO_TYPE
#define MP4_MPEG4_AUDIO_TYPE		   0x40
#define MP4_PRIVATE_AUDIO_TYPE		   0xC0
#define MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE 0xE0	/* a private definition */
#define MP4_VORBIS_AUDIO_TYPE		   0xE1	/* a private definition */
#define MP4_AC3_AUDIO_TYPE		   0xE2	/* a private definition */
#define MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE    0xE6 /* a private definition */
#define MP4_ALAC_AUDIO_TYPE                0xFE

#define MP4_IS_MPEG2_AAC_AUDIO_TYPE(type) \
	(((type) >= MP4_MPEG2_AAC_MAIN_AUDIO_TYPE \
		&& (type) <= MP4_MPEG2_AAC_SSR_AUDIO_TYPE))

#define MP4_IS_AAC_AUDIO_TYPE(type) \
	(MP4_IS_MPEG2_AAC_AUDIO_TYPE(type) \
	|| (type) == MP4_MPEG4_AUDIO_TYPE)

#define MP4_IS_MP3_AUDIO_TYPE(type) \
	((type) == MP4_MPEG1_AUDIO_TYPE || (type) == MP4_MPEG2_AUDIO_TYPE)

//--------- atoms ------------------------------------
#define ATOM_MOOV 1
#define ATOM_TRAK 2
#define ATOM_EDTS 3
#define ATOM_MDIA 4
#define ATOM_MINF 5
#define ATOM_STBL 6
#define ATOM_UDTA 7
#define SUBATOMIC 127 // if less

#define ATOM_FTYP 128
#define ATOM_WIDE 129
#define ATOM_MDAT 130
#define ATOM_MVHD 131
#define ATOM_TKHD 132
#define ATOM_TREF 133
#define ATOM_MDHD 134
#define ATOM_VMHD 135
#define ATOM_SMHD 136
#define ATOM_HMHD 137
#define ATOM_STSD 138
#define ATOM_STTS 139
#define ATOM_STSZ 140
#define ATOM_STZ2 141
#define ATOM_STCO 142
#define ATOM_CO64 143 // 64-bit version of STCO atom
#define ATOM_STSC 144
#define ATOM_MP4A 145
#define ATOM_MP4V 146
#define ATOM_MP4S 147
#define ATOM_ALAC 148
#define ATOM_ESDS 149
#define ATOM_CTTS 150
#define ATOM_FRMA 151
#define ATOM_IVIV 152
#define ATOM_PRIV 153
#define ATOM_USER 154
#define ATOM_KEY  155

#define ATOM_META 166 // iTunes Metadata box
#define ATOM_ILST 167 // iTunes metadata/tag list
#define ATOM_NAME 168 // iTunes Metadata name box
#define ATOM_DATA 169 // iTunes Metadata data box
#define ATOM_TITLE  170
#define ATOM_ARTIST 171
#define ATOM_WRITER 172
#define ATOM_ALBUM  173
#define ATOM_DATE   174
#define ATOM_TOOL   175
#define ATOM_COMMENT 176
#define ATOM_GENRE1 177
#define ATOM_TRACK  178
#define ATOM_DISC   179
#define ATOM_COMPILATION 180
#define ATOM_GENRE2 181
#define ATOM_TEMPO  182
#define ATOM_COVER  183
#define ATOM_DRMS   184
#define ATOM_SINF   185
#define ATOM_SCHI   186

#define ATOM_FREE 253
#define ATOM_SKIP 254

#define ATOM_INVALID 0
#define ATOM_UNKNOWN 255

// metadata tag structure
typedef struct
{
 unsigned int atom_num;
 mpxp_uint32_t atom_name;
 char *name;
 char *data;
 mpxp_uint32_t data_size;
 mpxp_uint32_t data_flags;
}mp4lib_onetag_info_t;

// metadata list structure
typedef struct
{
 mp4lib_onetag_info_t *tags;
 mpxp_uint32_t count;
 mpxp_uint32_t storage;
}mp4lib_ilst_info_t;

#pragma pack(push,1)
typedef struct
{
 mpxp_uint32_t count;
 mpxp_uint32_t delta;
}mp4lib_stts_sample_info_t;

typedef struct
{
 mpxp_int32_t first_chunk;
 mpxp_int32_t samples_per_chunk;
 mpxp_int32_t sample_desc_index;
}mp4lib_stsc_info_t;
#pragma pack(pop)

typedef struct
{
 mpxp_uint32_t atom_name;
 mpxp_filesize_t atom_filepos;
 mpxp_uint64_t atom_oldsize;
}mp4lib_atomlevel_info_t;

typedef struct
{
 unsigned int atomlevels;
 mp4lib_atomlevel_info_t *atompath;
}mp4lib_atompath_info_t;

typedef struct
{
 mpxp_uint32_t atom_name;
 mpxp_uint32_t atom_size; // without header
 mpxp_uint8_t *atom_data; // without header
}mp4lib_oneatom_info_t;

typedef struct
{
 unsigned int atoms_loaded;
 unsigned int atoms_allocated;
 mpxp_uint32_t atoms_totalsize;
 mp4lib_oneatom_info_t *atom_datas;
}mp4lib_atomlist_info_t;

typedef struct
{
 mpxp_int32_t  streamtype;
 mpxp_int32_t  audio_type;
 mpxp_uint32_t audio_wave_id;
 mpxp_int32_t  audio_channels;
 mpxp_int32_t  audio_bits;
 mpxp_uint32_t audio_freq;
 mpxp_uint32_t samples_per_frame;
 mpxp_uint32_t bytes_per_frame;

 mpxp_uint32_t stsd_entry_count;

 mpxp_uint32_t stsz_max_sample_size; // stsz_sample_size or max in stsz_table[]
 mpxp_uint32_t stsz_sample_size;
 mpxp_uint32_t stsz_sample_count;
 mpxp_uint32_t *stsz_table;

 mpxp_uint32_t stts_entry_count;
 mp4lib_stts_sample_info_t *stts_sample_infos;

 mpxp_int32_t stsc_entry_count;
 mp4lib_stsc_info_t *stsc_infos;

 mpxp_int32_t stco_entry_count;
 mpxp_filesize_t *stco_chunk_offset;

 //mpxp_int32_t ctts_entry_count;
 //mpxp_int32_t *ctts_sample_count;
 //mpxp_int32_t *ctts_sample_offset;

 mpxp_uint8_t *decoderConfig;
 mpxp_int32_t decoderConfigLen;
 mpxp_uint32_t maxBitrate;
 mpxp_uint32_t avgBitrate;
 mpxp_uint32_t timeScale;
 mpxp_uint64_t duration;

}mp4lib_track_info_t;

// mp4 main file structure
typedef struct
{
 struct mpxplay_filehand_buffered_func_s *fbfs;
 void *fbds;
 mpxp_uint32_t openmode;

 unsigned int atomlevel_current;
 mp4lib_atomlevel_info_t *atompath_current;
 unsigned int atompathes_allocated;
 mp4lib_atompath_info_t *atompath_infos;

 unsigned int new_atomfield_size;
 mpxp_uint8_t *new_atomfield_data;

 mp4lib_atomlist_info_t meta_subatoms_list;

 mp4lib_ilst_info_t ilst_infos;

 mpxp_int32_t total_tracks;
 mp4lib_track_info_t *lasttrack;
 mp4lib_track_info_t *track[MP4LIB_MAX_TRACKS];
}mp4lib_main_info_t;

static void mp4lib_tagging_close(mp4lib_ilst_info_t *tags);
static mpxp_int32_t mp4lib_tagging_read_meta_atom(mp4lib_main_info_t *f, const mpxp_uint64_t size);

//-------------------------------------------------------------------------
//atom handling

typedef struct atom_description_s{
 mpxp_uint8_t atom_id;
 char atomname[4];
}atom_description_s;

static struct atom_description_s atom_descriptions[]=
{
 {ATOM_MOOV,"moov"},{ATOM_TRAK,"trak"},{ATOM_EDTS,"edts"},{ATOM_MDIA,"mdia"},
 {ATOM_MINF,"minf"},{ATOM_STBL,"stbl"},{ATOM_UDTA,"udta"},{ATOM_ILST,"ilst"},
 {ATOM_TITLE,"©nam"},{ATOM_ARTIST,"©ART"},{ATOM_WRITER,"©wrt"},
 {ATOM_ALBUM,"©alb"},{ATOM_DATE,"©day"},{ATOM_TOOL,"©too"},{ATOM_COMMENT,"©cmt"},
 {ATOM_GENRE1,"©gen"},{ATOM_TRACK,"trkn"},{ATOM_DISC,"disk"},
 {ATOM_COMPILATION,"cpil"},{ATOM_GENRE2,"gnre"},{ATOM_TEMPO,"tmpo"},
 {ATOM_COVER,"covr"},{ATOM_DRMS,"drms"},{ATOM_SINF,"sinf"},{ATOM_SCHI,"schi"},

 {ATOM_FTYP,"ftyp"},{ATOM_MDAT,"mdat"},{ATOM_MVHD,"mvhd"},{ATOM_TKHD,"tkhd"},
 {ATOM_TREF,"tref"},{ATOM_MDHD,"mdhd"},{ATOM_VMHD,"vmhd"},{ATOM_SMHD,"smhd"},
 {ATOM_HMHD,"hmhd"},{ATOM_STSD,"stsd"},{ATOM_STTS,"stts"},{ATOM_STSZ,"stsz"},
 {ATOM_STZ2,"stz2"},{ATOM_STCO,"stco"},{ATOM_CO64,"co64"},{ATOM_STSC,"stsc"},
 {ATOM_MP4A,"mp4a"},{ATOM_MP4V,"mp4v"},{ATOM_MP4S,"mp4s"},{ATOM_ESDS,"esds"},
 {ATOM_META,"meta"},{ATOM_NAME,"name"},{ATOM_DATA,"data"},{ATOM_CTTS,"ctts"},
 {ATOM_FRMA,"frma"},{ATOM_IVIV,"iviv"},{ATOM_PRIV,"priv"},{ATOM_USER,"user"},
 {ATOM_KEY ,"key "},{ATOM_WIDE,"wide"},{ATOM_ALAC,"alac"},{ATOM_FREE,"free"},
 {ATOM_SKIP,"skip"},{ATOM_UNKNOWN,""}
};

static mpxp_uint8_t mp4lib_atom_name_to_type(char *cname)
{
 struct atom_description_s *a=&atom_descriptions[0];
 const mpxp_uint32_t iname=(*(mpxp_uint32_t *)cname);
 do{
  if((*(mpxp_uint32_t *)&a->atomname[0])==iname)
   break;
  a++;
 }while(a->atom_id<ATOM_UNKNOWN);
 /*if(a->atom_id==ATOM_UNKNOWN){
  unsigned int i=4;
  do{
   if((cname[0]<'a') || (cname[0]>'z'))
    return ATOM_INVALID;
   cname++;
  }while(--i);
 }*/
 return a->atom_id;
}

static mpxp_uint32_t mp4lib_atom_type_to_name(unsigned int atom_num)
{
 struct atom_description_s *a=&atom_descriptions[0];
 do{
  if(a->atom_id==atom_num)
   break;
  a++;
 }while(a->atom_id<ATOM_UNKNOWN);
 return PDS_GETB_LE32(&a->atomname[0]);
}

// read atom header, return atom size, atom size is with header included
static mpxp_uint64_t mp4lib_atom_read_header(mp4lib_main_info_t *f, mpxp_uint8_t *atom_type, mpxp_uint8_t *header_size, mpxp_uint32_t *atom_name)
{
 mpxp_uint32_t size32,name;
 mpxp_uint64_t size64;

 size32=mp4lib_read_int32(f);
 if(!size32)
  goto err_out_arh;

 name=f->fbfs->get_le32(f->fbds);
 if(!name)
  goto err_out_arh;

 if(atom_name)
  *atom_name=name;

 if(size32==1){ // check for 64 bit atom size
  *header_size=16;
  size64=mp4lib_read_int64(f);
 }else{
  *header_size=8;
  size64=size32;
 }

 *atom_type = mp4lib_atom_name_to_type((char *)&name);

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"a:%4.4s hs:%d s:%"PRIi64" pos:%d %X\n",(char *)&name,(int)(*header_size),size64,(long)f->fbfs->ftell(f->fbds)-8,(long)f->fbfs->ftell(f->fbds)-8);
#endif
 return size64;

err_out_arh:
 *atom_type=ATOM_INVALID;
 *header_size=0;
 return (mpxp_uint64_t)size32;
}

static mpxp_uint32_t mp4lib_read_mp4_descr_length(mp4lib_main_info_t *f)
{
 mpxp_uint32_t length = 0;
 mpxp_uint8_t b,numBytes = 0;

 do{
  b = mp4lib_read_byte(f);
  length = (length<<7) | (b&0x7F);
 }while((b&0x80) && (++numBytes<4));

 return length;
}

static mpxp_int32_t mp4lib_read_mdhd(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t * p_track = f->lasttrack;
 mpxp_uint32_t version;

 version = mp4lib_read_int32(f);
 if(version==1){
  mp4lib_read_int64(f);//creation-time
  mp4lib_read_int64(f);//modification-time
  p_track->timeScale = mp4lib_read_int32(f);
  p_track->duration  = mp4lib_read_int64(f);
 }else{ //version == 0
  mpxp_uint32_t temp;

  mp4lib_read_int32(f);//creation-time
  mp4lib_read_int32(f);//modification-time

  p_track->timeScale = mp4lib_read_int32(f);
  temp = mp4lib_read_int32(f);
  p_track->duration = (temp == (mpxp_uint32_t)(-1)) ? (mpxp_uint64_t)(-1) : (mpxp_uint64_t)(temp);
 }

 mp4lib_read_int16(f);
 mp4lib_read_int16(f);
 return 1;
}

static mpxp_int32_t mp4lib_read_esds(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 mpxp_uint8_t tag;
 mpxp_uint32_t temp;

 mp4lib_read_byte(f);  // version
 mp4lib_read_int24(f); // flags

 // get and verify ES_DescrTag
 tag = mp4lib_read_byte(f);
 if(tag==0x03){
  if(mp4lib_read_mp4_descr_length(f) < (5 + 15)) // ???
   return 1;
  mp4lib_read_int24(f); // skip 3 bytes
 }else
  mp4lib_read_int16(f); // skip 2 bytes

 // get and verify DecoderConfigDescrTab
 if(mp4lib_read_byte(f) != 0x04)
  return 1;

 temp = mp4lib_read_mp4_descr_length(f);
 if(temp<13)
  return 1;

 p_track->audio_type  = mp4lib_read_byte(f);
 mp4lib_read_int32(f);//0x15000414 ????
 p_track->maxBitrate = mp4lib_read_int32(f);
 p_track->avgBitrate = mp4lib_read_int32(f);

 // get and verify DecSpecificInfoTag
 if(mp4lib_read_byte(f) != 0x05)
  return 1;

 p_track->decoderConfigLen = mp4lib_read_mp4_descr_length(f);

 if(p_track->decoderConfig)
  free(p_track->decoderConfig);
 p_track->decoderConfig = malloc(p_track->decoderConfigLen);
 if(p_track->decoderConfig)
  mp4lib_read_data(f, (mpxp_int8_t *)p_track->decoderConfig, p_track->decoderConfigLen);
 else
  p_track->decoderConfigLen = 0;

 return 0;
}

static mpxp_int32_t mp4lib_read_mp4a(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 mpxp_uint32_t version;
 mpxp_uint8_t atom_type,header_size;

 mp4lib_read_int16(f); // reserved
 mp4lib_read_int32(f); // reserved

 mp4lib_read_int16(f); // data_reference_index
 version=mp4lib_read_int16(f);
 mp4lib_read_int16(f); // revision
 mp4lib_read_int32(f); // vendor_id

 p_track->audio_channels = mp4lib_read_int16(f);
 p_track->audio_bits   = mp4lib_read_int16(f);
 mp4lib_read_int16(f); // compression id
 p_track->audio_freq   = mp4lib_read_int32(f); // ???  2 bytes packet_size==0 + 2 bytes sample_rate
 mp4lib_read_int16(f); // unknown

 if(version)
  f->fbfs->fseek(f->fbds,((version==1)? 48:68),SEEK_CUR);

 if(mp4lib_atom_read_header(f, &atom_type, &header_size, NULL)>0)
  if(atom_type == ATOM_ESDS)
   mp4lib_read_esds(f);

 return 0;
}

static mpxp_uint32_t mp4lib_read_alac(mp4lib_main_info_t *f,mpxp_int32_t size)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 mpxp_uint16_t version,i;

 size-=8;

 for (i = 0; i < 6; i++)
  mp4lib_read_byte(f); // reserved
 size-=6;

 version=mp4lib_read_int16(f); size-=2; // version have to be 1
 mp4lib_read_int16(f); // revision level
 mp4lib_read_int32(f); // vendor
 mp4lib_read_int16(f); // something
 size-=8;

 p_track->audio_channels = mp4lib_read_int16(f); size-=2;
 p_track->audio_bits = mp4lib_read_int16(f); size-=2;
 mp4lib_read_int16(f); // compression id
 mp4lib_read_int16(f); // packet_size
 size-=4;
 p_track->audio_freq = mp4lib_read_int16(f); size-=2;
 mp4lib_read_int16(f); size-=2;

 p_track->decoderConfigLen = size+12+8;
 if(p_track->decoderConfig)
  free(p_track->decoderConfig);
 p_track->decoderConfig = malloc(p_track->decoderConfigLen);
 if(p_track->decoderConfig){
  ((mpxp_uint32_t *)p_track->decoderConfig)[0]=0x0c000000;
  pds_memcpy(p_track->decoderConfig+4,"frma",4);
  pds_memcpy(p_track->decoderConfig+8,"alac",4);
  mp4lib_read_data(f,(mpxp_int8_t *)p_track->decoderConfig+12,size);
  pds_memset(p_track->decoderConfig+12+size,0,8);
 }else
  p_track->decoderConfigLen = 0;

 p_track->audio_type=MP4_ALAC_AUDIO_TYPE;

 return 0;
}

static double ff_int2dbl(mpxp_int64_t v)
{
 if(v+v > 0xFFEULL<<52)
  return 0.0;
 return ldexp(((v&((1LL<<52)-1)) + (1LL<<52)) * (v>>63|1), (v>>52&0x7FF)-1075);
}

static struct audio_atom_s{
 mpxp_uint32_t atom_name;
 mpxp_uint32_t wave_id;
}audio_atoms[]={
 { PDS_GET4C_LE32('i', 'n', '3', '2'),MPXPLAY_WAVEID_PCM_SBE },
 //{ PDS_GET4C_LE32('i', 'n', '3', '2'),MPXPLAY_WAVEID_PCM_SLE },
 { PDS_GET4C_LE32('i', 'n', '2', '4'),MPXPLAY_WAVEID_PCM_SBE },
 //{ PDS_GET4C_LE32('i', 'n', '2', '4'),MPXPLAY_WAVEID_PCM_SLE },
 { PDS_GET4C_LE32('t', 'w', 'o', 's'),MPXPLAY_WAVEID_PCM_SBE },
 { PDS_GET4C_LE32('s', 'o', 'w', 't'),MPXPLAY_WAVEID_PCM_SLE },
 { PDS_GET4C_LE32('f', 'l', '3', '2'),MPXPLAY_WAVEID_PCM_F32BE },
 //{ PDS_GET4C_LE32('f', 'l', '3', '2'),MPXPLAY_WAVEID_PCM_FLOAT },
 { PDS_GET4C_LE32('f', 'l', '6', '4'),MPXPLAY_WAVEID_PCM_F64BE },
 //{ PDS_GET4C_LE32('f', 'l', '6', '4'),MPXPLAY_WAVEID_PCM_F64LE },
 { PDS_GET4C_LE32('r', 'a', 'w', ' '),MPXPLAY_WAVEID_PCM_ULE },
 { PDS_GET4C_LE32('N', 'O', 'N', 'E'),MPXPLAY_WAVEID_PCM_ULE },

 { PDS_GET4C_LE32('.', 'm', 'p', '2'),MPXPLAY_WAVEID_MP2 },
 { PDS_GET4C_LE32('.', 'm', 'p', '3'),MPXPLAY_WAVEID_MP3 },
 { 0x6D730055                        ,MPXPLAY_WAVEID_MP3 },
 { PDS_GET4C_LE32('a', 'c', '-', '3'),MPXPLAY_WAVEID_AC3 },
 { PDS_GET4C_LE32('s', 'a', 'c', '3'),MPXPLAY_WAVEID_AC3 },
 { PDS_GET4C_LE32('W', 'M', 'A', '2'),MPXPLAY_WAVEID_WMAV2 },

 { PDS_GET4C_LE32('a', 'l', 'a', 'w'),PDS_GET4C_LE32('A', 'L', 'A', 'W') },
 { PDS_GET4C_LE32('u', 'l', 'a', 'w'),PDS_GET4C_LE32('U', 'L', 'A', 'W') },
 { PDS_GET4C_LE32('i', 'm', 'a', '4'),PDS_GET4C_LE32('I', 'M', 'A', '4') },
 { PDS_GET4C_LE32('M', 'A', 'C', '3'),PDS_GET4C_LE32('M', 'A', 'C', '3') },
 { PDS_GET4C_LE32('M', 'A', 'C', '6'),PDS_GET4C_LE32('M', 'A', 'C', '6') },
 { PDS_GET4C_LE32('.', 'm', 'p', '1'),PDS_GET4C_LE32('M', 'P', '1', ' ') },
 { PDS_GET4C_LE32('s', 'a', 'm', 'r'),PDS_GET4C_LE32('A', 'M', 'R', 'N') },
 { PDS_GET4C_LE32('s', 'a', 'w', 'b'),PDS_GET4C_LE32('A', 'M', 'R', 'W') },
 { PDS_GET4C_LE32('a', 'g', 's', 'm'),PDS_GET4C_LE32('A', 'G', 'S', 'M') },
 { PDS_GET4C_LE32('Q', 'c', 'l', 'p'),PDS_GET4C_LE32('Q', 'C', 'L', 'P') },
 { PDS_GET4C_LE32('Q', 'c', 'l', 'q'),PDS_GET4C_LE32('Q', 'C', 'L', 'Q') },
 { PDS_GET4C_LE32('s', 'q', 'c', 'p'),PDS_GET4C_LE32('S', 'Q', 'C', 'P') },
 { PDS_GET4C_LE32('Q', 'D', 'M', 'C'),PDS_GET4C_LE32('Q', 'D', 'M', 'C') },
 { PDS_GET4C_LE32('Q', 'D', 'M', '2'),PDS_GET4C_LE32('Q', 'D', 'M', '2') },
 { PDS_GET4C_LE32('v', 'd', 'v', 'a'),PDS_GET4C_LE32('V', 'D', 'V', 'A') },
 { PDS_GET4C_LE32('d', 'v', 'c', 'a'),PDS_GET4C_LE32('D', 'V', 'C', 'A') },
 { 0, 0 }
};

static mpxp_int32_t mp4lib_read_audio(mp4lib_main_info_t *f, mpxp_uint32_t atom_name, mpxp_uint32_t size)
{
 mpxp_uint32_t version,flags;
 mp4lib_track_info_t *p_track = f->lasttrack;
 struct audio_atom_s *ats=&audio_atoms[0];

 if(size<28)
  return -1;

 do{
  if(atom_name==ats->atom_name){
   p_track->audio_wave_id=ats->wave_id;
   break;
  }
  ats++;
 }while(ats->atom_name);

 if(!p_track->audio_wave_id)
  if((atom_name&0xFFFF)==((mpxp_uint32_t)'m'+((mpxp_uint32_t)'s'<<8)) || (atom_name&0xFFFF) == ((mpxp_uint32_t)'T'+((mpxp_uint32_t)'S'<<8)))
   p_track->audio_wave_id=((atom_name>>8)&0xff00)|(atom_name>>24);

 if(!p_track->audio_wave_id){
  p_track->streamtype=TRACK_UNKNOWN;
  return -1;
 }

 p_track->streamtype=TRACK_AUDIO;

 mp4lib_read_int32(f); // reserved
 mp4lib_read_int16(f); // reserved
 mp4lib_read_int16(f); // data_reference_index

 version=mp4lib_read_int16(f);
 mp4lib_read_int16(f); // revision level
 mp4lib_read_int32(f); // vendor

 p_track->audio_channels = mp4lib_read_int16(f);
 p_track->audio_bits   = mp4lib_read_int16(f);
 mp4lib_read_int16(f); // cid
 mp4lib_read_int16(f); // packet_size
 p_track->audio_freq = mp4lib_read_int32(f)>>16;

 switch(version){
  case 1:
   if(size>=(28+16)){
    p_track->samples_per_frame=mp4lib_read_int32(f); // samples_per_frame
    mp4lib_read_int32(f); // bytes_per_packet
    p_track->bytes_per_frame=mp4lib_read_int32(f); // bytes_per_frame
    mp4lib_read_int32(f); // bytes_per_sample
   }
   break;
  case 2:
   if(size>=(28+32)){
    p_track->audio_freq = ff_int2dbl(mp4lib_read_int64(f));
    p_track->audio_channels = mp4lib_read_int32(f);
    mp4lib_read_int32(f); // always 0x7F000000 ?
    p_track->audio_bits = mp4lib_read_int32(f);
    flags=mp4lib_read_int32(f);
    p_track->bytes_per_frame=mp4lib_read_int32(f);
    p_track->samples_per_frame=mp4lib_read_int32(f);
   }
   break;
 }

 // ??? read ESDS (for WMA)

 return 0;
}

static mpxp_int32_t mp4lib_read_stsd(mp4lib_main_info_t *f)
{
 mpxp_uint32_t i,atom_name;
 mp4lib_track_info_t *p_track = f->lasttrack;
 mpxp_uint8_t atom_type,header_size;

 mp4lib_read_int32(f); // version (1) & flags (3)

 p_track->stsd_entry_count = mp4lib_read_int32(f);

 for(i=0; i<p_track->stsd_entry_count; i++){
  mpxp_uint64_t size = mp4lib_atom_read_header(f, &atom_type, &header_size, &atom_name);
  mpxp_filesize_t skipfilepos=f->fbfs->ftell(f->fbds)+size;

  switch(atom_type){
   case ATOM_MP4A:p_track->streamtype=TRACK_AUDIO;mp4lib_read_mp4a(f);break;
   case ATOM_MP4V:p_track->streamtype=TRACK_VIDEO;break;
   case ATOM_MP4S:p_track->streamtype=TRACK_SYSTEM;break;
   case ATOM_ALAC:p_track->streamtype=TRACK_AUDIO;mp4lib_read_alac(f,size);break;
   default:mp4lib_read_audio(f,atom_name,size);
  }

  f->fbfs->fseek(f->fbds,skipfilepos,SEEK_SET);
 }

 return 0;
}

/*static mpxp_int32_t mp4lib_read_ctts(mp4lib_main_info_t *f)
{
 mpxp_int32_t i;
 mp4lib_track_info_t *p_track = f->lasttrack;

 if(p_track->ctts_entry_count)
  return 0;

 mp4lib_read_int32(f); // version (1) & flags (3)

 p_track->ctts_entry_count = mp4lib_read_int32(f);
 if(!p_track->ctts_entry_count)
  return 1;

 p_track->ctts_sample_count  = (mpxp_int32_t*)malloc(p_track->ctts_entry_count * sizeof(mpxp_int32_t));
 p_track->ctts_sample_offset = (mpxp_int32_t*)malloc(p_track->ctts_entry_count * sizeof(mpxp_int32_t));
 if(!p_track->ctts_sample_count || !p_track->ctts_sample_offset){
  p_track->ctts_entry_count = 0;
  return 1;
 }

 for(i=0; i<f->lasttrack->ctts_entry_count; i++){
  p_track->ctts_sample_count[i]  = mp4lib_read_int32(f);
  p_track->ctts_sample_offset[i] = mp4lib_read_int32(f);
 }

 return 0;
}*/

static mpxp_int32_t mp4lib_read_stts(mp4lib_main_info_t *f)
{
 mpxp_int32_t i;
 mp4lib_track_info_t *p_track = f->lasttrack;
 mp4lib_stts_sample_info_t *stts;

 if(p_track->stts_entry_count)
  return 0;

 mp4lib_read_int32(f); // version (1) & flags (3)

 i = mp4lib_read_int32(f);
 if(!i)
  return 1;

 p_track->stts_sample_infos = stts = (mp4lib_stts_sample_info_t *)malloc(i * sizeof(mp4lib_stts_sample_info_t));
 if(!stts)
  return -1;
 p_track->stts_entry_count = i;

 if(mp4lib_read_data(f,p_track->stts_sample_infos,i*sizeof(mp4lib_stts_sample_info_t))!=(i*sizeof(mp4lib_stts_sample_info_t)))
  return -1;

 do{
  stts->count=PDS_GETB_BE32(&stts->count);
  stts->delta=PDS_GETB_BE32(&stts->delta); // ??? not used
  stts++;
 }while(--i);

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"STTS ec:%d sc0:%d sd0:%d\n",p_track->stts_entry_count,
  p_track->stts_sample_infos[0].count,p_track->stts_sample_infos[0].delta);
#endif

 return 0;
}

mpxp_int32_t mp4lib_read_stsc(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 mp4lib_stsc_info_t *stsc;
 mpxp_int32_t i;

 if(p_track->stsc_entry_count)
  return 0;

 mp4lib_read_int32(f); // version (1) & flags (3)

 p_track->stsc_entry_count = i = mp4lib_read_int32(f);
 if(!i)
  return 1;

 p_track->stsc_infos = stsc = (mp4lib_stsc_info_t *)malloc(i*sizeof(mp4lib_stsc_info_t));
 if(!stsc)
  return -1;

 if(mp4lib_read_data(f,stsc,i*sizeof(mp4lib_stsc_info_t))!=(i*sizeof(mp4lib_stsc_info_t)))
  return -1;

 do{
  stsc->first_chunk=PDS_GETB_BE32(&stsc->first_chunk);
  stsc->samples_per_chunk=PDS_GETB_BE32(&stsc->samples_per_chunk);
  stsc->sample_desc_index=PDS_GETB_BE32(&stsc->sample_desc_index); // ??? not used
  stsc++;
 }while(--i);

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"STSC ec:%d fc0:%d spc0:%d fc1:%d spc1:%d\n",p_track->stsc_entry_count,
  p_track->stsc_infos[0].first_chunk,p_track->stsc_infos[0].samples_per_chunk,
  p_track->stsc_infos[1].first_chunk,p_track->stsc_infos[1].samples_per_chunk);
#endif

 return 0;
}

static mpxp_int32_t mp4lib_read_stsz(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 unsigned int i;

 mp4lib_read_int32(f); // version (1) & flags (3)

 p_track->stsz_max_sample_size = p_track->stsz_sample_size = mp4lib_read_int32(f);
 p_track->stsz_sample_count = i = mp4lib_read_int32(f);

 if(!p_track->stsz_sample_size && i){
  mpxp_uint32_t *stsz,max_sample_size;

  p_track->stsz_table = stsz = (mpxp_uint32_t *)malloc(i*sizeof(mpxp_uint32_t));
  if(!stsz)
   return 1;

  if(mp4lib_read_data(f,stsz,i*sizeof(mpxp_uint32_t))!=i*sizeof(mpxp_uint32_t))
   return 1;

  max_sample_size=0;
  do{
   mpxp_uint32_t ss=PDS_GETB_BE32(stsz);
   *stsz++ = ss;
   if(ss>max_sample_size)
    max_sample_size=ss;
  }while(--i);
  p_track->stsz_max_sample_size=max_sample_size;
 }

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"STSZ ss:%d mss:%d sc:%d\n",p_track->stsz_sample_size,p_track->stsz_max_sample_size,p_track->stsz_sample_count);
#endif

 return 0;
}

mpxp_int32_t mp4lib_read_stco(mp4lib_main_info_t *f, const mpxp_uint8_t atom_type)
{
 mp4lib_track_info_t *p_track = f->lasttrack;
 mpxp_filesize_t *scop;
 mpxp_uint32_t i;

 if(p_track->stco_entry_count)
  return 0;

 mp4lib_read_int32(f); // version (1) & flags (3)

 i = mp4lib_read_int32(f);
 if(!i)
  return 1;
 p_track->stco_entry_count = i;

 scop = (mpxp_filesize_t *)malloc(i*sizeof(mpxp_filesize_t)+8);
 if(!scop)
  return 1;
 p_track->stco_chunk_offset = scop;

#ifdef MPXPLAY_FSIZE64
 if(atom_type==ATOM_CO64){
  if(mp4lib_read_data(f,scop,i*sizeof(mpxp_uint64_t))!=i*sizeof(mpxp_uint64_t))
   return 1;
  do{
#ifdef __WATCOMC__
   mpxp_filesize_t tmp=*scop;
   PDS_GETBD_BEU64(scop,&tmp);
#else
   *scop=PDS_GETB_BEU64(scop);
#endif
   scop++;
  }while(--i);
 }else{
  mpxp_uint32_t *sc32;
  if(mp4lib_read_data(f,scop,i*sizeof(mpxp_uint32_t))!=i*sizeof(mpxp_uint32_t))
   return 1;
  sc32=((mpxp_uint32_t *)scop)+i;
  scop+=i;
  do{
   scop--;sc32--;
   *scop=(mpxp_uint64_t)PDS_GETB_BE32(sc32);
  }while(--i);
 }
#else
 if(mp4lib_read_data(f,scop,i*sizeof(mpxp_filesize_t))!=i*sizeof(mpxp_filesize_t))
  return 1;
 do{
  PDS_PUTB_BEU32(scop,*scop);
  scop++;
 }while(--i);
#endif

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"STCO ec:%d co0:%"PRIi64" co1:%"PRIi64" co2:%"PRIi64" co3:%"PRIi64"\n",
  p_track->stco_entry_count,
  (mpxp_int64_t)p_track->stco_chunk_offset[0],(mpxp_int64_t)p_track->stco_chunk_offset[1],
  (mpxp_int64_t)p_track->stco_chunk_offset[2],(mpxp_int64_t)p_track->stco_chunk_offset[3]);
#endif

 return 0;
}

// modifies stco table in-place !!!
static mpxp_int32_t mp4lib_write_stco_entries(mp4lib_main_info_t *f, const mpxp_uint32_t atom_name, unsigned int tracknum, mpxp_int32_t offset_change)
{
 mp4lib_track_info_t *p_track;
 mpxp_filesize_t *scop;
 mpxp_uint32_t i;

 if(tracknum>=f->total_tracks)
  return -1;
 p_track=f->track[tracknum];

 i=p_track->stco_entry_count;
 scop=p_track->stco_chunk_offset;
 if(!i || !scop)
  return -1;

 // skip stco_size, stco_name, flags, entry_count (4*4 bytes)

#ifdef MPXPLAY_FSIZE64
 if(atom_name==PDS_GET4C_LE32('c','o','6','4')){
  do{
   mpxp_filesize_t tmp=*scop+(mpxp_filesize_t)offset_change;
   PDS_PUTB_BEU64(scop,tmp);
   scop++;
  }while(--i);
  i=p_track->stco_entry_count*sizeof(mpxp_uint64_t);
 }else{
  mpxp_uint32_t *sc32=(mpxp_uint32_t *)scop;
  do{
   PDS_PUTB_BEU32(sc32,((mpxp_uint32_t)*scop + offset_change));
   sc32++;scop++;
  }while(--i);
  i=p_track->stco_entry_count*sizeof(mpxp_uint32_t);
 }
#else
 do{
  PDS_PUTB_BEU32(scop,(*scop + offset_change));
  scop++;
 }while(--i);
 i=p_track->stco_entry_count*sizeof(mpxp_uint32_t);
#endif
 if(mp4lib_write_data(f,p_track->stco_chunk_offset,i)!=i)
  return -1;

#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"STCO ec:%d co0:%"PRIi64" co1:%"PRIi64" co2:%"PRIi64"\n",p_track->stco_entry_count,
  (mpxp_int64_t)p_track->stco_chunk_offset[0],(mpxp_int64_t)p_track->stco_chunk_offset[1],(mpxp_int64_t)p_track->stco_chunk_offset[2]);
#endif

 return 0;
}

//--------------------------------------------------------------------------
// mp4 lib open
static void mp4lib_close(mp4lib_main_info_t *ff);
static mpxp_int32_t mp4lib_parse_atoms(mp4lib_main_info_t *f);

static mp4lib_main_info_t *mp4lib_open_read(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,mpxp_uint32_t openmode)
{
 mp4lib_main_info_t *ff=calloc(1,sizeof(mp4lib_main_info_t));
 if(!ff)
  return ff;

#ifdef MP4LIB_ATOM_DEBUG
 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_ID3)
  openmode|=MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD|MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB;
#endif

 ff->fbfs=fbfs;
 ff->fbds=fbds;
 ff->openmode=openmode;

 if(openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD){
  ff->atompath_current=calloc(MP4LIB_ATOMPATH_MAXLEVELS,sizeof(mp4lib_atomlevel_info_t));
  if(!ff->atompath_current)
   goto err_out_open;
  ff->atompath_infos=calloc(MP4LIB_ATOMPATH_INITPATHES,sizeof(mp4lib_atompath_info_t));
  if(!ff->atompath_infos)
   goto err_out_open;
  ff->atompathes_allocated=MP4LIB_ATOMPATH_INITPATHES;
 }

 if(mp4lib_parse_atoms(ff)!=0)
  goto err_out_open;

#ifdef MP4LIB_ATOM_DEBUG
 if(openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD){
  unsigned int i,j;
  mp4lib_atompath_info_t *api=ff->atompath_infos;
  //char sout[128];
  fprintf(stdout,"tt:%d om:%8.8X\n",ff->total_tracks,openmode);
  //pds_textdisplay_printf(sout);
  for(i=0;i<(MP4LIB_ATOMPATHINDEX_NUM+ff->total_tracks);i++,api++){
   mp4lib_atomlevel_info_t *ali=api->atompath;
   for(j=0;j<api->atomlevels;j++,ali++){
    fprintf(stdout,"i:%d j:%d %4.4s s:%"PRIi64" p:%"PRIi64"\n",i,j,(char *)&ali->atom_name,ali->atom_oldsize,ali->atom_filepos);
    //pds_textdisplay_printf(sout);
   }
  }
 }
#endif

 return ff;

err_out_open:
 mp4lib_close(ff);
 return NULL;
}

static void mp4lib_close(mp4lib_main_info_t *ff)
{
 mpxp_int32_t i;
 mp4lib_atompath_info_t *api;

 if(ff){

  for(i = 0; i < ff->total_tracks; i++){
   if(ff->track[i]){
    if(ff->track[i]->stsz_table)        free(ff->track[i]->stsz_table);
    if(ff->track[i]->stts_sample_infos) free(ff->track[i]->stts_sample_infos);
    if(ff->track[i]->stsc_infos)        free(ff->track[i]->stsc_infos);
    if(ff->track[i]->stco_chunk_offset) free(ff->track[i]->stco_chunk_offset);
    if(ff->track[i]->decoderConfig)     free(ff->track[i]->decoderConfig);
    //if(ff->track[i]->ctts_sample_count) free(ff->track[i]->ctts_sample_count);
    //if(ff->track[i]->ctts_sample_offset)free(ff->track[i]->ctts_sample_offset);
    free(ff->track[i]);
   }
  }

  mp4lib_tagging_close(&(ff->ilst_infos));

  if(ff->openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD){
   if(ff->atompath_current)
    free(ff->atompath_current);

   api=ff->atompath_infos;
   if(api){
    for(i=0;i<ff->atompathes_allocated;i++,api++)
     if(api->atompath)
      free(api->atompath);
    free(ff->atompath_infos);
   }
   if(ff->meta_subatoms_list.atom_datas){
    i=ff->meta_subatoms_list.atoms_loaded;
    if(i){
     mp4lib_oneatom_info_t *ads=ff->meta_subatoms_list.atom_datas;
     do{
      if(ads->atom_data)
       free(ads->atom_data);
     }while(--i);
    }
    free(ff->meta_subatoms_list.atom_datas);
   }

   if(ff->new_atomfield_data)
    free(ff->new_atomfield_data);
  }

  free(ff);
 }
}

//------------------------------------------------------------------------
static mpxp_uint32_t mp4lib_atompathes_for_save[]={
 PDS_GET4C_LE32('m','e','t','a'),PDS_GET4C_LE32('u','d','t','a'),
 PDS_GET4C_LE32('m','o','o','v'),
 PDS_GET4C_LE32('s','t','c','o'),PDS_GET4C_LE32('c','o','6','4')
};

static mpxp_uint32_t mp4lib_atompathes_expected_parrents[]={
 PDS_GET4C_LE32('u','d','t','a'),PDS_GET4C_LE32('m','o','o','v'),
 0, // a root atom
 PDS_GET4C_LE32('s','t','b','l'),PDS_GET4C_LE32('s','t','b','l')
};

static mpxp_int32_t mp4lib_atomlevel_add(mp4lib_main_info_t *f, mpxp_uint32_t atom_name, mpxp_uint32_t atomheadsize, mpxp_uint64_t atom_size)
{
 mp4lib_atomlevel_info_t *ali=f->atompath_current;
 unsigned int i;
 if(!ali)
  return 0;
 if(f->atomlevel_current>=MP4LIB_ATOMPATH_MAXLEVELS)
  return -1;
 ali+=f->atomlevel_current++;
 ali->atom_name=atom_name;
 ali->atom_filepos=f->fbfs->ftell(f->fbds)-atomheadsize;
 ali->atom_oldsize=atom_size;

 for(i=0;i<5;i++){ // sizeof(mp4lib_atompathes_for_save)
  if(atom_name==mp4lib_atompathes_for_save[i]){
   mp4lib_atompath_info_t *api;
   if(f->atomlevel_current>=2)
    if(f->atompath_current[f->atomlevel_current-2].atom_name!=mp4lib_atompathes_expected_parrents[i])
     break;
   if(i>=MP4LIB_ATOMPATHINDEX_STCO){
    if(!f->total_tracks)
     return -1;
    i=MP4LIB_ATOMPATHINDEX_STCO+f->total_tracks-1;
   }
   if(i>=f->atompathes_allocated){
    unsigned int an=i+16;
    if(an>MP4LIB_ATOMPATH_MAXPATHES)
     return -1;
    api=calloc(an,sizeof(mp4lib_atompath_info_t));
    if(!api)
     return -1;
    pds_memcpy(api,f->atompath_infos,(f->atompathes_allocated*sizeof(mp4lib_atompath_info_t)));
    free(f->atompath_infos);
    f->atompath_infos=api;
    f->atompathes_allocated=an;
   }
   api=f->atompath_infos+i;
   if(api->atompath)
    return -1;
   api->atomlevels=f->atomlevel_current;
   api->atompath=calloc(f->atomlevel_current,sizeof(mp4lib_atomlevel_info_t));
   if(!api->atompath)
    return -1;
   pds_memcpy(api->atompath,f->atompath_current,f->atomlevel_current*sizeof(mp4lib_atomlevel_info_t));
   break;
  }
 }

 return 1;
}

static void mp4lib_atomlevel_remove(mp4lib_main_info_t *f)
{
 if(f->atomlevel_current)
  f->atomlevel_current--;
}

static mp4lib_oneatom_info_t *mp4lib_atomlist_add_atom(mp4lib_main_info_t *f,mp4lib_atomlist_info_t *al,mpxp_uint32_t atom_name,mpxp_uint32_t atom_size,mpxp_uint8_t *atom_data,unsigned int readdata)
{
 mp4lib_oneatom_info_t *ads;
 if(!al)
  return NULL;
 if(al->atoms_loaded>=al->atoms_allocated){
  ads=calloc((al->atoms_allocated+8),sizeof(mp4lib_oneatom_info_t));
  if(!ads)
   return NULL;
  if(al->atom_datas){
   if(al->atoms_allocated)
    pds_memcpy(ads,al->atom_datas,(al->atoms_allocated*sizeof(mp4lib_oneatom_info_t)));
   free(al->atom_datas);
  }
  al->atom_datas=ads;
  al->atoms_allocated+=8;
 }
 ads=&al->atom_datas[al->atoms_loaded];
 ads->atom_name=atom_name;
 ads->atom_size=atom_size;
 if(ads->atom_data){ // ??? after read fault
  free(ads->atom_data);
  ads->atom_data=NULL;
 }
 if(atom_size && (atom_data || readdata)){
  mpxp_uint8_t *p;
  ads->atom_data=p=malloc(atom_size+8);
  if(!p)
   return NULL;
  if(atom_data)
   pds_memcpy(p,atom_data,atom_size);
  else if(f->fbfs->fread(f->fbds,p,atom_size)!=atom_size)
   return NULL;
 }
 al->atoms_loaded++;
 al->atoms_totalsize+=8+atom_size;
 return ads;
}

static mpxp_int32_t mp4lib_atomlist_modify_atom(mp4lib_atomlist_info_t *al,mp4lib_oneatom_info_t *ads,mpxp_uint32_t atom_size,mpxp_uint8_t *atom_data)
{
 if(!ads)
  return MPXPLAY_ERROR_INFILE_MEMORY;
 if(al->atoms_totalsize>(ads->atom_size+8))
  al->atoms_totalsize-=(ads->atom_size+8);
 else
  al->atoms_totalsize=0;
 if(ads->atom_data){
  free(ads->atom_data);
  ads->atom_data=NULL;
 }
 if(atom_size){
  mpxp_uint8_t *p;
  ads->atom_data=p=malloc(atom_size+8);
  if(!p)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  if(atom_data)
   pds_memcpy(p,atom_data,atom_size);
  else
   pds_memset(p,0,atom_size);
 }
 ads->atom_size=atom_size;
 al->atoms_totalsize+=8+atom_size;
 return 1;
}

static mp4lib_oneatom_info_t *mp4lib_atomlist_search_atom(mp4lib_atomlist_info_t *al,mpxp_uint32_t atom_name)
{
 mp4lib_oneatom_info_t *ads;
 unsigned int i;
 if(!al)
  return NULL;
 i=al->atoms_loaded;
 if(!i)
  return NULL;
 ads=al->atom_datas;
 do{
  if(ads->atom_name==atom_name)
   return ads;
  ads++;
 }while(--i);
 return NULL;
}

//-----------------------------------------------------------------------
static mpxp_int32_t mp4lib_track_add(mp4lib_main_info_t *f)
{
 mp4lib_track_info_t *p_track;
 if(f->total_tracks >= MP4LIB_MAX_TRACKS)
  return -1;
 p_track = calloc(1,sizeof(mp4lib_track_info_t));
 if(!p_track)
  return -1;
 f->lasttrack = p_track;
 f->track[f->total_tracks] = p_track;
 f->total_tracks++;
 return 0;
}

static mpxp_filesize_t mp4lib_atoms_read(mp4lib_main_info_t *f, const mpxp_int32_t size, const mpxp_uint8_t atom_type)
{
 mpxp_filesize_t skipfilepos=f->fbfs->ftell(f->fbds)+size-8;

 if(f->openmode&MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB){
  switch(atom_type){
   case ATOM_STSZ: mp4lib_read_stsz(f);break; // sample size box
   case ATOM_STTS: mp4lib_read_stts(f);break; // time to sample box
   //case ATOM_CTTS: mp4lib_read_ctts(f);break; // composition offset box
   case ATOM_STSC: mp4lib_read_stsc(f);break; // sample to chunk box
#ifdef MPXPLAY_FSIZE64
   case ATOM_CO64:
#endif
   case ATOM_STCO:mp4lib_read_stco(f,atom_type);break; // chunk offset box
  }
 }
 switch(atom_type){
  case ATOM_STSD: mp4lib_read_stsd(f);break; // sample description box
  case ATOM_MDHD: mp4lib_read_mdhd(f);break; // track header
  case ATOM_META: // metadata box
#ifdef MP4LIB_ATOM_DEBUG
   fprintf(stdout,"META s:%d\n",size);
#endif
   if(f->openmode&MPXPLAY_INFILE_OPENMODE_INFO_ID3)
    mp4lib_tagging_read_meta_atom(f,size);
   break;
 }

 return f->fbfs->fseek(f->fbds,skipfilepos,SEEK_SET);
}

static mpxp_filesize_t mp4lib_parse_sub_atoms(mp4lib_main_info_t *f, const mpxp_uint64_t total_size)
{
 mpxp_uint8_t atom_type,header_size;
 mpxp_uint32_t atom_name;
 mpxp_filesize_t retcode=0,skipfilepos=f->fbfs->ftell(f->fbds)+total_size;
 mpxp_uint64_t size,counted_size = 0;

 while(counted_size < total_size){
  size = mp4lib_atom_read_header(f, &atom_type, &header_size, &atom_name);
  if((size<header_size) || (atom_type==ATOM_INVALID))
   break;
  if(size==0){
   if(f->fbfs->eof(f->fbds))
    return MPXPLAY_ERROR_INFILE_EOF;
   counted_size+=4; // else a 4 bytes hole ...
   continue;        // try to skip it
  }

  if((counted_size+size)>total_size){ // wrong subatom size
   if((counted_size+header_size)>=total_size)
    break;
   size=total_size-counted_size; // max valid size for the subatom
  }
  counted_size+=size;

/*#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"SUBB%d %4.4s s:%"PRIi64" ts:%"PRIi64"\n",f->atomlevel_current,(char *)&atom_name,size,total_size);
#endif*/

  if(atom_type==ATOM_TRAK)
   if((retcode=mp4lib_track_add(f))<0)
    break;

  if(mp4lib_atomlevel_add(f,atom_name,header_size,size)<0)
   return -1;
  if(atom_type<SUBATOMIC){
   if((retcode=mp4lib_parse_sub_atoms(f, size-header_size))<0)
    break;
  }else{
   if((retcode=mp4lib_atoms_read(f, (mpxp_uint32_t)size, atom_type))<0)
    break;
  }
  mp4lib_atomlevel_remove(f);
/*#ifdef MP4LIB_ATOM_DEBUG
  fprintf(stdout,"SUBE%d %4.4s s:%"PRIi64" ts:%"PRIi64"\n",f->atomlevel_current,(char *)&atom_name,size,total_size);
#endif*/
 }
 if((retcode>=0) && (counted_size!=total_size))
  retcode=f->fbfs->fseek(f->fbds,skipfilepos,SEEK_SET);
 return retcode;
}

static mpxp_int32_t mp4lib_parse_atoms(mp4lib_main_info_t *f)
{
 mpxp_uint8_t atom_type,header_size,firstatom=1;
 mpxp_uint32_t atom_name;
 mpxp_filesize_t retcode=0;
 mpxp_uint64_t size;

 do{
  size = mp4lib_atom_read_header(f, &atom_type, &header_size, &atom_name);

/*#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"ROOB %4.4s s:%"PRIi64"\n",(char *)&atom_name,size);
#endif*/
  if(firstatom){
   if((size==0) || (atom_type==ATOM_INVALID) || (atom_type==ATOM_UNKNOWN))
    return -1;
   firstatom=0;
  }
  if(size==0) // eof
   break;

  if(atom_type<SUBATOMIC){
   if(mp4lib_atomlevel_add(f,atom_name,header_size,size)<0)
    return -1;
   retcode=mp4lib_parse_sub_atoms(f, size-header_size);
   mp4lib_atomlevel_remove(f);
  }else
   retcode=f->fbfs->fseek(f->fbds,size-header_size,SEEK_CUR);
/*#ifdef MP4LIB_ATOM_DEBUG
 fprintf(stdout,"ROOE %4.4s s:%"PRIi64"\n",(char *)&atom_name,size);
#endif*/
 }while(retcode>=0);

 return 0;
}

//------------------------------------------------------------------------
static mpxp_int32_t mp4lib_merge_small_pcmframes(mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 mp4lib_track_info_t *p_track;
 unsigned int i,j,bytes_per_sample;

 if(track >= f->total_tracks)
  return -1;
 p_track=f->track[track];

 switch(p_track->audio_wave_id){
  case MPXPLAY_WAVEID_PCM_SLE:
  case MPXPLAY_WAVEID_PCM_SBE:
  case MPXPLAY_WAVEID_PCM_UBE:break;
  default:
   switch(p_track->audio_type){
    case MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE:
    case MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE:break;
    default:return 0;
   }
 }

 bytes_per_sample=p_track->audio_channels*p_track->audio_bits/8;
 if(!p_track->stsz_sample_size || (p_track->stsz_sample_size>bytes_per_sample) || !p_track->stco_entry_count)
  return 0;

 p_track->stsz_table=malloc(p_track->stco_entry_count*sizeof(mpxp_int32_t));
 if(!p_track->stsz_table)
  return -1;
 p_track->stsz_sample_count=p_track->stco_entry_count;
 p_track->stsz_sample_size=0;

 p_track->stts_entry_count=1;
 p_track->stts_sample_infos[0].count=p_track->stco_entry_count;
 p_track->stts_sample_infos[0].delta=p_track->stsc_infos[0].samples_per_chunk*bytes_per_sample; // ???

 for(i=0,j=0;(i<p_track->stco_entry_count);i++){ // frames = chunks
  p_track->stsz_table[i]=p_track->stsc_infos[j].samples_per_chunk*bytes_per_sample;
  //fprintf(stdout,"scec:%d coec:%d i:%d ti:%d\n",p_track->stsc_entry_count,p_track->stco_entry_count,i,p_track->stsz_table[i]);
  if(((j+1)<p_track->stsc_entry_count) && ((i+2)>=(p_track->stsc_infos[j+1].first_chunk)))
   j++;
 }

 p_track->stsz_max_sample_size=0;
 for(i=0;i<p_track->stsc_entry_count;i++){
  if(p_track->stsc_infos[i].samples_per_chunk*bytes_per_sample>p_track->stsz_max_sample_size)
   p_track->stsz_max_sample_size=p_track->stsc_infos[i].samples_per_chunk*bytes_per_sample;
  p_track->stsc_infos[i].samples_per_chunk=1;
 }
 p_track->stsc_entry_count=1;
 return 0;
}

static mpxp_int32_t mp4lib_get_decoder_config_size(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 if(track >= f->total_tracks)
  return -1;

 if((f->track[track]->decoderConfig==NULL) || (f->track[track]->decoderConfigLen<=0))
  return 0;

 return f->track[track]->decoderConfigLen;
}

static mpxp_int32_t mp4lib_get_decoder_config_v2(const mp4lib_main_info_t *f, const mpxp_int32_t track,
                                 mpxp_uint8_t *ppBuf, mpxp_uint32_t pBufSize)
{
 if(track >= f->total_tracks)
  return -1;

 if(f->track[track]->decoderConfig==NULL || f->track[track]->decoderConfigLen==0)
  return 0;

 if(pBufSize<f->track[track]->decoderConfigLen)
  return -2;

 pds_memcpy(ppBuf, f->track[track]->decoderConfig, f->track[track]->decoderConfigLen);

 return f->track[track]->decoderConfigLen;
}

static mpxp_int32_t mp4lib_get_track_type(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 return f->track[track]->streamtype;
}

static mpxp_int32_t mp4lib_total_tracks(const mp4lib_main_info_t *f)
{
 return f->total_tracks;
}

static mpxp_uint32_t mp4lib_get_audio_type(const mp4lib_main_info_t *f,const mpxp_int32_t track)
{
 return f->track[track]->audio_type;
}

static mpxp_uint32_t mp4lib_get_audio_waveid(const mp4lib_main_info_t *f,const mpxp_int32_t track)
{
 return f->track[track]->audio_wave_id;
}

static mpxp_uint32_t mp4lib_get_audio_freq(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 return f->track[track]->audio_freq;
}

static mpxp_uint32_t mp4lib_get_audio_channels(const mp4lib_main_info_t *f,const mpxp_int32_t track)
{
 return f->track[track]->audio_channels;
}

static mpxp_uint32_t mp4lib_get_audio_bits(const mp4lib_main_info_t *f,const mpxp_int32_t track)
{
 return f->track[track]->audio_bits;
}

static mpxp_uint32_t mp4lib_get_track_bitrate(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 return f->track[track]->avgBitrate;
}

static mpxp_int64_t mp4lib_get_track_duration(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 return f->track[track]->duration;
}

static mpxp_int32_t mp4lib_num_samples(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 mpxp_int32_t i;
 mpxp_int32_t total = 0;

 for (i = 0; i < f->track[track]->stts_entry_count; i++)
  total += f->track[track]->stts_sample_infos[i].count;

 return total;
}

// among all samples
static mpxp_int32_t mp4lib_read_sample_maxsize(const mp4lib_main_info_t *f, const mpxp_int32_t track)
{
 return f->track[track]->stsz_max_sample_size;
}

// specified sample
static mpxp_int32_t mp4lib_read_sample_getsize(mp4lib_main_info_t *f, const mpxp_int32_t track, const mpxp_int32_t sample)
{
 const mp4lib_track_info_t *p_track = f->track[track];
 mpxp_int32_t bytes;

 if(p_track->stsz_sample_size)
  bytes = p_track->stsz_sample_size;
 else
  bytes = p_track->stsz_table[sample];

 if(bytes<0)
  bytes=0;

 return bytes;
}

static mpxp_int32_t mp4lib_read_sample_v2(mp4lib_main_info_t *f, const mpxp_int32_t track, const mpxp_int32_t sample,mpxp_uint8_t *buffer)
{
 mpxp_int32_t result = 0;
 mpxp_int32_t size = mp4lib_read_sample_getsize(f,track,sample);

 if(size<=0)
  return 0;

 result = mp4lib_read_data(f,(mpxp_int8_t *)buffer,size);

 return result;
}

//------------------------------------------------------------------------
// metadata handling
#define TAGS_INIT_STORAGE 16

extern char *mpxplay_tagging_id3v1_index_to_genre(unsigned int i);
extern unsigned int mpxplay_tagging_id3v1_genre_to_index(char *genrename);

static void mp4lib_tagging_close(mp4lib_ilst_info_t *tags)
{
 mp4lib_onetag_info_t *tagp=tags->tags;
 if(tagp){
  mpxp_uint32_t i=tags->count;
  if(i){
   do{
    if(tagp->name)
     free(tagp->name);
    if(tagp->data)
     free(tagp->data);
    tagp++;
   }while(--i);
  }
  free(tags->tags);
  tags->tags=NULL;
 }
 tags->count=0;
}

static char *mp4lib_read_string(mp4lib_main_info_t *f, mpxp_uint32_t length)
{
 char *str = (char*)malloc(length + 2);
 if(str){
  if(mp4lib_read_data(f,(mpxp_int8_t *)str,length)==length)
   PDS_PUTB_LE16(&str[length],0);
  else{
   free(str);
   str = NULL;
  }
 }
 return str;
}

static mpxp_int32_t mp4lib_tag_add_or_modify_field(mp4lib_ilst_info_t *tags,unsigned int atom_num,mpxp_uint32_t atom_name, char *name,char *data,unsigned int data_size,mpxp_uint32_t data_flags)
{
 mp4lib_onetag_info_t *tagp;
 unsigned int i;

 if(!atom_num)
  return 0;

 if(tags->count>=tags->storage){
  unsigned int newsize=(tags->storage)? (tags->storage*2):TAGS_INIT_STORAGE;
  mp4lib_onetag_info_t *newmem=calloc(newsize,sizeof(mp4lib_onetag_info_t));
  if(!newmem)
   return 0;
  if(tags->tags){
   if(tags->count)
    pds_memcpy(newmem,tags->tags,tags->count*sizeof(mp4lib_onetag_info_t));
   free(tags->tags);
  }
  tags->tags=newmem;
  tags->storage=newsize;
 }

 if(atom_num!=ATOM_UNKNOWN){
  tagp=&tags->tags[0];
  for(i=0;i<tags->count;i++,tagp++){
   if(tagp->atom_num==atom_num){
    if(tagp->name)
     free(tagp->name);
    if(tagp->data)
     free(tagp->data);
    pds_memset(tagp,0,sizeof(mp4lib_onetag_info_t));
    break;
   }
  }
 }else{
  i=tags->count;
  tagp=&tags->tags[i];
 }

 if(data){
  if(name){
   unsigned int namelen=pds_strlen(name);
   if(namelen){
    tagp->name = malloc(namelen+1);
    if(!tagp->name)
     return 0;
    pds_memcpy(tagp->name,name,namelen+1);
   }
  }
  tagp->data=malloc(data_size+2);
  if(!tagp->data){
   if(tagp->name){
    free(tagp->name);
    tagp->name=NULL;
   }
   return 0;
  }
  if(data_size)
   pds_memcpy(tagp->data,data,data_size);
  PDS_PUTB_LE16(&tagp->data[data_size],0);
  tagp->atom_num=atom_num;
  tagp->atom_name=atom_name;
  tagp->data_size=data_size;
  tagp->data_flags=data_flags;
  if(i==tags->count)
   tags->count++;
 }

 return 1;
}

static mpxp_int32_t mp4lib_parse_tag(mp4lib_main_info_t *f, const mpxp_uint8_t parent_atom_type, mpxp_uint32_t parent_atom_name, const mpxp_uint32_t parent_size)
{
 mpxp_uint8_t atom_type,header_size;
 mpxp_uint32_t ddone=0,dflags=0;
 mpxp_filesize_t retcode,endoftag=f->fbfs->ftell(f->fbds)+parent_size;
 mpxp_uint64_t subsize, sumsize=0;
 char *name_ptr=NULL,*data_ptr=NULL;
 int dlen=-1;
 char strtmp[32];

 switch(parent_atom_type){
  case ATOM_UNKNOWN:
   dlen=parent_size;
   if(f->openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD)
    data_ptr=mp4lib_read_string(f,dlen);
  case ATOM_FREE:
  case ATOM_SKIP:
   sumsize=parent_size;
 }

 while(sumsize<parent_size){
  mpxp_uint32_t val,index,total;
  mpxp_filesize_t skipfilepos;

  subsize = mp4lib_atom_read_header(f, &atom_type, &header_size, NULL);
  skipfilepos=f->fbfs->ftell(f->fbds)+subsize-header_size;
  switch(atom_type){
   case ATOM_NAME:
    if((f->openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD) && !name_ptr){
     mp4lib_read_int32(f); // version (1) flags (3)
     name_ptr = mp4lib_read_string(f,(mpxp_uint32_t)(subsize-(header_size+4)));
    }
    break;
   case ATOM_DATA:
    if(ddone)
     break;

    dflags=mp4lib_read_int32(f); // version (1) flags (3)
    mp4lib_read_int32(f); // reserved (language?)

    switch(parent_atom_type){
     case ATOM_GENRE2:
      val=mp4lib_read_int16(f);
      if(val){
       data_ptr=mpxplay_tagging_id3v1_index_to_genre(val-1);
       mp4lib_tag_add_or_modify_field(&(f->ilst_infos),parent_atom_type,parent_atom_name,NULL,data_ptr,pds_strlen(data_ptr),dflags);
       data_ptr=NULL;
      }
      break;
     case ATOM_TEMPO:
      val=mp4lib_read_int16(f);
      if(val){
       dlen=sprintf(strtmp,"%.5u BPM",val);
       if(dlen>0)
        mp4lib_tag_add_or_modify_field(&(f->ilst_infos),parent_atom_type,parent_atom_name,NULL,strtmp,dlen,dflags);
      }
      break;
     case ATOM_TRACK:
     case ATOM_DISC:
      if(ddone || ((subsize-header_size)<16))
       break;
      mp4lib_read_int16(f);
      index = mp4lib_read_int16(f);
      total = mp4lib_read_int16(f);
      mp4lib_read_int16(f);
      if(total>0)
       dlen=sprintf(strtmp,"%d/%d",index,total);
      else
       dlen=sprintf(strtmp,"%d",index);
      if(dlen>0)
       mp4lib_tag_add_or_modify_field(&(f->ilst_infos),parent_atom_type,parent_atom_name,NULL,strtmp,dlen,dflags);
      break;
     default:
      dlen = subsize-(header_size+8);
      if(dlen>0)
       data_ptr = mp4lib_read_string(f,dlen);
      break;
    }
    ddone=1;
    break;
  }
  if((retcode=f->fbfs->fseek(f->fbds,skipfilepos,SEEK_SET))<0)
   return retcode;
  sumsize += subsize;
 }

 if(data_ptr){
  if(dlen>0)
   mp4lib_tag_add_or_modify_field(&(f->ilst_infos),parent_atom_type,parent_atom_name,name_ptr,data_ptr,dlen,dflags);
  free(data_ptr);
 }
 if(name_ptr)
  free(name_ptr);

 if((retcode=f->fbfs->fseek(f->fbds,endoftag,SEEK_SET))<0)
  return retcode;

 return 1;
}

static mpxp_int32_t mp4lib_tagging_read_ilst_atom(mp4lib_main_info_t *f, const mpxp_int32_t size)
{
 mpxp_uint8_t atom_type,header_size;
 mpxp_uint32_t atom_name;
 mpxp_uint64_t subsize, sumsize = 0;

 while(sumsize < size){
  subsize = mp4lib_atom_read_header(f, &atom_type, &header_size, &atom_name);
  if(!subsize)
   break;
  if(subsize>header_size)
   mp4lib_parse_tag(f, atom_type, atom_name, (mpxp_uint32_t)(subsize-header_size));
  sumsize += subsize;
 }
 return 0;
}

static mpxp_int32_t mp4lib_tagging_read_meta_atom(mp4lib_main_info_t *f, const mpxp_uint64_t size)
{
 mpxp_uint8_t atom_type,header_size=0;
 mpxp_uint32_t atom_name;
 mpxp_uint64_t subsize,sumsize=0;
 mpxp_filesize_t skipfilepos;

 mp4lib_read_int32(f); // version (1) & flags (3)

 while(sumsize < (size-(header_size+4))){
  subsize = mp4lib_atom_read_header(f, &atom_type, &header_size, &atom_name);
  if(subsize <= header_size+4)
   return 1;
  skipfilepos=f->fbfs->ftell(f->fbds)+subsize-header_size;

  if(f->openmode&MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD)
   if(!mp4lib_atomlist_add_atom(f,&f->meta_subatoms_list,atom_name,subsize-header_size,NULL,((atom_type==ATOM_ILST)? 0:1)))
    return MPXPLAY_ERROR_INFILE_MEMORY;

  if(atom_type==ATOM_ILST)
   mp4lib_tagging_read_ilst_atom(f, (mpxp_uint32_t)(subsize-(header_size+4)));

  if(f->fbfs->fseek(f->fbds,skipfilepos,SEEK_SET)!=skipfilepos)
   return 1;

  sumsize += subsize;
 }

 return 0;
}

static char *mp4lib_tagging_getdata_by_atom(mp4lib_main_info_t *f,mpxp_uint32_t atom,mpxp_uint32_t *data_flags)
{
 mp4lib_ilst_info_t *tags=&f->ilst_infos;
 mpxp_uint32_t i=tags->count;
 mp4lib_onetag_info_t *tagp=tags->tags;

 if(tagp && i){
  do{
   if(tagp->atom_num==atom){
    if(data_flags)
     *data_flags=tagp->data_flags;
    return tagp->data;
   }
   tagp++;
  }while(--i);
 }
 return NULL;
}

static mpxp_int32_t mp4lib_tagging_setdata_by_atom(mp4lib_main_info_t *f,mpxp_uint32_t atom_num, char *data, unsigned int data_size)
{
 mpxp_uint32_t atom_name,data_flags=0; // binary data type
 switch(atom_num){
  //case ATOM_GENRE1: // not used in Mpxplay
  case ATOM_GENRE2:
   if(mpxplay_tagging_id3v1_genre_to_index(data)<255){
    if(mp4lib_tagging_getdata_by_atom(f,ATOM_GENRE1,NULL))
     mp4lib_tag_add_or_modify_field(&f->ilst_infos,ATOM_GENRE1,0,NULL,NULL,0,0);
   }else{
    if(mp4lib_tagging_getdata_by_atom(f,ATOM_GENRE2,NULL))
     mp4lib_tag_add_or_modify_field(&f->ilst_infos,ATOM_GENRE2,0,NULL,NULL,0,0);
    atom_num=ATOM_GENRE1;
    data_flags=1; // UTF8
   }
   break;
  //case ATOM_TEMPO:
  //case ATOM_DISC:
  case ATOM_TRACK:break;
  default:data_flags=1; // UTF8 data type
 }
 atom_name=mp4lib_atom_type_to_name(atom_num);
 return mp4lib_tag_add_or_modify_field(&f->ilst_infos,atom_num,atom_name,NULL,data,data_size,data_flags);
}

static mpxp_int32_t mp4lib_tagging_create_ilst(mp4lib_main_info_t *f,mpxp_uint32_t writetag_control,mpxp_uint32_t atomsize_ilst_old)
{
 mp4lib_ilst_info_t *tags=&f->ilst_infos;
 mp4lib_onetag_info_t *tagp=tags->tags;
 mpxp_uint32_t i=tags->count,atomsize_ilst_new,atomsize_lasttag=0,atomsize_ilst_count;
 mpxp_uint8_t *p,*s;

 if(!tagp)
  return MPXPLAY_ERROR_INFILE_MEMORY;
 atomsize_ilst_new=0;
 if(!i)
  goto mtci_skip_datas;
 do{
  if(tagp->data){
   if(tagp->atom_num==ATOM_UNKNOWN){
    if(!(writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS))
     atomsize_ilst_new+=4+4+tagp->data_size;
   }else{
    atomsize_lasttag=4+4; // atom_tag_size, atom_tag_name
    if(tagp->name)
     atomsize_lasttag+=4+4+4+pds_strlen(tagp->name); // atom_size, ATOM_NAME, flags
    atomsize_lasttag+=4+4+4+4; // atom_size, ATOM_DATA, flags, reserved
    switch(tagp->atom_num){
     case ATOM_GENRE2:
     case ATOM_TEMPO:atomsize_lasttag+=2;break;
     case ATOM_TRACK:
     case ATOM_DISC:atomsize_lasttag+=2+2+2+2;break;
     default:atomsize_lasttag+=tagp->data_size;
    }
    if((writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS) && ((atomsize_ilst_new+atomsize_lasttag+8)>atomsize_ilst_old)){
     if(!atomsize_ilst_new) // nothing to trim
      return MPXPLAY_ERROR_INFILE_WRITETAG_NOSPMMRQ; // !!!
     break;
    }
    atomsize_ilst_new+=atomsize_lasttag;
   }
  }
  tagp++;
 }while(--i);

mtci_skip_datas:
 if(!atomsize_ilst_new)
  return atomsize_ilst_new;

 f->new_atomfield_data=p=malloc(atomsize_ilst_new+32);
 if(!p)
  return MPXPLAY_ERROR_INFILE_MEMORY;
 f->new_atomfield_size=atomsize_ilst_new;

 // ilst atom header is not included here (added in create_meta)

 atomsize_ilst_count=0;
 i=tags->count;
 tagp=tags->tags;

 do{
  if(tagp->data){
   if(tagp->atom_num==ATOM_UNKNOWN){
    if(!(writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS)){ // !!! we drop all unknown tags at TrimTags
     PDS_PUTB_BEU32(&p[0],4+4+tagp->data_size);
     PDS_PUTB_LE32(&p[4],tagp->atom_name);
     p+=8;
     if(tagp->data_size){
      if(tagp->data)
       pds_memcpy(p,tagp->data,tagp->data_size);
      p+=tagp->data_size;
     }
    }
   }else{
    mpxp_uint8_t *sizep=p;
    unsigned int nsize=0,dsize;
    PDS_PUTB_LE32(&p[4],tagp->atom_name);
    p+=8;
    if(tagp->name){
     PDS_PUTB_LE32(&p[4],PDS_GET4C_LE32('n','a','m','e'));
     PDS_PUTB_LE32(&p[8],0); // version (1) flags (3)
     nsize=pds_strcpy(&p[12],tagp->name);
     nsize+=4+4+4; // nsize, atom_name, flags
     PDS_PUTB_BEU32(&p[0],nsize);
     p+=nsize;
    }
    PDS_PUTB_LE32(&p[4],PDS_GET4C_LE32('d','a','t','a'));
    PDS_PUTB_BEU32(&p[8],tagp->data_flags);  // version (1) flags (3)
    PDS_PUTB_LE32(&p[12],0); // reserved
    switch(tagp->atom_num){
     case ATOM_GENRE2:
      {
       unsigned int iv1i=mpxplay_tagging_id3v1_genre_to_index(tagp->data);
       if(iv1i<255)
        iv1i++;
       else
        iv1i=0;
       PDS_PUTB_BEU16(&p[16],iv1i);
       dsize=2;
       break;
      }
     case ATOM_TEMPO:PDS_PUTB_BEU16(&p[16],pds_atol(tagp->data));dsize=2;break;
     case ATOM_TRACK:
     case ATOM_DISC:
       PDS_PUTB_BEU16(&p[16],0);
       PDS_PUTB_BEU16(&p[18],pds_atol(tagp->data));
       s=pds_strchr(tagp->data,'/');
       PDS_PUTB_BEU16(&p[20],((s)? pds_atol(s+1):0));
       PDS_PUTB_BEU16(&p[22],0);
       dsize=2+2+2+2;
       break;
     default:
      dsize=tagp->data_size;
      if(dsize)
       pds_memcpy(&p[16],tagp->data,dsize);
    }
    dsize+=4+4+4+4; // dsize, atom_data, flags, reserved
    PDS_PUTB_BEU32(&p[0],dsize);
    p+=dsize;
    PDS_PUTB_BEU32(sizep,4+4+nsize+dsize);
    if(writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS){
     atomsize_ilst_count+=4+4+nsize+dsize;
     if(atomsize_ilst_count>=atomsize_ilst_new)
      break;
    }
   }
  }
  tagp++;
 }while(--i);

 return atomsize_ilst_new;
}

static mpxp_int32_t mp4lib_tagging_create_meta_atom(mp4lib_main_info_t *f,mpxp_uint32_t writetag_control,mpxp_uint32_t atomsize_meta_old,unsigned int head_prepadding)
{
 mp4lib_atomlist_info_t *al=&f->meta_subatoms_list;
 mp4lib_oneatom_info_t *ads;
 mpxp_int32_t atomsize_ilst,atomsize_meta_new,atomsize_meta_chg,i;
 mpxp_uint8_t *atomfield_meta,*p;

 ads=mp4lib_atomlist_search_atom(al,PDS_GET4C_LE32('i','l','s','t'));
 if(ads)
  atomsize_ilst=mp4lib_tagging_create_ilst(f,writetag_control,ads->atom_size);
 else
  atomsize_ilst=mp4lib_tagging_create_ilst(f,0,0);
 if(atomsize_ilst<0)
  return atomsize_ilst;

 if(ads)
  mp4lib_atomlist_modify_atom(al,ads,atomsize_ilst,f->new_atomfield_data);
 else
  mp4lib_atomlist_add_atom(f,al,PDS_GET4C_LE32('i','l','s','t'),atomsize_ilst,f->new_atomfield_data,0);

 atomsize_meta_new=12+al->atoms_totalsize;
 atomsize_meta_chg=atomsize_meta_new-atomsize_meta_old;

 if(atomsize_meta_chg!=0){ // add or modify padding in a FREE atom
  mpxp_int32_t atomsize_free;
  ads=mp4lib_atomlist_search_atom(al,PDS_GET4C_LE32('f','r','e','e'));
  if(ads)
   atomsize_free=((long)ads->atom_size>atomsize_meta_chg)? (ads->atom_size-atomsize_meta_chg):1024;
  else{
   atomsize_free=(atomsize_meta_chg<=-8)? (-atomsize_meta_chg-8):1024;
   ads=mp4lib_atomlist_add_atom(f,al,PDS_GET4C_LE32('f','r','e','e'),atomsize_free,NULL,0);
   if(!ads)
    return MPXPLAY_ERROR_INFILE_MEMORY;
  }
  mp4lib_atomlist_modify_atom(al,ads,atomsize_free,NULL);
 }

 atomsize_meta_new=head_prepadding+12+al->atoms_totalsize;
 atomfield_meta=p=malloc(atomsize_meta_new+32);
 if(!atomfield_meta)
  return MPXPLAY_ERROR_INFILE_MEMORY;

 if(f->new_atomfield_data)
  free(f->new_atomfield_data);
 f->new_atomfield_data=atomfield_meta;
 f->new_atomfield_size=atomsize_meta_new;

 p+=head_prepadding;
 PDS_PUTB_BEU32(&p[0],atomsize_meta_new);
 PDS_PUTB_LE32( &p[4],PDS_GET4C_LE32('m','e','t','a'));
 PDS_PUTB_LE32( &p[8],0); // version (1) flags (3)
 p+=12;
 ads=al->atom_datas;
 i=al->atoms_loaded;
 do{
  PDS_PUTB_BEU32(&p[0],(8+ads->atom_size));
  PDS_PUTB_LE32( &p[4],ads->atom_name);
  if(ads->atom_size)
   if(ads->atom_data)
    pds_memcpy(&p[8],ads->atom_data,ads->atom_size);
   else
    return MPXPLAY_ERROR_INFILE_MEMORY; // should not happen
  p+=8+ads->atom_size;
  ads++;
 }while(--i);

 return atomsize_meta_new;
}

static mpxp_int32_t mp4lib_tagging_create_udta_atom(mp4lib_main_info_t *f)
{
 mpxp_int32_t atomsize_udta;
 atomsize_udta=mp4lib_tagging_create_meta_atom(f,0,0,8);
 if(atomsize_udta<=0)
  return atomsize_udta;
 PDS_PUTB_BEU32(&f->new_atomfield_data[0],atomsize_udta);
 PDS_PUTB_LE32( &f->new_atomfield_data[4],PDS_GET4C_LE32('u','d','t','a'));
 return atomsize_udta;
}

//---------------------------------------------------------------------
// sample/stream handling/reading

static mpxp_int32_t mp4lib_chunk_of_sample(const mp4lib_main_info_t *f, const mpxp_int32_t track, const mpxp_int32_t sample,
                                     mpxp_int32_t *chunk_sample, mpxp_int32_t *chunk)
{
 const mp4lib_track_info_t *p_track = f->track[track];
 mpxp_int32_t total_entries = 0;
 mpxp_int32_t chunk2entry;
 mpxp_int32_t chunk1, chunk2, chunk1samples, range_samples, total = 0;

 if(!p_track)
  return -1;

 total_entries = p_track->stsc_entry_count;

 chunk1 = 1;
 chunk1samples = 0;
 chunk2entry = 0;

 do{
  chunk2 = p_track->stsc_infos[chunk2entry].first_chunk;
  *chunk = chunk2 - chunk1;
  range_samples = *chunk * chunk1samples;

  if (sample < total + range_samples)
   break;

  chunk1samples = p_track->stsc_infos[chunk2entry].samples_per_chunk;
  chunk1 = chunk2;

  if(chunk2entry < total_entries){
   chunk2entry++;
   total += range_samples;
  }
 }while(chunk2entry < total_entries);

 if(chunk1samples)
  *chunk = (sample - total) / chunk1samples + chunk1;
 else
  *chunk = 1;

 *chunk_sample = total + (*chunk - chunk1) * chunk1samples;

 return 0;
}

static mpxp_filesize_t mp4lib_chunk_to_offset(const mp4lib_main_info_t *f, const mpxp_int32_t track, const mpxp_int32_t chunk)
{
 const mp4lib_track_info_t *p_track = f->track[track];

 if(p_track->stco_entry_count && (chunk>p_track->stco_entry_count))
  return p_track->stco_chunk_offset[p_track->stco_entry_count - 1];
 if(p_track->stco_entry_count)
  return p_track->stco_chunk_offset[chunk - 1];
 return 0;
}

static mpxp_filesize_t mp4lib_sample_range_size(const mp4lib_main_info_t *f, const mpxp_int32_t track,
                                       const mpxp_int32_t chunk_sample, const mpxp_int32_t sample)
{
 const mp4lib_track_info_t *p_track = f->track[track];
 mpxp_int32_t i;
 mpxp_filesize_t total;

 if(p_track->stsz_sample_size)
  return (((mpxp_filesize_t)sample - (mpxp_filesize_t)chunk_sample) * (mpxp_filesize_t)p_track->stsz_sample_size);
 if(sample>=p_track->stsz_sample_count) //error
  return 0;
 for(i=chunk_sample, total=0; i<sample; i++)
  total += (mpxp_filesize_t)p_track->stsz_table[i];
 //fprintf(stdout,"cs:%d s:%d ss:%d sc:%d t:%"PRIi64"\n",chunk_sample,sample,p_track->stsz_sample_size,p_track->stsz_sample_count,(mpxp_int64_t)total);
 return total;
}

static mpxp_filesize_t mp4lib_set_sample_position(mp4lib_main_info_t *f, void *fbds, const mpxp_int32_t track, const mpxp_int32_t sample)
{
 mpxp_int32_t chunk,chunk_sample;
 mpxp_filesize_t chunk_offset1,newfilepos;

 f->fbds=fbds; // !!!
 mp4lib_chunk_of_sample(f, track, sample, &chunk_sample, &chunk);
 chunk_offset1 = mp4lib_chunk_to_offset(f, track, chunk);
 newfilepos = chunk_offset1 + mp4lib_sample_range_size(f, track, chunk_sample, sample);
 //fprintf(stdout,"co1:%"PRIi64" co2:%"PRIi64"\n",chunk_offset1,(mpxp_int64_t)newfilepos);
 return f->fbfs->fseek(f->fbds,newfilepos,SEEK_SET);
}

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

#define MP4_AUDIO_BSBUF_SIZE 32768 // have to be enough

typedef struct mp4_stream_data_s{
 int  tracknum;
 int  tracktype;
 long max_framesize;
 mpxp_uint8_t *extradata;
 long extradata_size;
}mp4_stream_data_s;

typedef struct mp4_demuxer_data_s{
 mp4lib_main_info_t *infile;
 int  nb_tracks;
 int  track_audio;
 long numSamples;
 long sampleId;

 struct mp4_stream_data_s *streams;
 struct mp4_stream_data_s *stream_audio; // selected
 struct mp4_stream_data_s *stream_video; //
}mp4_demuxer_data_s;

static int mp4_get_tracknum(mp4lib_main_info_t *infile,unsigned int track_type,struct mpxplay_streampacket_info_s *spi)
{
 int numTracks,tracknum=-1;
 unsigned int i,tracktypecount=0;

 numTracks = mp4lib_total_tracks(infile);
 if(numTracks<=0)
  return tracknum;

 for(i=0; i<numTracks; i++){
  if(mp4lib_get_track_type(infile,i)==track_type){
   if(tracktypecount<=spi->stream_select)
    tracknum=i;
   tracktypecount++;
  }
 }

 spi->nb_streams=tracktypecount;

 return tracknum;
}

static unsigned int mp4_audiotype_to_waveid(unsigned int audiotype, struct mpxplay_audio_decoder_info_s *adi)
{
 unsigned int waveid=MPXPLAY_WAVEID_UNKNOWN;

 if(MP4_IS_AAC_AUDIO_TYPE(audiotype))
  waveid=MPXPLAY_WAVEID_AAC;
 else
  if(MP4_IS_MP3_AUDIO_TYPE(audiotype))
   waveid=MPXPLAY_WAVEID_MP3;
  else{
   switch(audiotype){ // private, unofficial types
    case MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE: waveid=MPXPLAY_WAVEID_PCM_SLE;adi->bits=16;break;
    case MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE: waveid=MPXPLAY_WAVEID_PCM_SBE;adi->bits=16;break;
    case MP4_VORBIS_AUDIO_TYPE: waveid=MPXPLAY_WAVEID_VORBIS;break;
    case MP4_AC3_AUDIO_TYPE: waveid=MPXPLAY_WAVEID_AC3;break;
    case MP4_ALAC_AUDIO_TYPE: waveid=MPXPLAY_WAVEID_ALAC;break;
   }
  }

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"at:%d wid:%8.8X",audiotype,waveid);

 return waveid;
}

static int MP4_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct mp4_demuxer_data_s *mp4i=NULL;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 long pcmdatalen,bytes;
 unsigned int i;

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

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

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

 mp4i->infile=mp4lib_open_read(fbfs,fbds,openmode);
 if(!mp4i->infile)
  goto err_out_open;

 mp4i->nb_tracks=mp4lib_total_tracks(mp4i->infile);
 if(mp4i->nb_tracks<1)
  goto err_out_open;

 mp4i->track_audio=mp4_get_tracknum(mp4i->infile,TRACK_AUDIO,spi);
 if(mp4i->track_audio<0)
  goto err_out_open;

 spi->streamtype=MPXPLAY_SPI_STREAMTYPE_AUDIO;
 spi->wave_id=mp4_audiotype_to_waveid(mp4lib_get_audio_type(mp4i->infile,mp4i->track_audio),adi);
 if(!spi->wave_id){
  spi->wave_id=mp4lib_get_audio_waveid(mp4i->infile,mp4i->track_audio);
  if(!spi->wave_id)
   goto err_out_open;
 }

 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  mp4lib_merge_small_pcmframes(mp4i->infile,mp4i->track_audio);
  funcbit_smp_value_put(mp4i->numSamples,mp4lib_num_samples(mp4i->infile,mp4i->track_audio));
  if(!mp4i->numSamples)
   goto err_out_open;
 }

 adi->freq=mp4lib_get_audio_freq(mp4i->infile,mp4i->track_audio);
 if(!adi->freq)
  goto err_out_open;
 adi->filechannels=adi->outchannels=mp4lib_get_audio_channels(mp4i->infile,mp4i->track_audio);
 if(!adi->filechannels)
  goto err_out_open;
 adi->bits=mp4lib_get_audio_bits(mp4i->infile,mp4i->track_audio); // at lossless formats only

 pcmdatalen=mp4lib_get_track_duration(mp4i->infile,mp4i->track_audio);
 miis->timemsec=(long)(1000.0*(float)pcmdatalen/(float)adi->freq);
 adi->bitrate=(mp4lib_get_track_bitrate(mp4i->infile,mp4i->track_audio)+500)/1000;

 if(spi->wave_id==MPXPLAY_WAVEID_ALAC){
  miis->longname="MP4/ALAC";
  adi->bitratetext=malloc(MPXPLAY_ADITEXTSIZE_BITRATE+8);
  if(adi->bitratetext){
   sprintf(adi->bitratetext,"%2d/%2.1f%%",adi->bits,
    100.0*(float)miis->filesize/((float)pcmdatalen*(adi->bits/8)*adi->filechannels));
  }
 }

 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  if(adi->infobits&ADI_CNTRLBIT_BITSTREAMOUT){
   switch(spi->wave_id){
    case MPXPLAY_WAVEID_AAC:
    case MPXPLAY_WAVEID_AC3:
    case MPXPLAY_WAVEID_MP3:
     miis->allframes=mp4i->numSamples;
     adi->infobits|=ADI_FLAG_BITSTREAMOUT|ADI_FLAG_BITSTREAMNOFRH;
   }
  }

  mp4i->streams=(struct mp4_stream_data_s *)calloc(mp4i->nb_tracks,sizeof(struct mp4_stream_data_s));
  if(!mp4i->streams)
   goto err_out_open;

  for(i=0;i<mp4i->nb_tracks;i++){
   struct mp4_stream_data_s *msd=&mp4i->streams[i];

   msd->tracknum=i;
   msd->max_framesize=mp4lib_read_sample_maxsize(mp4i->infile,i);

   bytes=mp4lib_get_decoder_config_size(mp4i->infile,i);
   if(bytes>0){
    msd->extradata=(mpxp_uint8_t *)malloc(bytes+MPXPLAY_SPI_EXTRADATA_PADDING);
    if(!msd->extradata)
     goto err_out_open;
    bytes=mp4lib_get_decoder_config_v2(mp4i->infile,i,msd->extradata,bytes);
    if(bytes<0)
     goto err_out_open;
    pds_memset(msd->extradata+bytes,0,MPXPLAY_SPI_EXTRADATA_PADDING);
    msd->extradata_size=bytes;
   }
  }

  mp4i->stream_audio=&mp4i->streams[mp4i->track_audio];
  spi=miis->audio_stream;
  spi->extradata=mp4i->stream_audio->extradata;
  spi->extradata_size=mp4i->stream_audio->extradata_size;
  spi->bs_framesize=mp4i->stream_audio->max_framesize;
  funcbit_enable(spi->flags,(MPXPLAY_SPI_FLAG_NEED_DECODER|MPXPLAY_SPI_FLAG_NEED_PARSING|MPXPLAY_SPI_FLAG_CONTAINER));
 }

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"fs:%d ns:%d",spi->bs_framesize,mp4i->numSamples);

 return MPXPLAY_ERROR_INFILE_OK;

err_out_open:
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%8.8X nbt:%d ta:%d tt0:%d tt1:%d wid:%8.8X",mp4i->infile,mp4i->nb_tracks,
  (int)mp4i->track_audio,(int)mp4lib_get_track_type(mp4i->infile,0),(int)mp4lib_get_track_type(mp4i->infile,1),spi->wave_id);
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void MP4_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct mp4_demuxer_data_s *mp4i=(mp4_demuxer_data_s *)miis->private_data;
 struct mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 unsigned int i;

 if(mp4i){
  if(mp4i->infile)
   mp4lib_close(mp4i->infile);
  if(mp4i->streams){
   for(i=0;i<mp4i->nb_tracks;i++)
    if(mp4i->streams[i].extradata)
     free(mp4i->streams[i].extradata);
   free(mp4i->streams);
  }
  if(adi->bitratetext)
   free(adi->bitratetext);
  free(mp4i);
 }
 fbfs->fclose(fbds);
}

static int MP4_infile_demux(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct mp4_demuxer_data_s *mp4i=(mp4_demuxer_data_s *)miis->private_data;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 long bsbytes,readbytes;//,i;
 //unsigned char *p;
 mpxp_filesize_t filepos;

 if(!mp4i)
  return MPXPLAY_ERROR_INFILE_EOF;

 if(mp4i->sampleId>=mp4i->numSamples)
  return MPXPLAY_ERROR_INFILE_EOF;

 filepos=mp4lib_set_sample_position(mp4i->infile,fbds,mp4i->track_audio,mp4i->sampleId);
 if(filepos<0){        // failed seek
  if(filepos==MPXPLAY_ERROR_MPXINBUF_SEEK_EOF){ // seekpoint is out of file
   funcbit_smp_value_increment(mp4i->sampleId); // seek to next frame
  }                    // else seekpoint is out of mpxbuffer
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"mp4 seek error");
  return MPXPLAY_ERROR_INFILE_RESYNC; //
 }

 bsbytes=mp4lib_read_sample_getsize(mp4i->infile,mp4i->track_audio,mp4i->sampleId);
 if((bsbytes<1) || (bsbytes>mp4i->stream_audio->max_framesize)){
  funcbit_smp_value_increment(mp4i->sampleId);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"mp4 sync error");
  return MPXPLAY_ERROR_INFILE_SYNC_IN;
 }

 readbytes = mp4lib_read_sample_v2(mp4i->infile,mp4i->track_audio,mp4i->sampleId,spi->bitstreambuf);
 if(readbytes<bsbytes){
  mp4lib_set_sample_position(mp4i->infile,fbds,mp4i->track_audio,mp4i->sampleId); // rewind datapos in buffer if we cannot read enough bytes
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"mp4 read error");
  return MPXPLAY_ERROR_INFILE_NODATA;
 }

 /*mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"i:%d fp:%"PRIi64" bs:%d rb:%d",mp4i->sampleId,(mpxp_int64_t)filepos,bsbytes,readbytes);
 p=spi->bitstreambuf;
 for(i=0;i<bsbytes;i++){
  fprintf(stdout,"%2.2X",(unsigned int)p[0]);
  p++;
  if(i && !(i&31))
   fprintf(stdout,"\n");
 }
 fprintf(stdout,"\n");*/

 spi->bs_leftbytes=readbytes;

 funcbit_smp_value_increment(mp4i->sampleId);

 return MPXPLAY_ERROR_INFILE_OK;
}

static long MP4_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
 struct mp4_demuxer_data_s *mp4i=(mp4_demuxer_data_s *)miis->private_data;

 if(mp4i){
  funcbit_smp_value_put(mp4i->sampleId,(long)((float)mp4i->numSamples*(float)newmpxframenum/(float)miis->allframes));
  if(mp4i->sampleId<mp4i->numSamples)
   if(mp4lib_set_sample_position(mp4i->infile,fbds,mp4i->track_audio,mp4i->sampleId)>=0)
    return newmpxframenum;
 }

 return MPXPLAY_ERROR_INFILE_EOF;
}

#define MP4_COMMENT_TYPES_GET 8
#define MP4_COMMENT_TYPES_PUT 7
static mpxp_uint8_t tag_mp4atoms[MP4_COMMENT_TYPES_GET]={ATOM_TITLE,ATOM_ARTIST,ATOM_ALBUM,ATOM_DATE,ATOM_COMMENT,ATOM_TRACK  ,ATOM_GENRE2,ATOM_GENRE1};
static mpxp_uint8_t tag_id3index[MP4_COMMENT_TYPES_GET]={I3I_TITLE ,I3I_ARTIST ,I3I_ALBUM ,I3I_YEAR ,I3I_COMMENT ,I3I_TRACKNUM,I3I_GENRE  ,I3I_GENRE};

static int MP4_infile_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct mp4_demuxer_data_s *mp4i=(mp4_demuxer_data_s *)miis->private_data;
 mpxp_uint32_t data_flags,srctexttype;
 long i,len;

 if(!mp4i)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;

 for(i=0;i<MP4_COMMENT_TYPES_GET;i++){
  char *tag=mp4lib_tagging_getdata_by_atom(mp4i->infile,tag_mp4atoms[i],&data_flags);
  if(tag){
   switch(data_flags){
    case 0: // binary handled like utf8
    case 1:srctexttype=MPXPLAY_TEXTCONV_TYPE_UTF8;break;
    case 2:srctexttype=MPXPLAY_TEXTCONV_TYPE_UTF16BE;break; // ??? BE or LE?
    default:continue;
   }
   len=-1;
   miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PUT,srctexttype,tag_id3index[i]),tag,&len);
  }
 }

 return MPXPLAY_ERROR_INFILE_OK;
}

static int MP4_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 mp4_demuxer_data_s *mp4i=NULL;
 mp4lib_main_info_t *f;
 mp4lib_atompath_info_t *ap;
 mp4lib_atomlevel_info_t *app,*apmoov;
 void *tmpfile_filehand=NULL;
 int retcode=MPXPLAY_ERROR_INFILE_CANTOPEN,i,taglen;
 char *tagptr,*copybuf=NULL;
 mpxp_int32_t atomoldsize,atomnewsize,skipsize,atomsizechg,atom_correctlevels;
 mpxp_uint32_t openmode;
 mpxp_filesize_t filesize,insertpos;
 mpxp_int64_t filelen;

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

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

 mp4i=(struct mp4_demuxer_data_s *)calloc(1,sizeof(struct mp4_demuxer_data_s));
 if(!mp4i)
  goto err_out_mtp;
 miis->private_data=mp4i;

 openmode=MPXPLAY_INFILE_OPENMODE_INFO_ID3|MPXPLAY_INFILE_OPENMODE_LOAD_FULLHEAD;
 if(writetag_control&MPXPLAY_WRITETAG_CNTRL_DUPFILE)
  openmode|=MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB;
 mp4i->infile=f=mp4lib_open_read(fbfs,fbds,openmode);
 if(!mp4i->infile)
  goto err_out_mtp;

 if(!f->atompath_infos)
  goto err_out_mtp;

 for(i=0;i<MP4_COMMENT_TYPES_PUT;i++){
  tagptr=NULL;taglen=0;
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_GET,MPXPLAY_TEXTCONV_TYPE_UTF8,tag_id3index[i]),&tagptr,&taglen);
  mp4lib_tagging_setdata_by_atom(mp4i->infile,tag_mp4atoms[i],tagptr,taglen);
  if(tagptr)
   free(tagptr);
 }

 insertpos=0;
 atomoldsize=0;
 ap=&f->atompath_infos[0];
 for(i=0;i<MP4LIB_ATOMPATHINDEX_NUM;i++,ap++){
  if(ap->atompath && ap->atomlevels){
   insertpos=ap->atompath[ap->atomlevels-1].atom_filepos;
   atomoldsize=ap->atompath[ap->atomlevels-1].atom_oldsize;
   break;
  }
 }
 if(!insertpos && !atomoldsize) // MOOV atom can start on filepos 0, UDTA,META size can be 0
  goto err_out_mtp;

 switch(i){
  case MP4LIB_ATOMPATHINDEX_META:atomnewsize=mp4lib_tagging_create_meta_atom(f,writetag_control,atomoldsize,0);break;
  case MP4LIB_ATOMPATHINDEX_UDTA:atomnewsize=mp4lib_tagging_create_meta_atom(f,0,0,0);break;
  case MP4LIB_ATOMPATHINDEX_MOOV:atomnewsize=mp4lib_tagging_create_udta_atom(f);break;
  default:goto err_out_mtp;
 }
 if(atomnewsize<=0){
  retcode=atomnewsize;
  goto err_out_mtp;
 }
 if(i==MP4LIB_ATOMPATHINDEX_META){
  skipsize=atomoldsize;
  atom_correctlevels=ap->atomlevels-1;
  atomsizechg=atomnewsize-atomoldsize;
 }else{
  insertpos+=atomoldsize;
  skipsize=0;
  atom_correctlevels=ap->atomlevels;
  atomsizechg=atomnewsize;
 }

 if((((insertpos+skipsize)>=filesize) || (atomsizechg==0)) // append or no-tagsize-change (no file duplication or meta-moov required)
#ifdef MPXPLAY_FSIZE64
  && ((filesize<0x7fffffff) || (atomsizechg>=0))
#endif
 ){
  fbfs->fclose(fbds);
  if(!fbfs->fopen(fbds,filename,O_RDWR|O_BINARY,0))
   goto err_out_mtp;
  if(fbfs->fseek(fbds,insertpos,SEEK_SET)!=insertpos)
   goto err_out_mtp;
  if(fbfs->fwrite(fbds,f->new_atomfield_data,atomnewsize)!=atomnewsize)
   goto err_out_write;
  if(atomsizechg!=0){
   app=&ap->atompath[0]; // correct parent atom headers/sizes
   for(i=0;i<atom_correctlevels;i++,app++){
    if(fbfs->fseek(fbds,app->atom_filepos,SEEK_SET)!=app->atom_filepos)
     goto err_out_write;
    fbfs->put_be32(fbds,((mpxp_int64_t)app->atom_oldsize+atomsizechg));
   }
   insertpos+=atomnewsize;
   if(insertpos<filesize)
    if(fbfs->chsize(fbds,insertpos)<=0) // trim file
     goto err_out_mtp;
  }
 }else if(writetag_control&MPXPLAY_WRITETAG_CNTRL_DUPFILE){ // duplicate file
  mp4lib_track_info_t **trackp;
  unsigned int t;
  if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPOPEN,&tmpfile_filehand,NULL))!=MPXPLAY_ERROR_OK)
   goto err_out_write;
  if((retcode=fbfs->fseek(fbds,0,SEEK_SET))!=0)
   goto err_out_write;
  filelen=insertpos;
  if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&filelen))!=MPXPLAY_ERROR_OK)
   goto err_out_write;
  if((retcode=miis->control_cb(tmpfile_filehand,MPXPLAY_CFGFUNCNUM_INFILE_FILE_WRITE_BYTES,f->new_atomfield_data,&atomnewsize))!=atomnewsize)
   goto err_out_write;
  if(skipsize)
   if((retcode=fbfs->fseek(fbds,(insertpos+skipsize),SEEK_SET))!=(insertpos+skipsize))
    goto err_out_write;
  filelen=-1;
  if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&filelen))!=MPXPLAY_ERROR_OK)
   goto err_out_write;

  // correct atom headers, stco entries
  f->fbds=tmpfile_filehand; // !!! hack
  app=&ap->atompath[0]; // correct parent atom headers of META atom
  for(i=0;i<atom_correctlevels;i++,app++){
   if(fbfs->fseek(tmpfile_filehand,app->atom_filepos,SEEK_SET)!=app->atom_filepos)
    goto err_out_write;
   fbfs->put_be32(tmpfile_filehand,((mpxp_int64_t)app->atom_oldsize+atomsizechg));
  }
  ap=&f->atompath_infos[MP4LIB_ATOMPATHINDEX_NUM];
  trackp=&f->track[0];
  for(t=0;t<f->total_tracks;t++,ap++,trackp++){ // stco correction for every tracks
   if(ap->atompath && ap->atomlevels){
    app=&ap->atompath[ap->atomlevels-1];
    if(app->atom_filepos>insertpos){ // correct parent atom headers of STCO atom
     app=&ap->atompath[0];
     for(i=0;i<(ap->atomlevels-1);i++,app++){
      if(fbfs->fseek(tmpfile_filehand,app->atom_filepos,SEEK_SET)!=app->atom_filepos)
       goto err_out_write;
      fbfs->put_be32(tmpfile_filehand,((mpxp_int64_t)app->atom_oldsize+atomsizechg));
     }
    }
    if((*trackp)->stco_chunk_offset[0]>insertpos){ // correct stco entries (chunk offsets)
     if(fbfs->fseek(tmpfile_filehand,(app->atom_filepos+16),SEEK_SET)!=(app->atom_filepos+16)) // skip stco header
      goto err_out_write;
     mp4lib_write_stco_entries(f,app->atom_name,t,atomsizechg);
    }
   }
  }
  if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPXCHCLOSE,&tmpfile_filehand,NULL))!=MPXPLAY_ERROR_OK)
   goto err_out_write;

 }else{ // move MOOV atom to the end of file (if required)
  unsigned int moov_at_eof; // is moov atom at the end of file already?
  apmoov=&f->atompath_infos[MP4LIB_ATOMPATHINDEX_MOOV].atompath[0];
  filelen=insertpos-apmoov->atom_filepos; // 1st block (before moov)
  if((apmoov->atom_filepos+apmoov->atom_oldsize)<filesize){ // move complete moov atom to the end of file
   if(!(writetag_control&MPXPLAY_WRITETAG_CNTRL_MOVEMETA)){
    retcode=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPMMRQ;
    goto err_out_mtp;
   }
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_OPEN|O_WRONLY|O_BINARY,&tmpfile_filehand,filename))!=MPXPLAY_ERROR_OK)
    goto err_out_write;
   if(fbfs->fseek(tmpfile_filehand,filesize,SEEK_SET)!=filesize)
    goto err_out_mtp;
   if(fbfs->fseek(fbds,apmoov->atom_filepos,SEEK_SET)!=apmoov->atom_filepos)
    goto err_out_mtp;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&filelen))!=MPXPLAY_ERROR_OK)
    goto err_out_write;
   if(skipsize){ // skip old meta
    if((retcode=fbfs->fseek(fbds,(insertpos+skipsize),SEEK_SET))!=(insertpos+skipsize))
     goto err_out_write;
    filelen+=skipsize;
   }
   moov_at_eof=0;
  }else{ // insert meta/udta (move only the data behind meta/udta)
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_OPEN|O_WRONLY|O_BINARY,&tmpfile_filehand,filename))!=MPXPLAY_ERROR_OK)
    goto err_out_write;
   filelen+=skipsize;
   if(apmoov->atom_oldsize>filelen){
    filelen=apmoov->atom_oldsize-filelen;
    copybuf=malloc(filelen+8); // !!! can be large
    if(!copybuf){
     retcode=MPXPLAY_ERROR_INFILE_MEMORY;
     goto err_out_mtp;
    }
    if(fbfs->fseek(fbds,insertpos+skipsize,SEEK_SET)!=(insertpos+skipsize))
     goto err_out_mtp;
    if(fbfs->fread(fbds,copybuf,filelen)!=filelen)
     goto err_out_mtp;
   }
   if(fbfs->fseek(tmpfile_filehand,insertpos,SEEK_SET)!=insertpos)
    goto err_out_mtp;
   moov_at_eof=1;
  }
  if(fbfs->fwrite(tmpfile_filehand,f->new_atomfield_data,atomnewsize)!=atomnewsize) // write new meta
   goto err_out_write;
  if(moov_at_eof){
   if(copybuf)
    if(fbfs->fwrite(tmpfile_filehand,copybuf,filelen)!=filelen)
     goto err_out_write;
   //if(atomnewsize<atomoldsize) // should not happen with the padding
   // if(!fbfs->chsize(tmpfile_filehand,(filesize-atomoldsize+atomnewsize)))
   //  goto err_out_mtp;
  }else if(apmoov->atom_oldsize>filelen){
   filelen=apmoov->atom_oldsize-filelen;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&filelen))!=MPXPLAY_ERROR_OK)
    goto err_out_write;
  }
  app=&ap->atompath[0]; // correct parent atom headers of META atom
  for(i=0;i<atom_correctlevels;i++,app++){
   insertpos=filesize+app->atom_filepos-apmoov->atom_filepos;
   if(fbfs->fseek(tmpfile_filehand,insertpos,SEEK_SET)!=insertpos)
    goto err_out_write;
   fbfs->put_be32(tmpfile_filehand,((mpxp_int64_t)app->atom_oldsize+atomsizechg));
  }
  if(!moov_at_eof){ // invalidate old moov atom
   if(fbfs->fseek(tmpfile_filehand,apmoov->atom_filepos+4,SEEK_SET)!=(apmoov->atom_filepos+4))
    goto err_out_mtp;
   fbfs->put_le32(tmpfile_filehand,PDS_GET4C_LE32('f','r','e','e'));
  }
 }
 retcode=MPXPLAY_ERROR_INFILE_OK;
 goto err_out_mtp;

err_out_write:
 if(retcode>=0)
  retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;

err_out_mtp:
 if(tmpfile_filehand)
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_CLOSE,&tmpfile_filehand,NULL);
 if(copybuf)
  free(copybuf);
 MP4_infile_close(fbfs,fbds,miis);
 miis->private_data=NULL;
 return retcode;
}

struct mpxplay_infile_func_s IN_MP4_funcs={
 0,
 NULL,
 NULL,
 &MP4_infile_open,
 &MP4_infile_close,
 &MP4_infile_demux,
 &MP4_infile_fseek,
 NULL,
 &MP4_infile_tag_get,
 &MP4_infile_tag_put,
 NULL,
 {"MP4","M4A","M4B","M4V","MOV","3GP","3G2",NULL}
};

#endif // MPXPLAY_LINK_INFILE_MP4
