ref: 67145058e417a1ecfcff723c3f78d9db66423678
dir: /src/wav.c/
/* * Microsoft's WAVE sound format handler * * Copyright 1998-2006 Chris Bagwell and SoX Contributors * Copyright 1991 Lance Norskog And Sundry Contributors * Copyright 1992 Rick Richardson * Copyright 1997 Graeme W. Gill, 93/5/17 * * Info for format tags can be found at: * http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html * */ #include <string.h> #include <stdlib.h> #include <stdio.h> #ifdef HAVE_UNISTD_H #include <unistd.h> /* For SEEK_* defines if not found in stdio */ #endif #include "sox_i.h" #include "wav.h" #include "ima_rw.h" #include "adpcm.h" #ifdef EXTERNAL_GSM #include <gsm/gsm.h> #else #include "libgsm/gsm.h" #endif /* To allow padding to samplesPerBlock. Works, but currently never true. */ static sox_size_t pad_nsamps = sox_false; /* Private data for .wav file */ typedef struct wavstuff { sox_size_t numSamples; /* samples/channel reading: starts at total count and decremented */ /* writing: starts at 0 and counts samples written */ sox_size_t dataLength; /* needed for ADPCM writing */ unsigned short formatTag; /* What type of encoding file is using */ unsigned short samplesPerBlock; unsigned short blockAlign; sox_size_t dataStart; /* need to for seeking */ int ignoreSize; /* ignoreSize allows us to process 32-bit WAV files that are * greater then 2 Gb and can't be represented by the * 32-bit size field. */ /* FIXME: Have some front-end code which sets this flag. */ /* following used by *ADPCM wav files */ unsigned short nCoefs; /* ADPCM: number of coef sets */ short *iCoefs; /* ADPCM: coef sets */ unsigned char *packet; /* Temporary buffer for packets */ short *samples; /* interleaved samples buffer */ short *samplePtr; /* Pointer to current sample */ short *sampleTop; /* End of samples-buffer */ unsigned short blockSamplesRemaining;/* Samples remaining per channel */ int state[16]; /* step-size info for *ADPCM writes */ /* following used by GSM 6.10 wav */ gsm gsmhandle; gsm_signal *gsmsample; int gsmindex; sox_size_t gsmbytecount; /* counts bytes written to data block */ } *wav_t; static char *wav_format_str(unsigned wFormatTag); static int wavwritehdr(sox_format_t *, int); /****************************************************************************/ /* IMA ADPCM Support Functions Section */ /****************************************************************************/ /* * * ImaAdpcmReadBlock - Grab and decode complete block of samples * */ static unsigned short ImaAdpcmReadBlock(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; size_t bytesRead; int samplesThisBlock; /* Pull in the packet and check the header */ bytesRead = sox_readbuf(ft, wav->packet, wav->blockAlign); samplesThisBlock = wav->samplesPerBlock; if (bytesRead < wav->blockAlign) { /* If it looks like a valid header is around then try and */ /* work with partial blocks. Specs say it should be null */ /* padded but I guess this is better than trailing quiet. */ samplesThisBlock = ImaSamplesIn(0, ft->signal.channels, bytesRead, 0); if (samplesThisBlock == 0) { sox_warn("Premature EOF on .wav input file"); return 0; } } wav->samplePtr = wav->samples; /* For a full block, the following should be true: */ /* wav->samplesPerBlock = blockAlign - 8byte header + 1 sample in header */ ImaBlockExpandI(ft->signal.channels, wav->packet, wav->samples, samplesThisBlock); return samplesThisBlock; } /****************************************************************************/ /* MS ADPCM Support Functions Section */ /****************************************************************************/ /* * * AdpcmReadBlock - Grab and decode complete block of samples * */ static unsigned short AdpcmReadBlock(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; size_t bytesRead; int samplesThisBlock; const char *errmsg; /* Pull in the packet and check the header */ bytesRead = sox_readbuf(ft, wav->packet, wav->blockAlign); samplesThisBlock = wav->samplesPerBlock; if (bytesRead < wav->blockAlign) { /* If it looks like a valid header is around then try and */ /* work with partial blocks. Specs say it should be null */ /* padded but I guess this is better than trailing quiet. */ samplesThisBlock = AdpcmSamplesIn(0, ft->signal.channels, bytesRead, 0); if (samplesThisBlock == 0) { sox_warn("Premature EOF on .wav input file"); return 0; } } errmsg = AdpcmBlockExpandI(ft->signal.channels, wav->nCoefs, wav->iCoefs, wav->packet, wav->samples, samplesThisBlock); if (errmsg) sox_warn((char*)errmsg); return samplesThisBlock; } /****************************************************************************/ /* Common ADPCM Write Function */ /****************************************************************************/ static int xxxAdpcmWriteBlock(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; sox_size_t chans, ct; short *p; chans = ft->signal.channels; p = wav->samplePtr; ct = p - wav->samples; if (ct>=chans) { /* zero-fill samples if needed to complete block */ for (p = wav->samplePtr; p < wav->sampleTop; p++) *p=0; /* compress the samples to wav->packet */ if (wav->formatTag == WAVE_FORMAT_ADPCM) { AdpcmBlockMashI(chans, wav->samples, wav->samplesPerBlock, wav->state, wav->packet, wav->blockAlign); }else{ /* WAVE_FORMAT_IMA_ADPCM */ ImaBlockMashI(chans, wav->samples, wav->samplesPerBlock, wav->state, wav->packet, 9); } /* write the compressed packet */ if (sox_writebuf(ft, wav->packet, wav->blockAlign) != wav->blockAlign) { sox_fail_errno(ft,SOX_EOF,"write error"); return (SOX_EOF); } /* update lengths and samplePtr */ wav->dataLength += wav->blockAlign; if (pad_nsamps) wav->numSamples += wav->samplesPerBlock; else wav->numSamples += ct/chans; wav->samplePtr = wav->samples; } return (SOX_SUCCESS); } /****************************************************************************/ /* WAV GSM6.10 support functions */ /****************************************************************************/ /* create the gsm object, malloc buffer for 160*2 samples */ static int wavgsminit(sox_format_t * ft) { int valueP=1; wav_t wav = (wav_t) ft->priv; wav->gsmbytecount=0; wav->gsmhandle=gsm_create(); if (!wav->gsmhandle) { sox_fail_errno(ft,SOX_EOF,"cannot create GSM object"); return (SOX_EOF); } if(gsm_option(wav->gsmhandle,GSM_OPT_WAV49,&valueP) == -1){ sox_fail_errno(ft,SOX_EOF,"error setting gsm_option for WAV49 format. Recompile gsm library with -DWAV49 option and relink sox"); return (SOX_EOF); } wav->gsmsample=(gsm_signal*)xmalloc(sizeof(gsm_signal)*160*2); wav->gsmindex=0; return (SOX_SUCCESS); } /*destroy the gsm object and free the buffer */ static void wavgsmdestroy(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; gsm_destroy(wav->gsmhandle); free(wav->gsmsample); } static sox_size_t wavgsmread(sox_format_t * ft, sox_ssample_t *buf, sox_size_t len) { wav_t wav = (wav_t) ft->priv; size_t done=0; int bytes; gsm_byte frame[65]; ft->sox_errno = SOX_SUCCESS; /* copy out any samples left from the last call */ while(wav->gsmindex && (wav->gsmindex<160*2) && (done < len)) buf[done++]=SOX_SIGNED_16BIT_TO_SAMPLE(wav->gsmsample[wav->gsmindex++],); /* read and decode loop, possibly leaving some samples in wav->gsmsample */ while (done < len) { wav->gsmindex=0; bytes = sox_readbuf(ft, frame, 65); if (bytes <=0) return done; if (bytes<65) { sox_warn("invalid wav gsm frame size: %d bytes",bytes); return done; } /* decode the long 33 byte half */ if(gsm_decode(wav->gsmhandle,frame, wav->gsmsample)<0) { sox_fail_errno(ft,SOX_EOF,"error during gsm decode"); return 0; } /* decode the short 32 byte half */ if(gsm_decode(wav->gsmhandle,frame+33, wav->gsmsample+160)<0) { sox_fail_errno(ft,SOX_EOF,"error during gsm decode"); return 0; } while ((wav->gsmindex <160*2) && (done < len)){ buf[done++]=SOX_SIGNED_16BIT_TO_SAMPLE(wav->gsmsample[(wav->gsmindex)++],); } } return done; } static int wavgsmflush(sox_format_t * ft) { gsm_byte frame[65]; wav_t wav = (wav_t) ft->priv; /* zero fill as needed */ while(wav->gsmindex<160*2) wav->gsmsample[wav->gsmindex++]=0; /*encode the even half short (32 byte) frame */ gsm_encode(wav->gsmhandle, wav->gsmsample, frame); /*encode the odd half long (33 byte) frame */ gsm_encode(wav->gsmhandle, wav->gsmsample+160, frame+32); if (sox_writebuf(ft, frame, 65) != 65) { sox_fail_errno(ft,SOX_EOF,"write error"); return (SOX_EOF); } wav->gsmbytecount += 65; wav->gsmindex = 0; return (SOX_SUCCESS); } static sox_size_t wavgsmwrite(sox_format_t * ft, const sox_ssample_t *buf, sox_size_t len) { wav_t wav = (wav_t) ft->priv; size_t done = 0; int rc; ft->sox_errno = SOX_SUCCESS; while (done < len) { while ((wav->gsmindex < 160*2) && (done < len)) wav->gsmsample[(wav->gsmindex)++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[done++], ft->clips); if (wav->gsmindex < 160*2) break; rc = wavgsmflush(ft); if (rc) return 0; } return done; } static void wavgsmstopwrite(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; ft->sox_errno = SOX_SUCCESS; if (wav->gsmindex) wavgsmflush(ft); /* Add a pad byte if amount of written bytes is not even. */ if (wav->gsmbytecount && wav->gsmbytecount % 2){ if(sox_writeb(ft, 0)) sox_fail_errno(ft,SOX_EOF,"write error"); else wav->gsmbytecount += 1; } wavgsmdestroy(ft); } /****************************************************************************/ /* General Sox WAV file code */ /****************************************************************************/ static int findChunk(sox_format_t * ft, const char *Label, sox_size_t *len) { char magic[5]; for (;;) { if (sox_reads(ft, magic, 4) == SOX_EOF) { sox_fail_errno(ft, SOX_EHDR, "WAVE file has missing %s chunk", Label); return SOX_EOF; } sox_debug("WAV Chunk %s", magic); if (sox_readdw(ft, len) == SOX_EOF) { sox_fail_errno(ft, SOX_EHDR, "WAVE file %s chunk is too short", magic); return SOX_EOF; } if (strncmp(Label, magic, 4) == 0) break; /* Found the given chunk */ /* skip to next chunk */ if (*len == 0 || sox_seeki(ft, (sox_ssize_t)(*len), SEEK_CUR) != SOX_SUCCESS) { sox_fail_errno(ft,SOX_EHDR, "WAV chunk appears to have invalid size %d.", *len); return SOX_EOF; } } return SOX_SUCCESS; } static int wavfail(sox_format_t * ft, const char *format) { sox_fail_errno(ft, SOX_EHDR, "Unhandled WAV file encoding (%s).\nTry overriding the encoding: e.g. for an MP3 WAV, `-t mp3'", format); return SOX_EOF; } /* * Do anything required before you start reading samples. * Read file header. * Find out sampling rate, * size and encoding of samples, * mono/stereo/quad. */ static int sox_wavstartread(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; char magic[5]; uint32_t len; int rc; /* wave file characteristics */ uint32_t dwRiffLength; unsigned short wChannels; /* number of channels */ uint32_t dwSamplesPerSecond; /* samples per second per channel */ uint32_t dwAvgBytesPerSec;/* estimate of bytes per second needed */ uint16_t wBitsPerSample; /* bits per sample */ uint32_t wFmtSize; uint16_t wExtSize = 0; /* extended field for non-PCM */ uint32_t dwDataLength; /* length of sound data in bytes */ sox_size_t bytesPerBlock = 0; int bytespersample; /* bytes per sample (per channel */ char text[256]; uint32_t dwLoopPos; ft->sox_errno = SOX_SUCCESS; wav->ignoreSize = 0; if (sox_reads(ft, magic, 4) == SOX_EOF || (strncmp("RIFF", magic, 4) != 0 && strncmp("RIFX", magic, 4) != 0)) { sox_fail_errno(ft,SOX_EHDR,"WAVE: RIFF header not found"); return SOX_EOF; } /* RIFX is a Big-endian RIFF */ if (strncmp("RIFX", magic, 4) == 0) { sox_debug("Found RIFX header, swapping bytes"); ft->signal.reverse_bytes = !ft->signal.reverse_bytes; } sox_readdw(ft, &dwRiffLength); if (sox_reads(ft, magic, 4) == SOX_EOF || strncmp("WAVE", magic, 4)) { sox_fail_errno(ft,SOX_EHDR,"WAVE header not found"); return SOX_EOF; } /* Now look for the format chunk */ if (findChunk(ft, "fmt ", &len) == SOX_EOF) { sox_fail_errno(ft,SOX_EHDR,"WAVE chunk fmt not found"); return SOX_EOF; } wFmtSize = len; if (wFmtSize < 16) { sox_fail_errno(ft,SOX_EHDR,"WAVE file fmt chunk is too short"); return SOX_EOF; } sox_readw(ft, &(wav->formatTag)); sox_readw(ft, &wChannels); sox_readdw(ft, &dwSamplesPerSecond); sox_readdw(ft, &dwAvgBytesPerSec); /* Average bytes/second */ sox_readw(ft, &(wav->blockAlign)); /* Block align */ sox_readw(ft, &wBitsPerSample); /* bits per sample per channel */ len -= 16; if (wav->formatTag == WAVE_FORMAT_EXTENSIBLE) { uint16_t extensionSize; uint16_t numberOfValidBits; uint32_t speakerPositionMask; uint16_t subFormatTag; uint8_t dummyByte; int i; if (wFmtSize < 18) { sox_fail_errno(ft,SOX_EHDR,"WAVE file fmt chunk is too short"); return SOX_EOF; } sox_readw(ft, &extensionSize); len -= 2; if (extensionSize < 22) { sox_fail_errno(ft,SOX_EHDR,"WAVE file fmt chunk is too short"); return SOX_EOF; } sox_readw(ft, &numberOfValidBits); sox_readdw(ft, &speakerPositionMask); sox_readw(ft, &subFormatTag); for (i = 0; i < 14; ++i) sox_readb(ft, &dummyByte); len -= 22; if (numberOfValidBits != wBitsPerSample) { sox_fail_errno(ft,SOX_EHDR,"WAVE file fmt with padded samples is not supported yet"); return SOX_EOF; } wav->formatTag = subFormatTag; } switch (wav->formatTag) { case WAVE_FORMAT_UNKNOWN: sox_fail_errno(ft,SOX_EHDR,"WAVE file is in unsupported Microsoft Official Unknown format."); return SOX_EOF; case WAVE_FORMAT_PCM: /* Default (-1) depends on sample size. Set that later on. */ if (ft->signal.encoding != SOX_ENCODING_UNKNOWN && ft->signal.encoding != SOX_ENCODING_UNSIGNED && ft->signal.encoding != SOX_ENCODING_SIGN2) sox_report("User options overriding encoding read in .wav header"); /* Needed by rawread() functions */ rc = sox_rawstartread(ft); if (rc) return rc; break; case WAVE_FORMAT_IMA_ADPCM: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_IMA_ADPCM) ft->signal.encoding = SOX_ENCODING_IMA_ADPCM; else sox_report("User options overriding encoding read in .wav header"); break; case WAVE_FORMAT_ADPCM: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_ADPCM) ft->signal.encoding = SOX_ENCODING_ADPCM; else sox_report("User options overriding encoding read in .wav header"); break; case WAVE_FORMAT_IEEE_FLOAT: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_FLOAT) ft->signal.encoding = SOX_ENCODING_FLOAT; else sox_report("User options overriding encoding read in .wav header"); /* Needed by rawread() functions */ rc = sox_rawstartread(ft); if (rc) return rc; break; case WAVE_FORMAT_ALAW: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_ALAW) ft->signal.encoding = SOX_ENCODING_ALAW; else sox_report("User options overriding encoding read in .wav header"); /* Needed by rawread() functions */ rc = sox_rawstartread(ft); if (rc) return rc; break; case WAVE_FORMAT_MULAW: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_ULAW) ft->signal.encoding = SOX_ENCODING_ULAW; else sox_report("User options overriding encoding read in .wav header"); /* Needed by rawread() functions */ rc = sox_rawstartread(ft); if (rc) return rc; break; case WAVE_FORMAT_OKI_ADPCM: return wavfail(ft, "OKI ADPCM"); case WAVE_FORMAT_DIGISTD: return wavfail(ft, "Digistd"); case WAVE_FORMAT_DIGIFIX: return wavfail(ft, "Digifix"); case WAVE_FORMAT_DOLBY_AC2: return wavfail(ft, "Dolby AC2"); case WAVE_FORMAT_GSM610: if (ft->signal.encoding == SOX_ENCODING_UNKNOWN || ft->signal.encoding == SOX_ENCODING_GSM ) ft->signal.encoding = SOX_ENCODING_GSM; else sox_report("User options overriding encoding read in .wav header"); break; case WAVE_FORMAT_ROCKWELL_ADPCM: return wavfail(ft, "Rockwell ADPCM"); case WAVE_FORMAT_ROCKWELL_DIGITALK: return wavfail(ft, "Rockwell DIGITALK"); case WAVE_FORMAT_G721_ADPCM: return wavfail(ft, "G.721 ADPCM"); case WAVE_FORMAT_G728_CELP: return wavfail(ft, "G.728 CELP"); case WAVE_FORMAT_MPEG: return wavfail(ft, "MPEG"); case WAVE_FORMAT_MPEGLAYER3: return wavfail(ft, "MP3"); case WAVE_FORMAT_G726_ADPCM: return wavfail(ft, "G.726 ADPCM"); case WAVE_FORMAT_G722_ADPCM: return wavfail(ft, "G.722 ADPCM"); default: sox_fail_errno(ft, SOX_EHDR, "Unknown WAV file encoding (type %x)", wav->formatTag); return SOX_EOF; } /* User options take precedence */ if (ft->signal.channels == 0 || ft->signal.channels == wChannels) ft->signal.channels = wChannels; else sox_report("User options overriding channels read in .wav header"); if (ft->signal.rate == 0 || ft->signal.rate == dwSamplesPerSecond) ft->signal.rate = dwSamplesPerSecond; else sox_report("User options overriding rate read in .wav header"); wav->iCoefs = NULL; wav->packet = NULL; wav->samples = NULL; /* non-PCM formats expect alaw and mulaw formats have extended fmt chunk. * Check for those cases. */ if (wav->formatTag != WAVE_FORMAT_PCM && wav->formatTag != WAVE_FORMAT_ALAW && wav->formatTag != WAVE_FORMAT_MULAW) { if (len >= 2) { sox_readw(ft, &wExtSize); len -= 2; } else { sox_warn("wave header missing FmtExt chunk"); } } if (wExtSize > len) { sox_fail_errno(ft,SOX_EOF,"wave header error: wExtSize inconsistent with wFmtLen"); return SOX_EOF; } switch (wav->formatTag) { case WAVE_FORMAT_ADPCM: if (wExtSize < 4) { sox_fail_errno(ft,SOX_EOF,"format[%s]: expects wExtSize >= %d", wav_format_str(wav->formatTag), 4); return SOX_EOF; } if (wBitsPerSample != 4) { sox_fail_errno(ft,SOX_EOF,"Can only handle 4-bit MS ADPCM in wav files"); return SOX_EOF; } sox_readw(ft, &(wav->samplesPerBlock)); bytesPerBlock = AdpcmBytesPerBlock(ft->signal.channels, wav->samplesPerBlock); if (bytesPerBlock > wav->blockAlign) { sox_fail_errno(ft,SOX_EOF,"format[%s]: samplesPerBlock(%d) incompatible with blockAlign(%d)", wav_format_str(wav->formatTag), wav->samplesPerBlock, wav->blockAlign); return SOX_EOF; } sox_readw(ft, &(wav->nCoefs)); if (wav->nCoefs < 7 || wav->nCoefs > 0x100) { sox_fail_errno(ft,SOX_EOF,"ADPCM file nCoefs (%.4hx) makes no sense", wav->nCoefs); return SOX_EOF; } wav->packet = (unsigned char *)xmalloc(wav->blockAlign); len -= 4; if (wExtSize < 4 + 4*wav->nCoefs) { sox_fail_errno(ft,SOX_EOF,"wave header error: wExtSize(%d) too small for nCoefs(%d)", wExtSize, wav->nCoefs); return SOX_EOF; } wav->samples = (short *)xmalloc(wChannels*wav->samplesPerBlock*sizeof(short)); /* nCoefs, iCoefs used by adpcm.c */ wav->iCoefs = (short *)xmalloc(wav->nCoefs * 2 * sizeof(short)); { int i, errct=0; for (i=0; len>=2 && i < 2*wav->nCoefs; i++) { sox_readw(ft, (unsigned short *)&(wav->iCoefs[i])); len -= 2; if (i<14) errct += (wav->iCoefs[i] != iCoef[i/2][i%2]); /* sox_debug("iCoefs[%2d] %4d",i,wav->iCoefs[i]); */ } if (errct) sox_warn("base iCoefs differ in %d/14 positions",errct); } bytespersample = SOX_SIZE_16BIT; /* AFTER de-compression */ break; case WAVE_FORMAT_IMA_ADPCM: if (wExtSize < 2) { sox_fail_errno(ft,SOX_EOF,"format[%s]: expects wExtSize >= %d", wav_format_str(wav->formatTag), 2); return SOX_EOF; } if (wBitsPerSample != 4) { sox_fail_errno(ft,SOX_EOF,"Can only handle 4-bit IMA ADPCM in wav files"); return SOX_EOF; } sox_readw(ft, &(wav->samplesPerBlock)); bytesPerBlock = ImaBytesPerBlock(ft->signal.channels, wav->samplesPerBlock); if (bytesPerBlock > wav->blockAlign || wav->samplesPerBlock%8 != 1) { sox_fail_errno(ft,SOX_EOF,"format[%s]: samplesPerBlock(%d) incompatible with blockAlign(%d)", wav_format_str(wav->formatTag), wav->samplesPerBlock, wav->blockAlign); return SOX_EOF; } wav->packet = (unsigned char *)xmalloc(wav->blockAlign); len -= 2; wav->samples = (short *)xmalloc(wChannels*wav->samplesPerBlock*sizeof(short)); bytespersample = SOX_SIZE_16BIT; /* AFTER de-compression */ break; /* GSM formats have extended fmt chunk. Check for those cases. */ case WAVE_FORMAT_GSM610: if (wExtSize < 2) { sox_fail_errno(ft,SOX_EOF,"format[%s]: expects wExtSize >= %d", wav_format_str(wav->formatTag), 2); return SOX_EOF; } sox_readw(ft, &wav->samplesPerBlock); bytesPerBlock = 65; if (wav->blockAlign != 65) { sox_fail_errno(ft,SOX_EOF,"format[%s]: expects blockAlign(%d) = %d", wav_format_str(wav->formatTag), wav->blockAlign, 65); return SOX_EOF; } if (wav->samplesPerBlock != 320) { sox_fail_errno(ft,SOX_EOF,"format[%s]: expects samplesPerBlock(%d) = %d", wav_format_str(wav->formatTag), wav->samplesPerBlock, 320); return SOX_EOF; } bytespersample = SOX_SIZE_16BIT; /* AFTER de-compression */ len -= 2; break; default: bytespersample = (wBitsPerSample + 7)/8; } switch (bytespersample) { case SOX_SIZE_BYTE: /* User options take precedence */ if (ft->signal.size == -1 || ft->signal.size == SOX_SIZE_BYTE) ft->signal.size = SOX_SIZE_BYTE; else sox_warn("User options overriding size read in .wav header"); /* Now we have enough information to set default encodings. */ if (ft->signal.encoding == SOX_ENCODING_UNKNOWN) ft->signal.encoding = SOX_ENCODING_UNSIGNED; break; case SOX_SIZE_16BIT: if (ft->signal.size == -1 || ft->signal.size == SOX_SIZE_16BIT) ft->signal.size = SOX_SIZE_16BIT; else sox_warn("User options overriding size read in .wav header"); /* Now we have enough information to set default encodings. */ if (ft->signal.encoding == SOX_ENCODING_UNKNOWN) ft->signal.encoding = SOX_ENCODING_SIGN2; break; case SOX_SIZE_24BIT: if (ft->signal.size == -1 || ft->signal.size == SOX_SIZE_24BIT) ft->signal.size = SOX_SIZE_24BIT; else sox_warn("User options overriding size read in .wav header"); /* Now we have enough information to set default encodings. */ if (ft->signal.encoding == SOX_ENCODING_UNKNOWN) ft->signal.encoding = SOX_ENCODING_SIGN2; break; case SOX_SIZE_32BIT: if (ft->signal.size == -1 || ft->signal.size == SOX_SIZE_32BIT) ft->signal.size = SOX_SIZE_32BIT; else sox_warn("User options overriding size read in .wav header"); /* Now we have enough information to set default encodings. */ if (ft->signal.encoding == SOX_ENCODING_UNKNOWN) ft->signal.encoding = SOX_ENCODING_SIGN2; break; default: sox_fail_errno(ft,SOX_EOF,"Sorry, don't understand .wav size"); return SOX_EOF; } /* Skip anything left over from fmt chunk */ sox_seeki(ft, (sox_ssize_t)len, SEEK_CUR); /* for non-PCM formats, there's a 'fact' chunk before * the upcoming 'data' chunk */ /* Now look for the wave data chunk */ if (findChunk(ft, "data", &len) == SOX_EOF) { sox_fail_errno(ft, SOX_EOF, "Could not find data chunk."); return SOX_EOF; } dwDataLength = len; /* Data starts here */ wav->dataStart = sox_tell(ft); switch (wav->formatTag) { case WAVE_FORMAT_ADPCM: wav->numSamples = AdpcmSamplesIn(dwDataLength, ft->signal.channels, wav->blockAlign, wav->samplesPerBlock); sox_debug_more("datalen %d, numSamples %d",dwDataLength, wav->numSamples); wav->blockSamplesRemaining = 0; /* Samples left in buffer */ ft->length = wav->numSamples*ft->signal.channels; break; case WAVE_FORMAT_IMA_ADPCM: /* Compute easiest part of number of samples. For every block, there are samplesPerBlock samples to read. */ wav->numSamples = ImaSamplesIn(dwDataLength, ft->signal.channels, wav->blockAlign, wav->samplesPerBlock); sox_debug_more("datalen %d, numSamples %d",dwDataLength, wav->numSamples); wav->blockSamplesRemaining = 0; /* Samples left in buffer */ initImaTable(); ft->length = wav->numSamples*ft->signal.channels; break; case WAVE_FORMAT_GSM610: wav->numSamples = ((dwDataLength / wav->blockAlign) * wav->samplesPerBlock); wavgsminit(ft); ft->length = wav->numSamples*ft->signal.channels; break; default: wav->numSamples = dwDataLength/ft->signal.size/ft->signal.channels; ft->length = wav->numSamples*ft->signal.channels; } sox_debug("Reading Wave file: %s format, %d channel%s, %d samp/sec", wav_format_str(wav->formatTag), ft->signal.channels, wChannels == 1 ? "" : "s", dwSamplesPerSecond); sox_debug(" %d byte/sec, %d block align, %d bits/samp, %u data bytes", dwAvgBytesPerSec, wav->blockAlign, wBitsPerSample, dwDataLength); /* Can also report extended fmt information */ switch (wav->formatTag) { case WAVE_FORMAT_ADPCM: sox_debug(" %d Extsize, %d Samps/block, %d bytes/block %d Num Coefs, %d Samps/chan", wExtSize,wav->samplesPerBlock,bytesPerBlock,wav->nCoefs, wav->numSamples); break; case WAVE_FORMAT_IMA_ADPCM: sox_debug(" %d Extsize, %d Samps/block, %d bytes/block %d Samps/chan", wExtSize, wav->samplesPerBlock, bytesPerBlock, wav->numSamples); break; case WAVE_FORMAT_GSM610: sox_debug("GSM .wav: %d Extsize, %d Samps/block, %d Samples/chan", wExtSize, wav->samplesPerBlock, wav->numSamples); break; default: sox_debug(" %d Samps/chans", wav->numSamples); } /* Horrible way to find Cool Edit marker points. Taken from Quake source*/ ft->loops[0].start = ~0u; if(ft->seekable){ /*Got this from the quake source. I think it 32bit aligns the chunks * doubt any machine writing Cool Edit Chunks writes them at an odd * offset */ len = (len + 1) & ~1u; if (sox_seeki(ft, (sox_ssize_t)len, SEEK_CUR) == SOX_SUCCESS && findChunk(ft, "LIST", &len) != SOX_EOF) { ft->comment = (char*)xmalloc(256); /* Initialize comment to a NULL string */ ft->comment[0] = 0; while(!sox_eof(ft)) { if (sox_reads(ft,magic,4) == SOX_EOF) break; /* First look for type fields for LIST Chunk and * skip those if found. Since a LIST is a list * of Chunks, treat the remaining data as Chunks * again. */ if (strncmp(magic, "INFO", 4) == 0) { /*Skip*/ sox_debug("Type INFO"); } else if (strncmp(magic, "adtl", 4) == 0) { /* Skip */ sox_debug("Type adtl"); } else { if (sox_readdw(ft,&len) == SOX_EOF) break; if (strncmp(magic,"ICRD",4) == 0) { sox_debug("Chunk ICRD"); if (len > 254) { sox_warn("Possible buffer overflow hack attack (ICRD)!"); break; } sox_reads(ft,text,len); if (strlen(ft->comment) + strlen(text) < 254) { if (ft->comment[0] != 0) strcat(ft->comment,"\n"); strcat(ft->comment,text); } if (strlen(text) < len) sox_seeki(ft, (sox_ssize_t)(len - strlen(text)), SEEK_CUR); } else if (strncmp(magic,"ISFT",4) == 0) { sox_debug("Chunk ISFT"); if (len > 254) { sox_warn("Possible buffer overflow hack attack (ISFT)!"); break; } sox_reads(ft,text,len); if (strlen(ft->comment) + strlen(text) < 254) { if (ft->comment[0] != 0) strcat(ft->comment,"\n"); strcat(ft->comment,text); } if (strlen(text) < len) sox_seeki(ft, (sox_ssize_t)(len - strlen(text)), SEEK_CUR); } else if (strncmp(magic,"cue ",4) == 0) { sox_debug("Chunk cue "); sox_seeki(ft,(sox_ssize_t)(len-4),SEEK_CUR); sox_readdw(ft,&dwLoopPos); ft->loops[0].start = dwLoopPos; } else if (strncmp(magic,"ltxt",4) == 0) { sox_debug("Chunk ltxt"); sox_readdw(ft,&dwLoopPos); ft->loops[0].length = dwLoopPos - ft->loops[0].start; if (len > 4) sox_seeki(ft, (sox_ssize_t)(len - 4), SEEK_CUR); } else { sox_debug("Attempting to seek beyond unsupported chunk '%c%c%c%c' of length %d bytes", magic[0], magic[1], magic[2], magic[3], len); len = (len + 1) & ~1u; sox_seeki(ft, (sox_ssize_t)len, SEEK_CUR); } } } } sox_clearerr(ft); sox_seeki(ft,(sox_ssize_t)wav->dataStart,SEEK_SET); } return SOX_SUCCESS; } /* * Read up to len samples from file. * Convert to signed longs. * Place in buf[]. * Return number of samples read. */ static sox_size_t sox_wavread(sox_format_t * ft, sox_ssample_t *buf, sox_size_t len) { wav_t wav = (wav_t) ft->priv; sox_size_t done; ft->sox_errno = SOX_SUCCESS; /* If file is in ADPCM encoding then read in multiple blocks else */ /* read as much as possible and return quickly. */ switch (ft->signal.encoding) { case SOX_ENCODING_IMA_ADPCM: case SOX_ENCODING_ADPCM: if (!wav->ignoreSize && len > (wav->numSamples*ft->signal.channels)) len = (wav->numSamples*ft->signal.channels); done = 0; while (done < len) { /* Still want data? */ /* See if need to read more from disk */ if (wav->blockSamplesRemaining == 0) { if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM) wav->blockSamplesRemaining = ImaAdpcmReadBlock(ft); else wav->blockSamplesRemaining = AdpcmReadBlock(ft); if (wav->blockSamplesRemaining == 0) { /* Don't try to read any more samples */ wav->numSamples = 0; return done; } wav->samplePtr = wav->samples; } /* Copy interleaved data into buf, converting to sox_sample_t */ { short *p, *top; size_t ct; ct = len-done; if (ct > (wav->blockSamplesRemaining*ft->signal.channels)) ct = (wav->blockSamplesRemaining*ft->signal.channels); done += ct; wav->blockSamplesRemaining -= (ct/ft->signal.channels); p = wav->samplePtr; top = p+ct; /* Output is already signed */ while (p<top) *buf++ = SOX_SIGNED_16BIT_TO_SAMPLE((*p++),); wav->samplePtr = p; } } /* "done" for ADPCM equals total data processed and not * total samples procesed. The only way to take care of that * is to return here and not fall thru. */ wav->numSamples -= (done / ft->signal.channels); return done; break; case SOX_ENCODING_GSM: if (!wav->ignoreSize && len > wav->numSamples*ft->signal.channels) len = (wav->numSamples*ft->signal.channels); done = wavgsmread(ft, buf, len); if (done == 0 && wav->numSamples != 0) sox_warn("Premature EOF on .wav input file"); break; default: /* assume PCM or float encoding */ if (!wav->ignoreSize && len > wav->numSamples*ft->signal.channels) len = (wav->numSamples*ft->signal.channels); done = sox_rawread(ft, buf, len); /* If software thinks there are more samples but I/O */ /* says otherwise, let the user know about this. */ if (done == 0 && wav->numSamples != 0) sox_warn("Premature EOF on .wav input file"); } /* Only return buffers that contain a totally playable * amount of audio. */ done -= done % ft->signal.channels; if (done/ft->signal.channels > wav->numSamples) wav->numSamples = 0; else wav->numSamples -= (done/ft->signal.channels); return done; } /* * Do anything required when you stop reading samples. * Don't close input file! */ static int sox_wavstopread(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; int rc = SOX_SUCCESS; ft->sox_errno = SOX_SUCCESS; free(wav->packet); free(wav->samples); free(wav->iCoefs); free(ft->comment); ft->comment = NULL; switch (ft->signal.encoding) { case SOX_ENCODING_GSM: wavgsmdestroy(ft); break; case SOX_ENCODING_IMA_ADPCM: case SOX_ENCODING_ADPCM: break; default: /* Needed for rawread() */ rc = sox_rawstopread(ft); } return rc; } static int sox_wavstartwrite(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; int rc; ft->sox_errno = SOX_SUCCESS; if (ft->signal.encoding != SOX_ENCODING_ADPCM && ft->signal.encoding != SOX_ENCODING_IMA_ADPCM && ft->signal.encoding != SOX_ENCODING_GSM) { rc = sox_rawstartwrite(ft); if (rc) return rc; } wav->numSamples = 0; wav->dataLength = 0; if (!ft->seekable) sox_warn("Length in output .wav header will be wrong since can't seek to fix it"); rc = wavwritehdr(ft, 0); /* also calculates various wav->* info */ if (rc != 0) return rc; wav->packet = NULL; wav->samples = NULL; wav->iCoefs = NULL; switch (wav->formatTag) { size_t ch, sbsize; case WAVE_FORMAT_IMA_ADPCM: initImaTable(); /* intentional case fallthru! */ case WAVE_FORMAT_ADPCM: /* #channels already range-checked for overflow in wavwritehdr() */ for (ch=0; ch<ft->signal.channels; ch++) wav->state[ch] = 0; sbsize = ft->signal.channels * wav->samplesPerBlock; wav->packet = (unsigned char *)xmalloc(wav->blockAlign); wav->samples = (short *)xmalloc(sbsize*sizeof(short)); wav->sampleTop = wav->samples + sbsize; wav->samplePtr = wav->samples; break; case WAVE_FORMAT_GSM610: wavgsminit(ft); break; default: break; } return SOX_SUCCESS; } /* wavwritehdr: write .wav headers as follows: bytes variable description 0 - 3 'RIFF'/'RIFX' Little/Big-endian 4 - 7 wRiffLength length of file minus the 8 byte riff header 8 - 11 'WAVE' 12 - 15 'fmt ' 16 - 19 wFmtSize length of format chunk minus 8 byte header 20 - 21 wFormatTag identifies PCM, ULAW etc 22 - 23 wChannels 24 - 27 dwSamplesPerSecond samples per second per channel 28 - 31 dwAvgBytesPerSec non-trivial for compressed formats 32 - 33 wBlockAlign basic block size 34 - 35 wBitsPerSample non-trivial for compressed formats PCM formats then go straight to the data chunk: 36 - 39 'data' 40 - 43 dwDataLength length of data chunk minus 8 byte header 44 - (dwDataLength + 43) the data non-PCM formats must write an extended format chunk and a fact chunk: ULAW, ALAW formats: 36 - 37 wExtSize = 0 the length of the format extension 38 - 41 'fact' 42 - 45 dwFactSize = 4 length of the fact chunk minus 8 byte header 46 - 49 dwSamplesWritten actual number of samples written out 50 - 53 'data' 54 - 57 dwDataLength length of data chunk minus 8 byte header 58 - (dwDataLength + 57) the data GSM6.10 format: 36 - 37 wExtSize = 2 the length in bytes of the format-dependent extension 38 - 39 320 number of samples per block 40 - 43 'fact' 44 - 47 dwFactSize = 4 length of the fact chunk minus 8 byte header 48 - 51 dwSamplesWritten actual number of samples written out 52 - 55 'data' 56 - 59 dwDataLength length of data chunk minus 8 byte header 60 - (dwDataLength + 59) the data (+ a padding byte if dwDataLength is odd) note that header contains (up to) 3 separate ways of describing the length of the file, all derived here from the number of (input) samples wav->numSamples in a way that is non-trivial for the blocked and padded compressed formats: wRiffLength - (riff header) the length of the file, minus 8 dwSamplesWritten - (fact header) the number of samples written (after padding to a complete block eg for GSM) dwDataLength - (data chunk header) the number of (valid) data bytes written */ static int wavwritehdr(sox_format_t * ft, int second_header) { wav_t wav = (wav_t) ft->priv; /* variables written to wav file header */ /* RIFF header */ uint32_t wRiffLength ; /* length of file after 8 byte riff header */ /* fmt chunk */ uint16_t wFmtSize = 16; /* size field of the fmt chunk */ uint16_t wFormatTag = 0; /* data format */ uint16_t wChannels; /* number of channels */ uint32_t dwSamplesPerSecond; /* samples per second per channel*/ uint32_t dwAvgBytesPerSec=0; /* estimate of bytes per second needed */ uint16_t wBlockAlign=0; /* byte alignment of a basic sample block */ uint16_t wBitsPerSample=0; /* bits per sample */ /* fmt chunk extension (not PCM) */ uint16_t wExtSize=0; /* extra bytes in the format extension */ uint16_t wSamplesPerBlock; /* samples per channel per block */ /* wSamplesPerBlock and other things may go into format extension */ /* fact chunk (not PCM) */ uint32_t dwFactSize=4; /* length of the fact chunk */ uint32_t dwSamplesWritten=0; /* windows doesnt seem to use this*/ /* data chunk */ uint32_t dwDataLength=0x7ffff000; /* length of sound data in bytes */ /* end of variables written to header */ /* internal variables, intermediate values etc */ int bytespersample; /* (uncompressed) bytes per sample (per channel) */ long blocksWritten = 0; sox_bool isExtensible = sox_false; /* WAVE_FORMAT_EXTENSIBLE? */ dwSamplesPerSecond = ft->signal.rate; wChannels = ft->signal.channels; /* Check to see if encoding is ADPCM or not. If ADPCM * possibly override the size to be bytes. It isn't needed * by this routine will look nicer (and more correct) * on verbose output. */ if ((ft->signal.encoding == SOX_ENCODING_ADPCM || ft->signal.encoding == SOX_ENCODING_IMA_ADPCM || ft->signal.encoding == SOX_ENCODING_GSM) && ft->signal.size != SOX_SIZE_BYTE) { sox_report("Overriding output size to bytes for compressed data."); ft->signal.size = SOX_SIZE_BYTE; } switch (ft->signal.size) { case SOX_SIZE_BYTE: wBitsPerSample = 8; if (ft->signal.encoding != SOX_ENCODING_UNSIGNED && ft->signal.encoding != SOX_ENCODING_ULAW && ft->signal.encoding != SOX_ENCODING_ALAW && ft->signal.encoding != SOX_ENCODING_GSM && ft->signal.encoding != SOX_ENCODING_ADPCM && ft->signal.encoding != SOX_ENCODING_IMA_ADPCM) { sox_report("Do not support %s with 8-bit data. Forcing to unsigned",sox_encodings_str[(unsigned char)ft->signal.encoding]); ft->signal.encoding = SOX_ENCODING_UNSIGNED; } break; case SOX_SIZE_16BIT: wBitsPerSample = 16; if (ft->signal.encoding != SOX_ENCODING_SIGN2) { sox_report("Do not support %s with 16-bit data. Forcing to Signed.",sox_encodings_str[(unsigned char)ft->signal.encoding]); ft->signal.encoding = SOX_ENCODING_SIGN2; } break; case SOX_SIZE_24BIT: wBitsPerSample = 24; if (ft->signal.encoding != SOX_ENCODING_SIGN2) { sox_report("Do not support %s with 24-bit data. Forcing to Signed.",sox_encodings_str[(unsigned char)ft->signal.encoding]); ft->signal.encoding = SOX_ENCODING_SIGN2; } break; case SOX_SIZE_32BIT: wBitsPerSample = 32; if (ft->signal.encoding != SOX_ENCODING_SIGN2 && ft->signal.encoding != SOX_ENCODING_FLOAT) { sox_report("Do not support %s with 32-bit data. Forcing to Signed.",sox_encodings_str[(unsigned char)ft->signal.encoding]); ft->signal.encoding = SOX_ENCODING_SIGN2; } break; default: sox_report("Do not support %s data in WAV files. Forcing to Signed 16-bit.",sox_sizes_str[(unsigned char)ft->signal.size]); ft->signal.encoding = SOX_ENCODING_SIGN2; ft->signal.size = SOX_SIZE_16BIT; wBitsPerSample = 16; break; } wSamplesPerBlock = 1; /* common default for PCM data */ switch (ft->signal.encoding) { case SOX_ENCODING_UNSIGNED: case SOX_ENCODING_SIGN2: wFormatTag = WAVE_FORMAT_PCM; bytespersample = (wBitsPerSample + 7)/8; wBlockAlign = wChannels * bytespersample; break; case SOX_ENCODING_FLOAT: wFormatTag = WAVE_FORMAT_IEEE_FLOAT; bytespersample = (wBitsPerSample + 7)/8; wBlockAlign = wChannels * bytespersample; break; case SOX_ENCODING_ALAW: wFormatTag = WAVE_FORMAT_ALAW; wBlockAlign = wChannels; break; case SOX_ENCODING_ULAW: wFormatTag = WAVE_FORMAT_MULAW; wBlockAlign = wChannels; break; case SOX_ENCODING_IMA_ADPCM: if (wChannels>16) { sox_fail_errno(ft,SOX_EOF,"Channels(%d) must be <= 16",wChannels); return SOX_EOF; } wFormatTag = WAVE_FORMAT_IMA_ADPCM; wBlockAlign = wChannels * 256; /* reasonable default */ wBitsPerSample = 4; wExtSize = 2; wSamplesPerBlock = ImaSamplesIn(0, wChannels, wBlockAlign, 0); break; case SOX_ENCODING_ADPCM: if (wChannels>16) { sox_fail_errno(ft,SOX_EOF,"Channels(%d) must be <= 16",wChannels); return SOX_EOF; } wFormatTag = WAVE_FORMAT_ADPCM; wBlockAlign = wChannels * 128; /* reasonable default */ wBitsPerSample = 4; wExtSize = 4+4*7; /* Ext fmt data length */ wSamplesPerBlock = AdpcmSamplesIn(0, wChannels, wBlockAlign, 0); break; case SOX_ENCODING_GSM: if (wChannels!=1) { sox_report("Overriding GSM audio from %d channel to 1",wChannels); wChannels = ft->signal.channels = 1; } wFormatTag = WAVE_FORMAT_GSM610; /* dwAvgBytesPerSec = 1625*(dwSamplesPerSecond/8000.)+0.5; */ wBlockAlign=65; wBitsPerSample=0; /* not representable as int */ wExtSize=2; /* length of format extension */ wSamplesPerBlock = 320; break; default: break; } wav->formatTag = wFormatTag; wav->blockAlign = wBlockAlign; wav->samplesPerBlock = wSamplesPerBlock; if (!second_header) { /* adjust for blockAlign */ blocksWritten = dwDataLength/wBlockAlign; dwDataLength = blocksWritten * wBlockAlign; dwSamplesWritten = blocksWritten * wSamplesPerBlock; } else { /* fixup with real length */ dwSamplesWritten = wav->numSamples; switch(wFormatTag) { case WAVE_FORMAT_ADPCM: case WAVE_FORMAT_IMA_ADPCM: dwDataLength = wav->dataLength; break; case WAVE_FORMAT_GSM610: /* intentional case fallthrough! */ default: blocksWritten = (dwSamplesWritten+wSamplesPerBlock-1)/wSamplesPerBlock; dwDataLength = blocksWritten * wBlockAlign; } } if (wFormatTag == WAVE_FORMAT_GSM610) dwDataLength = (dwDataLength+1) & ~1u; /*round up to even */ if ((wFormatTag == WAVE_FORMAT_PCM && wBitsPerSample > 16) || wChannels > 2) { isExtensible = sox_true; wFmtSize += 2 + 22; } else if (wFormatTag != WAVE_FORMAT_PCM) wFmtSize += 2+wExtSize; /* plus ExtData */ wRiffLength = 4 + (8+wFmtSize) + (8+dwDataLength); if (wFormatTag != WAVE_FORMAT_PCM) /* PCM omits the "fact" chunk */ wRiffLength += (8+dwFactSize); /* dwAvgBytesPerSec <-- this is BEFORE compression, isn't it? guess not. */ dwAvgBytesPerSec = (double)wBlockAlign*ft->signal.rate / (double)wSamplesPerBlock + 0.5; /* figured out header info, so write it */ /* If user specified opposite swap than we think, assume they are * asking to write a RIFX file. */ if (ft->signal.reverse_bytes != SOX_IS_BIGENDIAN) { if (!second_header) sox_report("Requested to swap bytes so writing RIFX header"); sox_writes(ft, "RIFX"); } else sox_writes(ft, "RIFF"); sox_writedw(ft, wRiffLength); sox_writes(ft, "WAVE"); sox_writes(ft, "fmt "); sox_writedw(ft, wFmtSize); sox_writew(ft, isExtensible ? WAVE_FORMAT_EXTENSIBLE : wFormatTag); sox_writew(ft, wChannels); sox_writedw(ft, dwSamplesPerSecond); sox_writedw(ft, dwAvgBytesPerSec); sox_writew(ft, wBlockAlign); sox_writew(ft, wBitsPerSample); /* end info common to all fmts */ if (isExtensible) { size_t i; static const unsigned char guid[14] = "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71"; sox_writew(ft, 22); sox_writew(ft, wBitsPerSample); /* No padding in container */ sox_writedw(ft, 0); /* Speaker mapping not specified */ sox_writew(ft, wFormatTag); for (i = 0; i < array_length(guid); ++i) { sox_writeb(ft, guid[i]); } } else /* if not PCM, we need to write out wExtSize even if wExtSize=0 */ if (wFormatTag != WAVE_FORMAT_PCM) sox_writew(ft,wExtSize); switch (wFormatTag) { int i; case WAVE_FORMAT_IMA_ADPCM: sox_writew(ft, wSamplesPerBlock); break; case WAVE_FORMAT_ADPCM: sox_writew(ft, wSamplesPerBlock); sox_writew(ft, 7); /* nCoefs */ for (i=0; i<7; i++) { sox_writew(ft, (uint16_t)(iCoef[i][0])); sox_writew(ft, (uint16_t)(iCoef[i][1])); } break; case WAVE_FORMAT_GSM610: sox_writew(ft, wSamplesPerBlock); break; default: break; } /* if not PCM, write the 'fact' chunk */ if (isExtensible || wFormatTag != WAVE_FORMAT_PCM){ sox_writes(ft, "fact"); sox_writedw(ft,dwFactSize); sox_writedw(ft,dwSamplesWritten); } sox_writes(ft, "data"); sox_writedw(ft, dwDataLength); /* data chunk size */ if (!second_header) { sox_debug("Writing Wave file: %s format, %d channel%s, %d samp/sec", wav_format_str(wFormatTag), wChannels, wChannels == 1 ? "" : "s", dwSamplesPerSecond); sox_debug(" %d byte/sec, %d block align, %d bits/samp", dwAvgBytesPerSec, wBlockAlign, wBitsPerSample); } else { sox_debug("Finished writing Wave file, %u data bytes %u samples", dwDataLength,wav->numSamples); if (wFormatTag == WAVE_FORMAT_GSM610){ sox_debug("GSM6.10 format: %u blocks %u padded samples %u padded data bytes", blocksWritten, dwSamplesWritten, dwDataLength); if (wav->gsmbytecount != dwDataLength) sox_warn("help ! internal inconsistency - data_written %u gsmbytecount %u", dwDataLength, wav->gsmbytecount); } } return SOX_SUCCESS; } static sox_size_t sox_wavwrite(sox_format_t * ft, const sox_ssample_t *buf, sox_size_t len) { wav_t wav = (wav_t) ft->priv; sox_ssize_t total_len = len; ft->sox_errno = SOX_SUCCESS; switch (wav->formatTag) { case WAVE_FORMAT_IMA_ADPCM: case WAVE_FORMAT_ADPCM: while (len>0) { short *p = wav->samplePtr; short *top = wav->sampleTop; if (top>p+len) top = p+len; len -= top-p; /* update residual len */ while (p < top) *p++ = (*buf++) >> 16; wav->samplePtr = p; if (p == wav->sampleTop) xxxAdpcmWriteBlock(ft); } return total_len - len; break; case WAVE_FORMAT_GSM610: len = wavgsmwrite(ft, buf, len); wav->numSamples += (len/ft->signal.channels); return len; break; default: len = sox_rawwrite(ft, buf, len); wav->numSamples += (len/ft->signal.channels); return len; } } static int sox_wavstopwrite(sox_format_t * ft) { wav_t wav = (wav_t) ft->priv; ft->sox_errno = SOX_SUCCESS; /* Call this to flush out any remaining data. */ switch (wav->formatTag) { case WAVE_FORMAT_IMA_ADPCM: case WAVE_FORMAT_ADPCM: xxxAdpcmWriteBlock(ft); break; case WAVE_FORMAT_GSM610: wavgsmstopwrite(ft); break; } free(wav->packet); free(wav->samples); free(wav->iCoefs); /* Flush any remaining data */ if (wav->formatTag != WAVE_FORMAT_IMA_ADPCM && wav->formatTag != WAVE_FORMAT_ADPCM && wav->formatTag != WAVE_FORMAT_GSM610) sox_rawstopwrite(ft); /* All samples are already written out. */ /* If file header needs fixing up, for example it needs the */ /* the number of samples in a field, seek back and write them here. */ if (!ft->seekable) return SOX_EOF; if (sox_seeki(ft, 0, SEEK_SET) != 0) { sox_fail_errno(ft,SOX_EOF,"Can't rewind output file to rewrite .wav header."); return SOX_EOF; } return (wavwritehdr(ft, 1)); } /* * Return a string corresponding to the wave format type. */ static char *wav_format_str(unsigned wFormatTag) { switch (wFormatTag) { case WAVE_FORMAT_UNKNOWN: return "Microsoft Official Unknown"; case WAVE_FORMAT_PCM: return "Microsoft PCM"; case WAVE_FORMAT_ADPCM: return "Microsoft ADPCM"; case WAVE_FORMAT_IEEE_FLOAT: return "IEEE Float"; case WAVE_FORMAT_ALAW: return "Microsoft A-law"; case WAVE_FORMAT_MULAW: return "Microsoft U-law"; case WAVE_FORMAT_OKI_ADPCM: return "OKI ADPCM format."; case WAVE_FORMAT_IMA_ADPCM: return "IMA ADPCM"; case WAVE_FORMAT_DIGISTD: return "Digistd format."; case WAVE_FORMAT_DIGIFIX: return "Digifix format."; case WAVE_FORMAT_DOLBY_AC2: return "Dolby AC2"; case WAVE_FORMAT_GSM610: return "GSM 6.10"; case WAVE_FORMAT_ROCKWELL_ADPCM: return "Rockwell ADPCM"; case WAVE_FORMAT_ROCKWELL_DIGITALK: return "Rockwell DIGITALK"; case WAVE_FORMAT_G721_ADPCM: return "G.721 ADPCM"; case WAVE_FORMAT_G728_CELP: return "G.728 CELP"; case WAVE_FORMAT_MPEG: return "MPEG"; case WAVE_FORMAT_MPEGLAYER3: return "MPEG Layer 3"; case WAVE_FORMAT_G726_ADPCM: return "G.726 ADPCM"; case WAVE_FORMAT_G722_ADPCM: return "G.722 ADPCM"; default: return "Unknown"; } } static int sox_wavseek(sox_format_t * ft, sox_size_t offset) { wav_t wav = (wav_t) ft->priv; int new_offset, channel_block, alignment; switch (wav->formatTag) { case WAVE_FORMAT_IMA_ADPCM: case WAVE_FORMAT_ADPCM: sox_fail_errno(ft,SOX_ENOTSUP,"ADPCM not supported"); break; case WAVE_FORMAT_GSM610: { sox_size_t gsmoff; /* rounding bytes to blockAlign so that we * don't have to decode partial block. */ gsmoff = offset * wav->blockAlign / wav->samplesPerBlock + wav->blockAlign * ft->signal.channels / 2; gsmoff -= gsmoff % (wav->blockAlign * ft->signal.channels); ft->sox_errno = sox_seeki(ft, (sox_ssize_t)(gsmoff + wav->dataStart), SEEK_SET); if (ft->sox_errno != SOX_SUCCESS) return SOX_EOF; /* offset is in samples */ new_offset = offset; alignment = offset % wav->samplesPerBlock; if (alignment != 0) new_offset += (wav->samplesPerBlock - alignment); wav->numSamples = ft->length - (new_offset / ft->signal.channels); } break; default: new_offset = offset * ft->signal.size; /* Make sure request aligns to a channel block (ie left+right) */ channel_block = ft->signal.channels * ft->signal.size; alignment = new_offset % channel_block; /* Most common mistaken is to compute something like * "skip everthing upto and including this sample" so * advance to next sample block in this case. */ if (alignment != 0) new_offset += (channel_block - alignment); new_offset += wav->dataStart; ft->sox_errno = sox_seeki(ft, new_offset, SEEK_SET); if( ft->sox_errno == SOX_SUCCESS ) wav->numSamples = (ft->length / ft->signal.channels) - (new_offset / ft->signal.size / ft->signal.channels); } return(ft->sox_errno); } /* Microsoft RIFF */ static const char *wavnames[] = { "wav", NULL }; static sox_format_handler_t sox_wav_format = { wavnames, SOX_FILE_SEEK | SOX_FILE_LIT_END, sox_wavstartread, sox_wavread, sox_wavstopread, sox_wavstartwrite, sox_wavwrite, sox_wavstopwrite, sox_wavseek }; const sox_format_handler_t *sox_wav_format_fn(void); const sox_format_handler_t *sox_wav_format_fn() { return &sox_wav_format; }