Searching the web for weeks i came to the following code for calculating the amplitudes of a given
.wav
file in Java . Now the problem is it doesn't scale well at all with big audio files like let's say 30 minutes , the produces array is huge.
To draw it i am using JavaFX created this repository (ps the code might be different there due to i have not committed some days now).
So :
/**
* Get Wav Amplitudes
*
* @param file
* @return
* @throws UnsupportedAudioFileException
* @throws IOException
*/
private int[] getWavAmplitudes(File file) throws UnsupportedAudioFileException , IOException {
System.out.println("Calculting WAV amplitudes");
int[] amplitudes = null;
//Get Audio input stream
try (AudioInputStream input = AudioSystem.getAudioInputStream(file)) {
AudioFormat baseFormat = input.getFormat();
Encoding encoding = AudioFormat.Encoding.PCM_UNSIGNED;
float sampleRate = baseFormat.getSampleRate();
int numChannels = baseFormat.getChannels();
AudioFormat decodedFormat = new AudioFormat(encoding, sampleRate, 16, numChannels, numChannels * 2, sampleRate, false);
int available = input.available();
amplitudes = new int[available];
//Get the PCM Decoded Audio Input Stream
try (AudioInputStream pcmDecodedInput = AudioSystem.getAudioInputStream(decodedFormat, input)) {
final int BUFFER_SIZE = 4096; //this is actually bytes
System.out.println(available);
//Create a buffer
byte[] buffer = new byte[BUFFER_SIZE];
//Read all the available data on chunks
int counter = 0;
while (pcmDecodedInput.readNBytes(buffer, 0, BUFFER_SIZE) > 0)
for (int i = 0; i < buffer.length - 1; i += 2, counter += 2) {
if (counter == available)
break;
amplitudes[counter] = ( ( buffer[i + 1] << 8 ) | buffer[i] & 0xff ) << 16;
amplitudes[counter] /= 32767;
amplitudes[counter] *= WAVEFORM_HEIGHT_COEFFICIENT;
}
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (Exception ex) {
ex.printStackTrace();
}
//System.out.println("Finished Calculting amplitudes");
return amplitudes;
}
And then i process the amplitudes like this :
/**
* Process the amplitudes
*
* @param sourcePcmData
* @return An array with amplitudes
*/
private float[] processAmplitudes(int[] sourcePcmData) {
System.out.println("Processing WAV amplitudes");
//The width of the resulting waveform panel
int width = waveVisualization.width;
System.out.println("P Width :" + width);
float[] waveData = new float[width];
int samplesPerPixel = sourcePcmData.length / width;
//Calculate
float nValue;
for (int w = 0; w < width; w++) {
//if (isCancelled())
// break;
//For performance keep it here
int c = w * samplesPerPixel;
nValue = 0.0f;
//Keep going
for (int s = 0; s < samplesPerPixel; s++) {
//if (isCancelled())
// break;
nValue += ( Math.abs(sourcePcmData[c + s]) / 65536.0f );
}
//Set WaveData
waveData[w] = nValue / samplesPerPixel;
}
System.out.println("Finished Processing amplitudes");
return waveData;
}
The output is this :
Found a really good solution , though i am not sure what the final array maximum size should be , after some experiments 100.000
seems a good number.
So the method getWavAmplitudes becomes :
/**
* Get Wav Amplitudes
*
* @param file
* @return
* @throws UnsupportedAudioFileException
* @throws IOException
*/
private int[] getWavAmplitudes(File file) throws UnsupportedAudioFileException , IOException {
//Get Audio input stream
try (AudioInputStream input = AudioSystem.getAudioInputStream(file)) {
AudioFormat baseFormat = input.getFormat();
//Encoding
Encoding encoding = AudioFormat.Encoding.PCM_UNSIGNED;
float sampleRate = baseFormat.getSampleRate();
int numChannels = baseFormat.getChannels();
AudioFormat decodedFormat = new AudioFormat(encoding, sampleRate, 16, numChannels, numChannels * 2, sampleRate, false);
int available = input.available();
//Get the PCM Decoded Audio Input Stream
try (AudioInputStream pcmDecodedInput = AudioSystem.getAudioInputStream(decodedFormat, input)) {
final int BUFFER_SIZE = 4096; //this is actually bytes
//Create a buffer
byte[] buffer = new byte[BUFFER_SIZE];
//Now get the average to a smaller array
int maximumArrayLength = 100000;
int[] finalAmplitudes = new int[maximumArrayLength];
int samplesPerPixel = available / maximumArrayLength;
//Variables to calculate finalAmplitudes array
int currentSampleCounter = 0;
int arrayCellPosition = 0;
float currentCellValue = 0.0f;
//Variables for the loop
int arrayCellValue = 0;
//Read all the available data on chunks
while (pcmDecodedInput.readNBytes(buffer, 0, BUFFER_SIZE) > 0)
for (int i = 0; i < buffer.length - 1; i += 2) {
//Calculate the value
arrayCellValue = (int) ( ( ( ( ( buffer[i + 1] << 8 ) | buffer[i] & 0xff ) << 16 ) / 32767 ) * WAVEFORM_HEIGHT_COEFFICIENT );
//Every time you him [currentSampleCounter=samplesPerPixel]
if (currentSampleCounter != samplesPerPixel) {
++currentSampleCounter;
currentCellValue += Math.abs(arrayCellValue);
} else {
//Avoid ArrayIndexOutOfBoundsException
if (arrayCellPosition != maximumArrayLength)
finalAmplitudes[arrayCellPosition] = finalAmplitudes[arrayCellPosition + 1] = (int) currentCellValue / samplesPerPixel;
//Fix the variables
currentSampleCounter = 0;
currentCellValue = 0;
arrayCellPosition += 2;
}
}
return finalAmplitudes;
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (Exception ex) {
ex.printStackTrace();
}
//You don't want this to reach here...
return new int[1];
}
Any recommendation and improvements very welcome .