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
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 am trying to read a .wav file, convert it into double array and FFT the array.
I have the recorded .wav file in storage but I have no idea how I can read the file and use the data.
I am a beginner in application development so it would be nice if you could lead me through step by step as well as show some sample code.
Appreciate your help.
I can't give you a full code since it's a long solution and I have other things to do. I can give you hints.
First, check this link as reference.
As you see, a .wav or a WAVE file does not only contain the audio samples but it also contains other metadata that describes the contents of the file. To correctly read the audio samples, you'll need the values of these metadata.
To do this, first instantiate a FileInputstream. You will need a File object representing your .wav file to do that.
Next, you'll need to read each field from top to bottom. As you see from the illustration, the number of bytes for each field is indicated. Use the following code when reading a field.
byte[] bytes = new byte[numOfBytes];
fileInputStream.read(bytes, 0, bytes.length);
// The value of current field is now stored in the bytes array
After each read, the fileInputStream will automatically point to the next field. Just repeat the above code until you have reached the start of the audio samples data.
To interpret the obtained values, you'll have to carefully read the description of each field.
To convert field from byte array to ASCII or String, use:
String value = new String(bytes);
To convert field from byte array to a number, use:
ByteBuffer buffer = ByteBuffer.allocate(numOfBytes);
buffer.order(BIG_ENDIAN); // Check the illustration. If it says little endian, use
// LITTLE_ENDIAN
buffer.put(bytes);
buffer.rewind();
If field consists of two bytes:
Short value = buffer.getShort();
If field consists of 4 bytes:
Int value = buffer.getInt();
To read the samples, just continue what you're doing above. But you'll also have to consider the number of bits per sample as given by the BitsPerSample field. For 8 bits, read 1 byte. For 16 bits, read 2 bytes, and so on and so forth. Just repeat to get each sample until you reach the end of file.
To check the end of file, get the returned value from read and check if it is -1:
int read = fileInputSream.read(bytes, 0, bytes.length);
// If read equals -1 then end of file
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();
I'm attempting to read magic numbers/bytes to check the format of a file. Will reading a file byte by byte work in the same way on a Linux machine?
Edit: The following shows to get the magic bytes from a class file using an int. I'm trying to do the same for a variable number of bytes.
http://www.rgagnon.com/javadetails/java-0544.html
I'm not sure that I understand what you are trying to do, but it sounds like what you are trying to do isn't the same thing as what the code that you are linking to is doing.
The java class format is specified to start with a magic number, so that code can only be used to verify if a file might be a java class or not. You can't use the same logic and apply it to arbritraty file formats.
Edit: .. or do you only want to check for wav files?
Edit2: Everything in Java is in big endian, that means that you can use DataInputStream.readInt to read the first four bytes from the file, and then compare the returned int with 0x52494646 (RIFF as a big endian integer)
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