Hi does anyone know how to find the amplitudes within a WAV file in Java? If the file was stereo (or has more channels) how can the data be put into arrays?
Thanks!
Hi does anyone know how to find the amplitudes within a WAV file in Java? If the file was stereo (or has more channels) how can the data be put into arrays?
Thanks!
Processing a WAV file header
Next trick is a bit more of a challenge as the internal data format could be a variety of data types. If you are looking at your classic windows WAV file, it is probably just PCM 16 bit or maybe 8 bit. Which, means, you can easily load the data into a byte or short array.
However, you will find other formats. When you know the type you have, google it. You'll find information for most.
How to open a WAVE from inputStream
// The WAVE-File-Reader of Java needs to reset on marks
final InputStream markSupportedInputStream;
if (inputStream.markSupported()) {
markSupportedInputStream = inputStream;
} else {
// BufferedInputStream wraps an InputStream, buffers the read data
// and so it can reset on marks
// Including RIFF header, format chunk and data chunk, standard
// WAVE files have an overall header size of 44 bytes. 8192 Bytes should
// be enough. Unconsidered are untypically chucks, like cue chunk,
// playlist chunk etc.
final int bufferSize = 8192;
markSupportedInputStream = new BufferedInputStream(inputStream,
bufferSize);
}
final AudioInputStream stream;
try {
stream = AudioSystem.getAudioInputStream(markSupportedInputStream);
} catch (final UnsupportedAudioFileException e) {
throw new UnsuportedFormatException();
}
final AudioFormat format = stream.getFormat();
final int numChannels = format.getChannels();
After that, a typicall WAVE file is PCM encoded (there a other codecs, like floats). You have to read the samples from markSupportedInputStream
.
PCM includes many combinations of parameters: (Mono|Stereo), (Signed|Unsigned), (8 Bit|16 Bit), (Big Endian|Little Endian for more than 8 Bit). You can figure out this on the format
object like format.getChannels()
. For this reason I have written a PcmCodec
class with methods like decodeUnsigned16BitLittleEndian(buffer, offset)
. And I normalize the sample values to [-1,1].
Here is how I figure out what PCM it is:
public static boolean isAudioFormatSupported(
final @NonNull AudioFormat format) {
final Encoding encoding = format.getEncoding();
final int numChannels = format.getChannels();
final int sampleSizeBits = format.getSampleSizeInBits();
final boolean encodingSupported = (encoding == Encoding.PCM_SIGNED || encoding == Encoding.PCM_UNSIGNED);
final boolean channelsSupported = (numChannels == AudioSystem.NOT_SPECIFIED
|| numChannels == 1 || numChannels == 2);
final boolean sampleSizeSupported = (sampleSizeBits == AudioSystem.NOT_SPECIFIED
|| sampleSizeBits == 8 || sampleSizeBits == 16);
return encodingSupported && channelsSupported && sampleSizeSupported;
}
@NonNull
private static Format toInternalFormat(final @NonNull AudioFormat audioFormat) {
final Format internalFormat;
if (audioFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) {
switch (audioFormat.getSampleSizeInBits()) {
case 8:
internalFormat = Format.SIGNED_8_BIT;
break;
case 16:
case AudioSystem.NOT_SPECIFIED:
if (audioFormat.isBigEndian()) {
internalFormat = Format.SIGNED_16_BIT_BIG_ENDIAN;
} else {
internalFormat = Format.SIGNED_16_BIT_LITTLE_ENDIAN;
}
break;
default:
throw new AssertionError(audioFormat.getSampleSizeInBits()
+ " Bit not supported");
}
} else if (audioFormat.getEncoding().equals(
AudioFormat.Encoding.PCM_UNSIGNED)) {
switch (audioFormat.getSampleSizeInBits()) {
case 8:
internalFormat = Format.UNSIGNED_8_BIT;
break;
case 16:
case AudioSystem.NOT_SPECIFIED:
if (audioFormat.isBigEndian()) {
internalFormat = Format.UNSIGNED_16_BIT_BIG_ENDIAN;
} else {
internalFormat = Format.UNSIGNED_16_BIT_LITTLE_ENDIAN;
}
break;
default:
throw new AssertionError(audioFormat.getSampleSizeInBits()
+ " Bit not supported");
}
} else {
throw new AssertionError("Neither PCM_SIGNED nor PCM_UNSIGNED");
}
return internalFormat;
}
Here is an example how I decode a special PCM:
You need to read from markSupportedInputStream
into a byte array (buffer). After that, you can decode the bytes:
public float decodeMono(final @NonNull byte[] buffer, final int offset) {
final float sample;
switch (format) {
case SIGNED_8_BIT:
sample = decodeSigned8Bit(buffer, offset);
break;
case UNSIGNED_8_BIT:
sample = decodeUnsigned8Bit(buffer, offset);
break;
case SIGNED_16_BIT_BIG_ENDIAN:
sample = decodeSigned16BitBigEndian(buffer, offset);
break;
case SIGNED_16_BIT_LITTLE_ENDIAN:
sample = decodeSigned16BitLittleEndian(buffer, offset);
break;
case UNSIGNED_16_BIT_BIG_ENDIAN:
sample = decodeUnsigned16BitBigEndian(buffer, offset);
break;
case UNSIGNED_16_BIT_LITTLE_ENDIAN:
sample = decodeUnsigned16BitLittleEndian(buffer, offset);
break;
default:
throw new AssertionError();
}
return Util.clamp(sample, -1f, 1f);
}
private static float decodeUnsigned16BitBigEndian(
final @NonNull byte[] buffer, final int offset) {
final byte lower, higher;
higher = buffer[offset];
lower = buffer[offset + 1];
final int sampleInt = ((higher & 0xff) << 8 | lower & 0xff) - 0x8000;
final float sample = (float) sampleInt / (float) 0x7fff;
return sample;
}