/************************************************************* * Source File: file_headers.c * Compilation: gcc -o sph2pipe sph2pipe.c shorten_x.c file_headers.c -lm * Author: Dave Graff; LDC, University of Pennsylvania * Purpose: functions to read/write SPHERE headers, and * write RIFF, AU, AIFF headers */ #include "sph_convert.h" #include static char hdr[90]; static int hdrsize, origSampcount; /************************************************************* * readSphHeader *------------------------------------------------------------ * get relevant fields from input sphere file header; return true (1) for success; * return false if: * - input file appears not to contain a sphere header * - header does not contain all of these fields: * channel_count, sample_count, sample_rate, sample_n_bytes, sample_coding(*) * (* if sample_coding is missing, assume uncompressed pcm) * - there is no data following the header * If these conditions pass, check first four bytes of data to confirm whether * file is "shorten" compressed, then assign values to corresponding globals */ int readSphHeader( char *hdrfname ) { size_t n; int nx, inphdrsize; char *field, fldname[24], fldtype[8], fldsval[32], cmpcheck[4]; FILE *fphd; if ( hdrfname == NULL ) fphd = fpin; else if (( fphd = fopen( hdrfname, "rb" )) == NULL ) { fprintf( stderr, "Unable to open %s as input header\n", hdrfname ); return 1; } n = fread( inpbuf, 1, 1024, fphd ); /* nearly all sphere headers are 1024 bytes */ if ( n != 1024 || strncmp( inpbuf, "NIST_1A", 7 )) return 1; if ( sscanf( &inpbuf[8], "%d", &hdrsize ) != 1 ) return 1; if ( hdrsize > 1024 ) { if ( hdrsize >= STD_BUF_SIZE*2 ) { /* this should not happen */ fprintf( stderr, "Invalid header size (%d) in %s\n", hdrsize, hdrfname ); return 1; } fseek( fphd, 0, 0 ); n = fread( inpbuf, 1, hdrsize, fphd ); if ( n != hdrsize ) { fprintf( stderr, "Couldn't read %d byte header in %s\n", hdrsize, hdrfname ); return 1; } } /* from now on, inphdrsize should represent an offset into the data file (fpin), to the point where actual sample data begins; in the case where the header was read from a separate file (hdrfname is not null), this offset is zero */ inphdrsize = hdrsize; if ( hdrfname != NULL ) { fclose( fphd ); inphdrsize = 0; } /* having read the header, also read the first four bytes of sample data */ if ( fread( cmpcheck, 1, 4, fpin ) != 4 ) { fprintf( stderr, "Unable to read sample data in %s\n", inpname ); return 1; } fseek( fpin, inphdrsize, 0 ); samptype = sampsize = sampcount = samprate = chancount = UNKNOWN; inporder = NULL; field = strtok( inpbuf, "\n" ); while ( field != NULL && strcmp( field, "end_head" )) { if ( !strncmp( field, "channel_count -i ", 17 )) sscanf( field, "%s %s %d", fldname, fldtype, &chancount ); else if ( !strncmp( field, "sample_count -i ", 16 )) sscanf( field, "%s %s %d", fldname, fldtype, &sampcount ); else if ( !strncmp( field, "sample_rate -i ", 15 )) sscanf( field, "%s %s %d", fldname, fldtype, &samprate ); else if ( !strncmp( field, "sample_n_bytes -i ", 18 )) sscanf( field, "%s %s %d", fldname, fldtype, &sampsize ); else if ( !strncmp( field, "sample_byte_format -s", 21 )) { sscanf( field, "%s %s %s", fldname, fldtype, fldsval ); inporder = strdup( fldsval ); } else if ( !strncmp( field, "sample_coding -s", 16 )) { sscanf( field, "%s %s %s", fldname, fldtype, fldsval ); samptype = ( !strncmp( fldsval, "ulaw", 4 )) ? ULAW : ( !strncmp( fldsval, "alaw", 4 )) ? ALAW : ( !strncmp( fldsval, "pcm", 3 )) ? PCM : UNKNOWN; } field = strtok( NULL, "\n" ); } if ( strcmp( field, "end_head" )) /* this shouldn't happen */ return 1; if ( !samptype && ( sampsize == 2 || ( inporder && strlen( inporder ) == 2 ))) samptype = PCM; /* having done that, the following things must be known, or else * we don't really have a usable sphere file: */ if ( !samptype || !sampcount || !samprate || !chancount || ( samptype == PCM && inporder == NULL )) return 1; /* if "sample_n_bytes" was not specified, we can set it based * on sample_coding ( samptype & 3 ) */ if ( sampsize == UNKNOWN ) sampsize = samptype & 3; /* Now that we're done with the text data in the sphere header, look * at the first four bytes of waveform data to see if it's shortened */ origSampcount = sampcount; if ( !strncmp( cmpcheck, "ajkg", 4 )) { /* the "magic number" for shorten */ doshorten++; if ( samptype == ALAW ) /* this must be a mistake -- abort now */ return 1; } else { /* when not shortened, we can try to check file size vs. header specs */ struct stat statbuf; int fdin; fdin = fileno( fpin ); if ( fstat( fdin, &statbuf ) < 0 ) fprintf( stderr, "Warning: unable to determine size of file %s\n", inpname ); else { n = inphdrsize + chancount * sampcount * sampsize; if ( statbuf.st_size != n ) { /* if they conflict, go with the file size */ sampcount = ( statbuf.st_size - inphdrsize ) / ( chancount * sampsize ); fprintf( stderr, "Warning:%s: sample_count reset to %d to match size (%d bytes)\n", inpname, sampcount, statbuf.st_size ); } } } /* compute total duration, leave file pointer at end of header (start of data) */ totalsec = sampcount / (double) samprate; return 0; } /************************************************************* * writeSphHeader *------------------------------------------------------------ * If used at all, this function is called almost immediately * after readSphHeader, so SPH header data is still present in * inpbuf. Now, make adjustments to the header data as needed * (to reflect uncompression, and/or conversion between u/alaw * and pcm, and/or demux), and adjust padding to assure the * correct header size on output. */ void writeSphHeader( void ) { char *field, *ohdr, *fldsval, extrahdr[16]; int i, flen, hdrbytesOut = 0, didSBFormat = 0; /* Header data is still in inpbuf after call to readSphHeader, * but first we have to undo the effects of strtok(): */ ohdr = inpbuf; for ( i=0; i chanout ) flen = sprintf( ohdr, "channel_count -i 1\n" ); else if ( !strncmp( field, "sample_count -i ", 16 ) && sampcount != origSampcount ) flen = sprintf( ohdr, "sample_count -i %d\n", sampcount ); else if ( !strncmp( field, "sample_n_bytes -i ", 18 ) && sampsize != sizeout ) flen = sprintf( ohdr, "sample_n_bytes -i %d\n", sizeout ); else if ( !strncmp( field, "sample_byte_format -s", 21 ) && ( sampsize != sizeout || ( sizeout == 2 && strcmp( outorder, inporder )))) { if ( sizeout == 1 ) { field = strtok( NULL, "\n" ); /* don't need this field for u/alaw output */ continue; } flen = sprintf( ohdr, "sample_byte_format -s2 %s\n", outorder ); didSBFormat++; } else if ( !strncmp( field, "sample_coding -s", 16 ) && ( doshorten || ( samptype != typeout ))) { if ( typeout == PCM ) { i = 3; fldsval = "pcm"; } else { i = 4; fldsval = ( typeout == ALAW ) ? "alaw" : "ulaw"; } flen = sprintf( ohdr, "sample_coding -s%d %s\n", i, fldsval ); } else if ( !strncmp( field, "sample_sig_bits -i ", 19 ) ) { int bits; sscanf( &field[19], "%d", &bits ); if ( bits > 8 && typeout != PCM ) flen = sprintf( ohdr, "sample_sig_bits -i 8\n" ); else if ( bits == 8 && typeout == PCM ) flen = sprintf( ohdr, "sample_sig_bits -i 16\n" ); else flen = sprintf( ohdr, "%s\n", field ); /* no change needed */ } else flen = sprintf( ohdr, "%s\n", field ); ohdr += flen; hdrbytesOut += flen; field = strtok( NULL, "\n" ); } /* Minor detail: if input is ulaw/alaw AND output is pcm AND the input header * happens to lack the "sample_byte_format" field (because this is not needed * for ulaw/alaw data), now we have to add this field to the output header. */ if ( didSBFormat == 0 && sampsize < sizeout ) { flen = sprintf( ohdr, "sample_byte_format -s2 %s\n", outorder ); ohdr += flen; hdrbytesOut += flen; } flen = sprintf( ohdr, "end_head\n" ); /* add the end-of-header marker */ ohdr += flen; hdrbytesOut += flen; /* Final detail: it's possible that changing "sample_sig_bits" and/or * "sample_coding", and/or adding/changing "sample_byte_format" could * enlarge the header size beyond its current multiple of 1024 -- if so, * we need to expand the header size to the next multiple of 1024: */ if ( hdrbytesOut > hdrsize ) { hdrsize += 1024; sprintf( extrahdr, "%7d", hdrsize ); strncpy( &outbuf[8], extrahdr, 7 ); } /* Add white-space padding to complete the header block */ while ( hdrbytesOut < hdrsize ) { *ohdr++ = (char)(( hdrbytesOut % 32 ) ? ' ' : '\n' ); hdrbytesOut++; } if ( fwrite( outbuf, 1, hdrsize, fpout ) != hdrsize ) { fprintf( stderr, "Couldn't write %d byte output header from %s\n", hdrsize, inpname ); exit(1); } } /************************************************************* * copycharr *------------------------------------------------------------ * could've used memcopy, but this feels more stable/portable... */ void copycharr( char *from, char *to, int n ) { int i; for ( i=0; i 16384) || !(fMant < 1)) { /* Infinity or NaN */ expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */ } else { /* Finite */ expon += 16382; if (expon < 0) { /* denormalized */ fMant = ldexp(fMant, expon); expon = 0; } expon |= sign; fMant = ldexp(fMant, 32); fsMant = floor(fMant); hiMant = FloatToUnsigned(fsMant); fMant = ldexp(fMant - fsMant, 32); fsMant = floor(fMant); loMant = FloatToUnsigned(fsMant); } } bytes[0] = expon >> 8; bytes[1] = expon; bytes[2] = hiMant >> 24; bytes[3] = hiMant >> 16; bytes[4] = hiMant >> 8; bytes[5] = hiMant; bytes[6] = loMant >> 24; bytes[7] = loMant >> 16; bytes[8] = loMant >> 8; bytes[9] = loMant; } /* The following code has been adapted from the file "aiff.c" provided * in the SoX source code distribution. */ void writeAIFFHeader( void ) { char *ordr = "10"; /* AIFF header want high-byte first */ int fsize, hsize, hoffs; short int nbyts, nchan, fmtyp; char ieeebuf[10]; nchan = ( chanout < chancount ) ? 1 : chancount; nbyts = nchan * sizeout; hsize = 46; fsize = nbyts * sampcount; ConvertToIeeeExtended((double)samprate, ieeebuf); copycharr( "FORM", &hdr[0], 4 ); copylong( fsize + hsize, &hdr[4], ordr ); copycharr( "AIFF", &hdr[8], 4 ); copycharr( "COMM", &hdr[12], 4 ); copylong( 18, &hdr[16], ordr ); copyshort( nchan, &hdr[20], ordr ); copylong( sampcount, &hdr[22], ordr ); copyshort( 16, &hdr[26], ordr ); copycharr( ieeebuf, &hdr[28], 10 ); copycharr( "SSND", &hdr[38], 4 ); copylong( 8+fsize, &hdr[42], ordr ); copylong( 0, &hdr[46], ordr ); copylong( 0, &hdr[50], ordr ); hsize += 8; /* add in the first 8 bytes, which weren't included earlier */ if ( fwrite( hdr, 1, hsize, fpout ) != hsize ) { fprintf( stderr, "Failed to write AIFF header to %s\n", outname ); exit(1); } }