Converting the sample rate on-the-fly when reading a WAV file into a samples array with Java
Asked Answered
I

2

8

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?

Irritate answered 16/2, 2010 at 20:19 Comment(0)
R
8

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.

Riding answered 16/2, 2010 at 23:33 Comment(4)
How do I calculate the cut-off frequency? And why is this step necessary?Irritate
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.Seasonseasonable
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.Seasonseasonable
Hey Paul please can you take a look on my very similar question #51128847Biysk
C
6

I believe the accepted answer answers another question -- it solves the same problem (downsampling the audio) but in another way (manually in stead of using the java sound API). I had the same thing and dug into it.

The correct way (or java sound API way) to do this is indeed (as suggested in http://docs.oracle.com/javase/tutorial/sound/converters.html)

AudioFormat outDataFormat = new AudioFormat((float) 8000.0, (int) 8, (int) 1, true, false);
AudioInputStream lowResAIS = AudioSystem.getAudioInputStream(outDataFormat, inFileAIS);

Problem is that standard java doesn't ship with resampling (or even stereo-mono conversion) code (or at least not in that part of the code -- see http://www.jsresources.org/faq_audio.html#convert_sample_rate).

The jsresources pages point to the answers as well: simply installing 2 plugins does the trick. Easiest is to install these plugins in the Extensions directory, on OSX Lion this will do the trick (provided you have wget):

wget http://www.tritonus.org/tritonus_share-0.3.6.jar -O /Library/Java/Extensions/tritonus_share-0.3.6.jar
wget http://www.tritonus.org/tritonus_remaining-0.3.6.jar -O /Library/Java/Extensions/tritonus_remaining-0.3.6.jar

After adding these 2 jar files, everything worked (just one extra warning: if you want to change both the number of channels and the sample rate, it's still not supported as one step).

Customs answered 13/2, 2012 at 18:55 Comment(3)
Claude please can you take a look at a very similar question i have #51128847, i am searching years for an answer...Biysk
@Biysk I’m afraid it has been years since I checked the java sound libraries, I expect to be not much of a help. SorryCustoms
It hasn't change much really, almost nothing and just i need the code you used in this answer i have added the 2 libraries :)Biysk

© 2022 - 2024 — McMap. All rights reserved.