/* * shn_reader.cpp * shorten_decoder * * Created by Alex Lagutin on Sun Jul 07 2002. * Copyright (c) 2002 Eckysoft All rights reserved. * */ /* shn.c - main functions for xmms-shn * Copyright (C) 2000-2001 Jason Jordan (shnutils@freeshell.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * $Id: shn.c,v 1.9 2001/12/30 05:12:04 jason Exp $ */ #include #include #include #include #include "shn_reader.h" #define WAVE_RIFF (0x46464952) /* 'RIFF' in little-endian */ #define WAVE_WAVE (0x45564157) /* 'WAVE' in little-endian */ #define WAVE_FMT (0x20746d66) /* ' fmt' in little-endian */ #define WAVE_DATA (0x61746164) /* 'data' in little-endian */ #define WAVE_FORMAT_PCM (0x0001) #define CANONICAL_HEADER_SIZE (44) #define CACHE_SIZE (256*1024) //256K static void init_offset(slong **offset,int nchan,int nblock,int ftype) { slong mean = 0; int chan, i; /* initialise offset */ switch(ftype) { case TYPE_AU1: case TYPE_S8: case TYPE_S16HL: case TYPE_S16LH: case TYPE_ULAW: case TYPE_AU2: case TYPE_AU3: case TYPE_ALAW: mean = 0; break; case TYPE_U8: mean = 0x80; break; case TYPE_U16HL: case TYPE_U16LH: mean = 0x8000; break; default: //fprintf(stderr, "Unknown file type: %d\n", ftype); break; } for(chan = 0; chan < nchan; chan++) for(i = 0; i < nblock; i++) offset[chan][i] = mean; } shn_reader::shn_reader() { init(); pthread_mutex_init(&mRingLock, NULL); pthread_cond_init(&mRunCond, NULL); } shn_reader::~shn_reader() { exit(); pthread_mutex_destroy(&mRingLock); pthread_cond_destroy(&mRunCond); } void shn_reader::init() { mSeekTable = NULL; mSeekTo = -1; mFP = NULL; mFatalError = false; mGoing = false; mEOF = false; memset(&mDecodeState, 0, sizeof(mDecodeState)); memset(&mWAVEHeader, 0, sizeof(mWAVEHeader)); mRing.Init(CACHE_SIZE); } void shn_reader::exit() { if (mGoing) { mGoing = false; pthread_cond_signal(&mRunCond); pthread_join(mThread, NULL); } if (mDecodeState.getbuf) { free(mDecodeState.getbuf); mDecodeState.getbuf = NULL; } if (mDecodeState.writebuf) { free(mDecodeState.writebuf); mDecodeState.writebuf = NULL; } if (mDecodeState.writefub) { free(mDecodeState.writefub); mDecodeState.writefub = NULL; } if (mSeekTable) { free(mSeekTable); mSeekTable = NULL; } if (mFP) { fclose(mFP); mFP = NULL; } } int shn_reader::open(const char *fn, bool should_load_seek_table) { struct stat sz; exit(); init(); if (stat(fn, &sz)) return -1; if (!S_ISREG(sz.st_mode)) return -1; mWAVEHeader.actual_size = (ulong)sz.st_size; mFP = fopen(fn, "r"); if (!mFP) return -1; if (!get_wave_header() || !verify_header()) { fclose(mFP); mFP = NULL; return -1; } if (should_load_seek_table) load_seek_table(fn); return 0; } int shn_reader::go() { if (!mFP) return -1; fseek(mFP, 0, SEEK_SET); mGoing = true; if (pthread_create(&mThread, NULL, (void *(*)(void *))thread_runner, this)) { mGoing = false; return -1; } return 0; } long shn_reader::read(void *buf, long size) { long actread; if (!mGoing) return -2; pthread_mutex_lock(&mRingLock); actread = mRing.ReadData((char *) buf, size); if (!actread) actread = mEOF ? -2 : -1; pthread_mutex_unlock(&mRingLock); if (actread > 0) pthread_cond_signal(&mRunCond); return actread; } float shn_reader::seek(float seconds) { if (!mSeekTable) return -1.0f; mSeekTo = (int) seconds; shn_seek_entry *seek_info = seek_entry_search(mSeekTo * (ulong)mWAVEHeader.samples_per_sec,0, (ulong)(mSeekTableEntries - 1)); ulong sample = uchar_to_ulong_le(seek_info->data); return (float)sample/((float)mWAVEHeader.samples_per_sec); } int shn_reader::shn_get_buffer_block_size(int blocks)//derek { int blk_size = blocks * (mWAVEHeader.bits_per_sample / 8) * mWAVEHeader.channels; if(blk_size > OUT_BUFFER_SIZE) { blk_size = NUM_DEFAULT_BUFFER_BLOCKS * (mWAVEHeader.bits_per_sample / 8) * mWAVEHeader.channels; } return blk_size; } unsigned int shn_reader::shn_get_song_length()//derek { if(mWAVEHeader.length > 0) return (unsigned int)(1000 * mWAVEHeader.length); /* Something failed or just isn't correct */ return (unsigned int)0; } bool shn_reader::file_info(long *size, int *nch, float *rate, float *time, int *samplebits, bool *seekable) { float frate; int frame_size; if (!mFP) return false; if (seekable) *seekable = mSeekTable ? true : false; if (size) *size = mWAVEHeader.actual_size; if (nch) *nch = mWAVEHeader.channels; frame_size = (uint)mWAVEHeader.channels * (uint)mWAVEHeader.bits_per_sample / 8; frate = (float)mWAVEHeader.samples_per_sec; if (rate) *rate = frate; if (time) *time = (float)mWAVEHeader.data_size/((float)frame_size * frate); if (samplebits) *samplebits = mWAVEHeader.bits_per_sample; return true; } void shn_reader::init_decode_state() { if (mDecodeState.getbuf) free(mDecodeState.getbuf); if (mDecodeState.writebuf) free(mDecodeState.writebuf); if (mDecodeState.writefub) free(mDecodeState.writefub); memset(&mDecodeState, 0, sizeof(mDecodeState)); } int shn_reader::get_wave_header() { int version = FORMAT_VERSION; int ftype = TYPE_EOF; char *magic = MAGIC; int internal_ftype; int retval = 1; init_decode_state(); mBytesInHeader = 0; /***********************/ /* EXTRACT starts here */ /***********************/ /* read magic number */ #ifdef STRICT_FORMAT_COMPATABILITY if(FORMAT_VERSION < 2) { for(int i = 0; i < strlen(magic); i++) if(getc(mFP) != magic[i]) return 0; /* get version number */ version = getc(mFP); if (version == EOF) return 0; } else #endif /* STRICT_FORMAT_COMPATABILITY */ { int nscan = 0; version = MAX_VERSION + 1; while(version > MAX_VERSION) { int byte = getc(mFP); if(byte == EOF) return 0; if(magic[nscan] != '\0' && byte == magic[nscan]) nscan++; else if(magic[nscan] == '\0' && byte <= MAX_VERSION) version = byte; else { if(byte == magic[0]) nscan = 1; else { nscan = 0; } version = MAX_VERSION + 1; } } } /* check version number */ if(version > MAX_SUPPORTED_VERSION) return 0; /* initialise the variable length file read for the compressed stream */ var_get_init(); if (mFatalError) return 0; /* get the internal file type */ internal_ftype = UINT_GET(TYPESIZE); /* has the user requested a change in file type? */ if(internal_ftype != ftype) { if(ftype == TYPE_EOF) ftype = internal_ftype; /* no problems here */ else /* check that the requested conversion is valid */ if(internal_ftype == TYPE_AU1 || internal_ftype == TYPE_AU2 || internal_ftype == TYPE_AU3 || ftype == TYPE_AU1 ||ftype == TYPE_AU2 || ftype == TYPE_AU3) goto got_enough_data; } UINT_GET(CHANSIZE); /* get blocksize if version > 0 */ if(version > 0) { int byte, nskip; UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2)); UINT_GET(LPCQSIZE); UINT_GET(0); nskip = UINT_GET(NSKIPSIZE); for(int i = 0; i < nskip; i++) { byte = uvar_get(XBYTESIZE); } } /* find verbatim command */ while(1) { int cmd = uvar_get(FNSIZE); switch(cmd) { case FN_VERBATIM: { int cklen = uvar_get(VERBATIM_CKSIZE_SIZE); while (cklen--) { if (mBytesInHeader >= OUT_BUFFER_SIZE) { retval = 0; goto got_enough_data; } mBytesInBuf = 0; mHeader[mBytesInHeader++] = (unsigned char)uvar_get(VERBATIM_BYTE_SIZE); } } break; default: goto got_enough_data; } } got_enough_data: /* wind up */ var_get_quit(); mBytesInBuf = 0; return retval; } int shn_reader::verify_header() { ulong l; int cur = 0; if (mBytesInHeader < CANONICAL_HEADER_SIZE) return 0; if (WAVE_RIFF != uchar_to_ulong_le(&mHeader[cur])) return 0; cur += 4; mWAVEHeader.chunk_size = uchar_to_ulong_le(&mHeader[cur]); cur += 4; if (WAVE_WAVE != uchar_to_ulong_le(&mHeader[cur])) return 0; cur += 4; for (;;) { cur += 4; l = uchar_to_ulong_le(&mHeader[cur]); cur += 4; if (WAVE_FMT == uchar_to_ulong_le(&mHeader[cur-8])) break; cur += l; } if (l < 16) return 0; mWAVEHeader.wave_format = uchar_to_ushort_le(&mHeader[cur]); cur += 2; switch (mWAVEHeader.wave_format) { case WAVE_FORMAT_PCM: break; default: return 0; } mWAVEHeader.channels = uchar_to_ushort_le(&mHeader[cur]); cur += 2; mWAVEHeader.samples_per_sec = uchar_to_ulong_le(&mHeader[cur]); cur += 4; mWAVEHeader.avg_bytes_per_sec = uchar_to_ulong_le(&mHeader[cur]); cur += 4; mWAVEHeader.block_align = uchar_to_ushort_le(&mHeader[cur]); cur += 2; mWAVEHeader.bits_per_sample = uchar_to_ushort_le(&mHeader[cur]); cur += 2; if (mWAVEHeader.bits_per_sample != 8 && mWAVEHeader.bits_per_sample != 16) return 0; l -= 16; if (l > 0) cur += l; for (;;) { cur += 4; l = uchar_to_ulong_le(&mHeader[cur]); cur += 4; if (WAVE_DATA == uchar_to_ulong_le(&mHeader[cur-8])) break; cur += l; } mWAVEHeader.rate = ((uint)mWAVEHeader.samples_per_sec * (uint)mWAVEHeader.channels * (uint)mWAVEHeader.bits_per_sample) / 8; mWAVEHeader.header_size = cur; mWAVEHeader.data_size = l; mWAVEHeader.total_size = mWAVEHeader.chunk_size + 8; mWAVEHeader.length = mWAVEHeader.data_size / mWAVEHeader.rate; /* header looks ok */ return 1; } void shn_reader::write_and_wait(int block_size) { int bytes_to_write; if (mBytesInBuf < block_size) return; bytes_to_write = MIN(mBytesInBuf, block_size); if (bytes_to_write <= 0) return; while (mGoing && mSeekTo == -1) { long written; pthread_mutex_lock(&mRingLock); written = mRing.WriteData((char *) mBuffer, bytes_to_write); bytes_to_write -= written; mBytesInBuf -= written; if (!bytes_to_write) { pthread_mutex_unlock(&mRingLock); break; } pthread_cond_wait(&mRunCond, &mRingLock); pthread_mutex_unlock(&mRingLock); } } void shn_reader::Run() { slong **buffer = NULL, **offset = NULL; slong lpcqoffset = 0; int version = FORMAT_VERSION, bitshift = 0; int ftype = TYPE_EOF; char *magic = MAGIC; int blocksize = DEFAULT_BLOCK_SIZE, nchan = DEFAULT_NCHAN; int i, chan, nwrap, nskip = DEFAULT_NSKIP; int *qlpc = NULL, maxnlpc = DEFAULT_MAXNLPC, nmean = UNDEFINED_UINT; int cmd; int internal_ftype; int blk_size; int cklen; uchar tmp; restart: mBytesInBuf = 0; init_decode_state(); blk_size = 512 * (mWAVEHeader.bits_per_sample / 8) * mWAVEHeader.channels; /***********************/ /* EXTRACT starts here */ /***********************/ /* read magic number */ #ifdef STRICT_FORMAT_COMPATABILITY if(FORMAT_VERSION < 2) { for(i = 0; i < strlen(magic); i++) if(getc(this_shn->vars.fd) != magic[i]) { mFatalError = true; goto exit_thread; } /* get version number */ version = getc(this_shn->vars.fd); if (version == EOF) { mFatalError = true; goto exit_thread; } } else #endif /* STRICT_FORMAT_COMPATABILITY */ { int nscan = 0; version = MAX_VERSION + 1; while(version > MAX_VERSION) { int byte = getc(mFP); if(byte == EOF) { mFatalError = true; goto exit_thread; } if(magic[nscan] != '\0' && byte == magic[nscan]) nscan++; else if(magic[nscan] == '\0' && byte <= MAX_VERSION) version = byte; else { if(byte == magic[0]) nscan = 1; else { nscan = 0; } version = MAX_VERSION + 1; } } } /* check version number */ if(version > MAX_SUPPORTED_VERSION) { mFatalError = true; goto exit_thread; } /* set up the default nmean, ignoring the command line state */ nmean = (version < 2) ? DEFAULT_V0NMEAN : DEFAULT_V2NMEAN; /* initialise the variable length file read for the compressed stream */ var_get_init(); if (mFatalError) goto exit_thread; /* initialise the fixed length file write for the uncompressed stream */ fwrite_type_init(); /* get the internal file type */ internal_ftype = UINT_GET(TYPESIZE); /* has the user requested a change in file type? */ if(internal_ftype != ftype) { if(ftype == TYPE_EOF) ftype = internal_ftype; /* no problems here */ else /* check that the requested conversion is valid */ if(internal_ftype == TYPE_AU1 || internal_ftype == TYPE_AU2 || internal_ftype == TYPE_AU3 || ftype == TYPE_AU1 ||ftype == TYPE_AU2 || ftype == TYPE_AU3) { mFatalError = true; goto cleanup; } } nchan = UINT_GET(CHANSIZE); /* get blocksize if version > 0 */ if(version > 0) { int byte; blocksize = UINT_GET((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2)); maxnlpc = UINT_GET(LPCQSIZE); nmean = UINT_GET(0); nskip = UINT_GET(NSKIPSIZE); for(i = 0; i < nskip; i++) { byte = uvar_get(XBYTESIZE); } } else blocksize = DEFAULT_BLOCK_SIZE; nwrap = MAX(NWRAP, maxnlpc); /* grab some space for the input buffer */ buffer = long2d((ulong) nchan, (ulong) (blocksize + nwrap)); if (mFatalError) goto exit_thread; offset = long2d((ulong) nchan, (ulong) MAX(1, nmean)); if (mFatalError) { if (buffer) { free(buffer); buffer = NULL; } goto exit_thread; } for(chan = 0; chan < nchan; chan++) { for(i = 0; i < nwrap; i++) buffer[chan][i] = 0; buffer[chan] += nwrap; } if(maxnlpc > 0) { qlpc = (int*) pmalloc((ulong) (maxnlpc * sizeof(*qlpc))); if (mFatalError) { if (buffer) { free(buffer); buffer = NULL; } if (offset) { free(offset); buffer = NULL; } goto exit_thread; } } if(version > 1) lpcqoffset = V2LPCQOFFSET; init_offset(offset, nchan, MAX(1, nmean), internal_ftype); /* get commands from file and execute them */ chan = 0; while(1) { cmd = uvar_get(FNSIZE); if (mFatalError) goto cleanup; switch(cmd) { case FN_ZERO: case FN_DIFF0: case FN_DIFF1: case FN_DIFF2: case FN_DIFF3: case FN_QLPC: { slong coffset, *cbuffer = buffer[chan]; int resn = 0, nlpc, j; if(cmd != FN_ZERO) { resn = uvar_get(ENERGYSIZE); if (mFatalError) goto cleanup; /* this is a hack as version 0 differed in definition of var_get */ if(version == 0) resn--; } /* find mean offset : N.B. this code duplicated */ if(nmean == 0) coffset = offset[chan][0]; else { slong sum = (version < 2) ? 0 : nmean / 2; for(i = 0; i < nmean; i++) sum += offset[chan][i]; if(version < 2) coffset = sum / nmean; else coffset = ROUNDEDSHIFTDOWN(sum / nmean, bitshift); } switch(cmd) { case FN_ZERO: for(i = 0; i < blocksize; i++) cbuffer[i] = 0; break; case FN_DIFF0: for(i = 0; i < blocksize; i++) { cbuffer[i] = var_get(resn) + coffset; if (mFatalError) goto cleanup; } break; case FN_DIFF1: for(i = 0; i < blocksize; i++) { cbuffer[i] = var_get(resn) + cbuffer[i - 1]; if (mFatalError) goto cleanup; } break; case FN_DIFF2: for(i = 0; i < blocksize; i++) { cbuffer[i] = var_get(resn) + (2 * cbuffer[i - 1] - cbuffer[i - 2]); if (mFatalError) goto cleanup; } break; case FN_DIFF3: for(i = 0; i < blocksize; i++) { cbuffer[i] = var_get(resn) + 3 * (cbuffer[i - 1] - cbuffer[i - 2]) + cbuffer[i - 3]; if (mFatalError) goto cleanup; } break; case FN_QLPC: nlpc = uvar_get(LPCQSIZE); if (mFatalError) goto cleanup; for(i = 0; i < nlpc; i++) { qlpc[i] = var_get(LPCQUANT); if (mFatalError) goto cleanup; } for(i = 0; i < nlpc; i++) cbuffer[i - nlpc] -= coffset; for(i = 0; i < blocksize; i++) { slong sum = lpcqoffset; for(j = 0; j < nlpc; j++) sum += qlpc[j] * cbuffer[i - j - 1]; cbuffer[i] = var_get(resn) + (sum >> LPCQUANT); if (mFatalError) goto cleanup; } if(coffset != 0) for(i = 0; i < blocksize; i++) cbuffer[i] += coffset; break; } /* store mean value if appropriate : N.B. Duplicated code */ if(nmean > 0) { slong sum = (version < 2) ? 0 : blocksize / 2; for(i = 0; i < blocksize; i++) sum += cbuffer[i]; for(i = 1; i < nmean; i++) offset[chan][i - 1] = offset[chan][i]; if(version < 2) offset[chan][nmean - 1] = sum / blocksize; else offset[chan][nmean - 1] = (sum / blocksize) << bitshift; } /* do the wrap */ for(i = -nwrap; i < 0; i++) cbuffer[i] = cbuffer[i + blocksize]; fix_bitshift(cbuffer, blocksize, bitshift, internal_ftype); if(chan == nchan - 1) { if (!mGoing || mFatalError) goto cleanup; fwrite_type(buffer, ftype, nchan, blocksize); write_and_wait(blk_size); if (mSeekTo != -1) { shn_seek_entry *seek_info = seek_entry_search(mSeekTo * (ulong)mWAVEHeader.samples_per_sec,0, (ulong)(mSeekTableEntries - 1)); pthread_mutex_lock(&mRingLock); mRing.Empty(); pthread_mutex_unlock(&mRingLock); buffer[0][-1] = uchar_to_slong_le(seek_info->data+24); buffer[0][-2] = uchar_to_slong_le(seek_info->data+28); buffer[0][-3] = uchar_to_slong_le(seek_info->data+32); offset[0][0] = uchar_to_slong_le(seek_info->data+48); offset[0][1] = uchar_to_slong_le(seek_info->data+52); offset[0][2] = uchar_to_slong_le(seek_info->data+56); offset[0][3] = uchar_to_slong_le(seek_info->data+60); if (nchan > 1) { buffer[1][-1] = uchar_to_slong_le(seek_info->data+36); buffer[1][-2] = uchar_to_slong_le(seek_info->data+40); buffer[1][-3] = uchar_to_slong_le(seek_info->data+44); offset[1][0] = uchar_to_slong_le(seek_info->data+64); offset[1][1] = uchar_to_slong_le(seek_info->data+68); offset[1][2] = uchar_to_slong_le(seek_info->data+72); offset[1][3] = uchar_to_slong_le(seek_info->data+76); } bitshift = uchar_to_ushort_le(seek_info->data+22); fseek(mFP,(slong)uchar_to_ulong_le(seek_info->data+8),SEEK_SET); fread((uchar*) mDecodeState.getbuf, 1, BUFSIZ, mFP); mDecodeState.getbufp = mDecodeState.getbuf + uchar_to_ushort_le(seek_info->data+14); mDecodeState.nbitget = uchar_to_ushort_le(seek_info->data+16); mDecodeState.nbyteget = uchar_to_ushort_le(seek_info->data+12); mDecodeState.gbuffer = uchar_to_ulong_le(seek_info->data+18); mBytesInBuf = 0; mSeekTo = -1; } } chan = (chan + 1) % nchan; break; } break; case FN_QUIT: /* empty out last of buffer */ write_and_wait(mBytesInBuf); mEOF = true; while (1) { if (!mGoing) goto finish; if (mSeekTo != -1) { var_get_quit(); fwrite_type_quit(); if (buffer) free((void *) buffer); if (offset) free((void *) offset); if(maxnlpc > 0 && qlpc) free((void *) qlpc); fseek(mFP,0,SEEK_SET); goto restart; } } goto cleanup; break; case FN_BLOCKSIZE: blocksize = UINT_GET((int) (log((double) blocksize) / M_LN2)); if (mFatalError) goto cleanup; break; case FN_BITSHIFT: bitshift = uvar_get(BITSHIFTSIZE); if (mFatalError) goto cleanup; break; case FN_VERBATIM: cklen = uvar_get(VERBATIM_CKSIZE_SIZE); if (mFatalError) goto cleanup; while (cklen--) { tmp = (uchar)uvar_get(VERBATIM_BYTE_SIZE); if (mFatalError) goto cleanup; } break; default: mFatalError = true; //fprintf(stderr,"Sanity check fails trying to decode function: %d\n",cmd); goto cleanup; } } cleanup: write_and_wait(mBytesInBuf); finish: mSeekTo = -1; mEOF = true; /* wind up */ var_get_quit(); fwrite_type_quit(); if (buffer) free((void *) buffer); if (offset) free((void *) offset); if(maxnlpc > 0 && qlpc) free((void *) qlpc); exit_thread: pthread_exit(NULL); }