views:

514

answers:

1

I've got a collection of short WAV files that I would like to process in Java using various digital signal processing algorithms. I need to get an array of int valued samples for this purpose, encoded at the 11025 Hz frame rate.

The source files have several different sample rates, including 11025 Hz and 44100 Hz. Here's the code I'm trying to use to read them:

// read the WAV file
FileInputStream fileInputStream = new FileInputStream(new File("test.wav"));
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(fileInputStream );

// copy the AudioInputStream to a byte array called buffer
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] data = new byte[4096];
int tempBytesRead = 0;
int byteCounter = 0;
while ((tempBytesRead = audioInputStream.read(data, 0, data.length)) != -1) {
  bos.write(data, 0, tempBytesRead);
            byteCounter += tempBytesRead;
}
bos.close();
byte[] buffer = bos.toByteArray();

AudioFileFormat audioFileFormat = new AudioFileFormat(AudioFileFormat.Type.WAVE, audioInputStream.getFormat(), (int)audioInputStream.getFrameLength());

// get the resulting sample array
int[] samples = new int[audioFileFormat.getFrameLength()];
for (int i = 0; i < samples.length; i++) {
  samples[i] = getSampleValue(i); // the getSampleValue method reads the sample values from the "buffer" array, handling different encoding types like PCM unsigned/signed, mono/stereo, 8 bit/16 bit
}

// RESULT: the "samples" array

The problem is, that the code doesn't handle different sample rates properly. So for the 44100 Hz frame rate I get four times as many samples as for the 11025 Hz frame rate. I would like the resulting sample array to use the 11025 Hz frame rate, regardless of the frame rate of the source file. I tried to force Java to convert the frame rate for me when reading the AudioInputStream, but I get an exception similar to the following one:

java.lang.IllegalArgumentException: Unsupported conversion: PCM_SIGNED 11025.0 Hz, 16 bit, mono, 2 bytes/frame, 44100.0 frames/second, little-endian from PCM_SIGNED 44100.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian
    at javax.sound.sampled.AudioSystem.getAudioInputStream(AudioSystem.java:955)

I read the Java Sound API tutorial: http://java.sun.com/docs/books/tutorial/sound/converters.html. It seems that the Java Sound API doesn't support this kind of conversion of my operating system (Windows 7). And I would like to avoid dependencies on any external libraries. Is there any way to do the sampling rate conversion on my own?

+4  A: 

For sample rates > 11025 Hz you need to downsample, which is a two stage process. First you need to low pass filter to satisfy the Nyquist criterion, and then you can decimate, e.g. for 44.1 kHz sample rate data you would need to low pass filter with a cut-off frequency of 5.5 kHz and then you can throw away 3 out of every 4 samples for a 4:1 downsampling ratio. You'll need a different filter for each downsampling ratio that you want to support.

Paul R
How do I calculate the cut-off frequency? And why is this step necessary?
pako
The filtering is neccissary because of the nyquist effect. In short: if your sr is 11025 hz and your input had a 5572.5 hz tone in it, that would be reproduced as a 60 hz tone. Nyquist wrap is totally non-harmonic (translation: sounds really ugly and bad). You need to filter off all input above half your new sr in order to eliminate nyquist noise.
Justin Smith
and by "filter all input above half your new sr" I mean make sure that there is zero content above that frequency - and the amount of filtering and where you cut it off can vary based on your source material - listen to the result, it will be obvious from the noise added if your filter needs to be steeper or needs a lower cutoff frequency.
Justin Smith