I have a WAV file here: http://uppit.com/slpmuzpywxhs/202.wav
and I need to construct a Java AudioFormat object based on properties of that sound file using the following the parameters:
float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian.
So my question is: How can I analyze that WAV file in order to determine those values?
EDIT: Solution found thanks to jaket!
You can look at the wave header to determine that the format is:
22050 sample rate
8 bits per sample
endianess - irrelevant for 8-bits but may as well say little endian.
1 channel
I found this info by downloading the file, right clicking and selecting "Get Info" on the mac. There are lots of other ways to find this out.
If I were writing code for this though I probably wouldn't want to hard code the values though. Google found me this:
AudioInputStream stream = AudioSystem.getAudioInputStream("202.wav");
AudioFormat format = stream.getFormat();
Related
I am trying to figure out how to use the java.sound api to convert a WAV file to RAW/PCM format. I need to convert the WAV file from some given audio format to a RAW file encoded as follows:
16 kHz
16 bits
Mono
Signed
Little endian
Many of the posts I found here are for the other way around, how to go from RAW and get a WAV. Being new to audio, I was hoping someone could post an example showing how to do this.
The Java sound api does not convert to RAW audio as far as I know. It does convert to WAV, which is RAW audio with a 44-byte header. Once you understand this, you can break the problem down into 2 parts:
1. Convert audio from any format to a WAV format.
The code example below has only been tested from WAV to WAV (different audio format), but in theory the call to AudioSystem.getAudioInputStream(audioFormat, originalAudioStream); should find the appropriate codec if it has it.
This method will convert audio bytes to the given format and produce a byte[] result as a WAV.
private static final AudioFormat EXAMPLE_FORMAT = new AudioFormat(
16_000,
16,
1,
true,
false
);
public byte[] formatAudioToWav(#NotNull final byte[] audioFileContent,
#NotNull final AudioFormat audioFormat) throws
IOException,
UnsupportedAudioFileException {
try (
final AudioInputStream originalAudioStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioFileContent));
final AudioInputStream formattedAudioStream = AudioSystem.getAudioInputStream(audioFormat, originalAudioStream);
final AudioInputStream lengthAddedAudioStream = new AudioInputStream(formattedAudioStream, audioFormat, audioFileContent.length);
final ByteArrayOutputStream convertedOutputStream = new ByteArrayOutputStream()
) {
AudioSystem.write(lengthAddedAudioStream, AudioFileFormat.Type.WAVE, convertedOutputStream);
return convertedOutputStream.toByteArray();
}
}
2. Strip the header from the WAV file created in step 1.
public byte[] formatWavToRaw(#NotNull final byte[] audioFileContent) {
return Arrays.copyOfRange(audioFileContent, 44, audioFileContent.length);
}
Notes
The code has the advantage of properly closing all the streams, but it has the disadvantage of working with byte[] directly, so it won't work well with large files. It can be converted to pure streams, but notice you need the length of one of the streams in the call to new AudioInputStream(formattedAudioStream, audioFormat, audioFileContent.length);.
Another disadvantage is that even if the code is legible, there are 3 AudioInputStream's that need to be created/wrapped to make this work, which is somewhat convoluted. I was unable to find a better solution.
Further Reading
What is raw audio?
Are there usage examples?
Do we lose precision if we upsample/downsample? A, B
Is there an online converter to compare results with?
You can easily obtain the PCM data (in raw bytes) from a WAV file by reading it with an AudioInputStream, storing the data in a ByteBuffer perhaps.
Java does have some built in format converters. I'm not sure if the particular conversions you want are supported. Documentation about supported format conversions is at Audio Trail: Using Files and Format Converters
If you have to do some sort of decimation, for example, to go from a wav with 44.1kHz sample rate to 16kHz, I would guess using linear interpolation would be fine.
convert the incoming bytes to PCM data values (perhaps normalizing to floats)
iterate through the resulting array by increments of 2.76625 (derived from 44.1 / 16), using linear interpolation to get your new values
convert the new PCM values back to bytes
The resulting byte array can be written to a file using a FileOutputStream, if there isn't any header info. Java Tutorials: Byte Streams
Linear interpolation with float precision is generally considered good enough for preserving audio fidelity.
I have an AudioInputStream audioInput which the object itself has encoding as ULAW
System.out.println( audioInput.getFormat().getEncoding() );
Which return ULAW encoding in the console.
ULAW
However, when I use AudioSystem.getTargetEncodings( audioInput ) it return a different set of Encoding
Encoding availableEncoding[] = AudioSystem.getTargetEncodings( audioIn.getFormat() );
for ( Encoding encode : availableEncoding )
{
System.out.println( encode );
}
which return :
PCM_SIGNED
The thing is, I have to work with a lot of these files, where the encoding of the object and and target encoding doesn't match. These audio files cannot be opened by AudioSystem clip and throw an exception
Clip audioClip = AudioSystem.getClip();
audioClip.open( audioInput );
// this throws error javax.sound.sampled.LineUnavailableException: line with format ULAW 8000.0 Hz, 8 bit, mono, 1 bytes/frame, not supported.
However, if I convert the audio to one of the target encoding
audioInput = AudioSystem.getAudioInputStream( Encoding.PCM_SIGNED , audioInput);
audioClip.open( audioInput ); //it works !!
Although this approach seem to work for all the sound file to work with, I barely have sufficient knowledge of what I am doing. According to the java doc, the AudioSystem.getTargetEncodings description is ( in case it might help ) :
"Obtains the encodings that the system can obtain from an audio input stream with the specified format using the set of installed format converters."
Sorry for the long detail but here are my questions :
Why does java AudioSystem.getTargetEncodings( AudioFormat ) return different result than the encoding of the AudioFormat we provide we provide in the parameter ?
So does this mean, in my case, that the system can perceive and process the file only with PCM_SIGNED encoding even though the encoding of the file itself is ULAW ?
Is my solution legit ? It really bug me a lot to have a solution that seems to work without a good reason
According to what I've learned so far:
Java is looking at the default setup for playback for your system's configuration, not at the format of the audio file. Many (most?) computer systems are set up to play back PCM_SIGNED.
Depends what you mean by "only." There are probably ways to change your system's playback preferences, but I have always just gone with the default, or one of the "supported" options.
The solution is totally legit! The streaming conversions were written for just this circumstance: many audio file types, system that is set up to play a limited subset. They also help if you wish to programmatically convert and save files in a new format.
But for my audio uses (SF/X for game cues--I come across files of different types), I tend to convert them all, externally, using Audacity, prior to packaging them with the game, so as not to have to do any conversion during the game.
I have a few raw PCM audio files. I can successfully read a stream of bytes from these files and play them through an audio playing mechanism which accepts PCM data as input.
When i read data from these files, i store it in byte[]. These tracks have the same size and are complementary in terms of sound (they sound good together). Therefore, I want to add several byte[] containing PCM data into a single byte[] of the same size, representing the final music.
I tried it in an easy thoughtless manner by simply doing it like this:
for(int i=0; i<finalbytes.length; i++)
{
finalbytes[i] = (byte) (music1bytes[i] + music2bytes[i]);
}
It actually wasn't that bad. The final sound is indeed an addition of both tracks. The problem is, when a few tracks are added, sometimes in specific parts of the song, peaks of static noise can be heard. It is probably due to the addition resulting in non-clamped values or something, which I don't know how to solve.
So, how to add two or more byte arrays of PCM data?
I'm assuming the samples of these raw audio files are 8 bit signed.
What's happening is overflow. If both samples add up to greater than 127 or less than -128 you won't get the correct result - you get integer overflow.
You could divide each resulting sample by 2:
finalbytes[i] = (byte) ((music1bytes[i] + music2bytes[i]) / 2);
This way, even if each audio file has a maximum sample value you will not get overflow. Disadvantage is that resulting file might be a bit quiet.
Another option is to clip:
int sample = music1bytes[i] + music2bytes[i];
sample = Math.min(sample, Byte.MAX_VALUE);
sample = Math.max(sample, Byte.MIN_VALUE);
finalbytes[i] = (byte)sample;
If both audio sources are pretty loud then there might be a lot of clipping and it mightn't sound that great.
You could also try using SoftMixingMixer from JavaSound and let it do the mixing for you. Might actually be a lot more work this way since you'd need to define the audio format of the RAW audio files, but it likely will give the best sounding result. With this option, you'll need to use openStream(AudioFormat) with the audio format of the output file and tell the mixer to play your 2 RAW audio files through lines.
Trying to pipe an AudioInputStream I got from a libary to this process:
oggenc - -o wtf.ogg
via
AudioSystem.write(audio, AudioFileFormat::Type::WAVE, process.getOutputStream());
gives me the error:
IOException
stream length not specified
file: com.sun.media.sound.WaveFileWriter.write(WaveFileWriter.java)
line: 129
So it seems audio stream length needs to be specified. This is a 44.8 kHz stereo PCM format audio with no length specified. I got this from a library so I can't modify their code. I tried creating another AudioInputStream from this stream, but I couldn't find a proper length. So how can I specify the length ?
So, I found that you are reading audio from somewhere unknown, and then trying to write it into WAV format. But WAV format requires to write header, which should contain file length. Anyway WaveFileWriter throws exception if feeded by stream without knowing length.
I guess your direction is Java -> oggenc.
So, you are to learn oggenc and know if it accepts headless WAV stream. If so, then just pass your audio to output stream, without processing with AudioSystem.write() which is for headed WAVs
According to here http://www.linuxcommand.org/man_pages/oggenc1.html you are able to accept RAW with oggenc.
You will be able to solve the issue with length if you reimplement AudioSystem.write method. It doesn't work with stream and WAVE format because of data length in the header. You should write audio data to some array and then prepare correct WAVE Header. Example: How to record audio to byte array
This question already has answers here:
How do I use audio sample data from Java Sound?
(2 answers)
Closed 7 years ago.
I'm trying to read a WAV file (and in the future also MP3 and Ogg) file as an array of floats in Java, much like libsndfile in C.
I'm looking at the various documentation in the javax.sampled packages, but I can't find a clear explanation on how to do that; I'd like to avoid recurring to external libraries if possible.
Any suggestion?
JavaSound will only give you "raw" access to the sound data embedded in the WAV file, e.g. 8 bit unsigned PCM or 16 bit signed PCM data (if your WAV files are PCM encoded). In either case, the value of each sample is in the range 0 to 255 or -32768 to 32767 and you simply have to shift the range to fit in -1f to 1f.
You should start with AudioSystem.getAudioInputStream(...) using an appropriate source (File, InputStream or URL), examine the format to check the sample size and the number of channels and then you can read and convert the PCM encoded data from the AudioInputStream accordingly.
found this page:
http://www.jsresources.org/faq_audio.html
see in particular 9.4: How can I reconstruct sample values from a byte array?
It works as expected, by comparing java's output with c++ (libsndfile) and matlab (wavread).
I'm going to put the code on m github account when I have time