Unwanted downsampling : Java Sound

Multi tool use
Unwanted downsampling : Java Sound
I have been trying to manually read a wav file in Java and read an array of bytes then write to an audio buffer for playback. I am receiving playback but it is heavily distorted. Java sound supports 16 bit sample rates but not 24-bit.
I went in to Logic 9 and exported a 24-bit audio file in to 16-bit and then used with my program. Originally, the 24-bit samples would produces white noise. Now I can hear my sample but very distorted and sounds like it has been bit crushed.
Can anyone help me to get a clean signal?
I am very new to audio programming but I am currently working on a basic Digital Audio Workstation.
import javax.sound.sampled.*;
import javax.sound.sampled.DataLine.Info;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.*;
public class AudioData {
private String filepath;
private String filepath1;
private File file;
private byte fileContent;
private Mixer mixer;
private Mixer.Info mixInfos;
private AudioInputStream input;
private ByteArrayOutputStream byteoutput;
public static void main (String args) {
AudioData audiodata = new AudioData();
}
public AudioData () {
filepath = "/Users/ivaannagen/Documents/Samples/Engineering Samples - Obscure Techno Vol 3 (WAV)/ES_OT3_Kit03_Gmin_130bpm/ES_OT3_Kit03_FX_Fast_Snare_Riser_Gmin_130bpm.wav";
filepath1 = "/Users/ivaannagen/Documents/Samples/dawsampletest.wav";
file = new File (filepath1);
readAudio();
}
public void readAudio () {
mixInfos = AudioSystem.getMixerInfo();
mixer = AudioSystem.getMixer(mixInfos[0]);
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
// set up an audio format.
try {
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); // creates data line with class type and audio format.
SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info);
System.out.println("Size of data line buffer: " + source.getBufferSize());
fileContent = new byte [source.getBufferSize() / 50];
byteoutput = new ByteArrayOutputStream();
input = AudioSystem.getAudioInputStream(file);
int readBytes = 0;
while ((readBytes = input.read(fileContent, 0, fileContent.length)) != -1) {
byteoutput.write(fileContent, 0, readBytes);
}
System.out.println("Size of audio buffer: " + fileContent.length);
//byteoutput.write(0);
// byteoutput.write(0);
System.out.println("Size of audio buffer: " + byteoutput.size());
source.open(format, source.getBufferSize()); // line must be open to be recognised by the mixer.
Line lines = mixer.getSourceLines();
System.out.println("mixer lines: " + lines.length);
// for(byte bytes: fileContent) {
// System.out.println(bytes);
// }
Thread playback = new Thread () {
public void run () {
// System.out.println((byteoutput.size() +2) % 4);
source.start(); // play (buffer originally empty)
source.write(byteoutput.toByteArray(), 0, byteoutput.size()); // write input bytes to output buffer
} // end run (to do).
}; // end thread action
playback.start(); // start thread
}
catch (LineUnavailableException lue) {
System.out.println(lue.getMessage());
}
catch (FileNotFoundException fnfe) {
System.out.println(fnfe.getMessage());
}
catch(IOException ioe) {
System.out.println(ioe.getMessage());
}
catch(UnsupportedAudioFileException uafe) {
System.out.println(uafe.getMessage());
}
}
}
new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
For now, I want to see things working. Javasound only supports wav files and I haven't looked in to supporting mp3 etc.
– Ivaan
Jun 28 at 16:43
JavaFX audio has some support for mp3 I believe, but I haven't tried it yet.
– Phil Freihofner
Jun 28 at 20:23
"Javasound only supports wav files" See
AudioSystem.getAudioFileTypes()
. Other formats can be supported by SPI. One is available for MP3. @PhilFreihofner "JavaFX audio has some support for mp3 I believe" Yep. I changed my Java Sound + MP3 SPI for the media player of Java-FX & it seems to play all my MP3s just fine. The Java-FX interface to sound and video media is so high level though, that it doesn't support access to the sound stream itself (AFAIU - crudely). Though U can get spectrum!– Andrew Thompson
Jun 29 at 2:29
AudioSystem.getAudioFileTypes()
Agreed about high level. I looked into it a bit when writing AudioCue, but got impression that while there are some improvements over Clip in JavaFX, it was too high level for my purposes. I wonder if they wrote an entirely new subsystem or if they piggyback on some of java.sound. I haven't gone in deep enough to know. I think what is supported can vary. javax.sound.sampled is supposed to have some provision for other types besides .wav, but I've never had a reason to use them and haven't explored this aspect.
– Phil Freihofner
Jun 29 at 8:04
2 Answers
2
Whether or not you can load and play a 24-bit file is system dependent, afaik.
I use Audacity for conversions. You should be able import your file into Audacity and export it as 16-bit, stereo, little-endian, 44100 fps, and then load that export with Java's AudioInputStream
.
AudioInputStream
What you hear when playing from Audacity or from Java should be pretty much identical (adjusting for volume). If not, the most likely reason probably pertains to a mistake or overlook in the code, which is very easy to do.
The use of a ByteOutputStream
in your code is superfluous. Read from the AudioInputStream
into a fixed-size byte array (size being the buffer length, I recommend trying 8 or 16 * 1024 bytes as a first try) and then use the SourceDataLine
write method to ship that array.
ByteOutputStream
AudioInputStream
SourceDataLine
Following is code that works on my system for loading a playing a "CD Quality" wav called "a3.wav" that I have that is in the same directory as the Java class. You should be able to swap in your own 44100, 16-bit, stereo, little-endian wav file.
I've commented out an attempt to load and play a 24-bit wav file called "spoken8000_24.wav". That attempt gave me an IllegalArgumentException
: No line matching interface SourceDataLine supporting format PCM_SIGNED 8000.0 Hz, 24 bit, stereo, 6 bytes/frame, little-endian is supported.
IllegalArgumentException
No line matching interface SourceDataLine supporting format PCM_SIGNED 8000.0 Hz, 24 bit, stereo, 6 bytes/frame, little-endian is supported.
I have to admit, I'm unclear if my system doesn't provide the needed line or if I might have coded the format incorrectly! My OS can certainly play the file. So I'm thinking there is a distinction between what an OS can do and what a "Mixer" on a given system provides to Java.
As a get-around, I just always convert everything to "CD Quality" format, as that seems to be the most widely supported.
public class TriggerSound_SDL extends JFrame
{
public TriggerSound_SDL()
{
JButton button = new JButton("Play Sound");
button.addActionListener(e -> new Thread(() -> playBuzzer()).start());
getContentPane().add(button);
}
private void playBuzzer()
{
try
{
URL url;
url = getClass().getResource("a3.wav");
// url = getClass().getResource("spoken8000_24.wav");
AudioInputStream ais = AudioSystem.getAudioInputStream(url);
System.out.println(ais.getFormat());
AudioFormat audioFmt;
// "CD Quality" 44100 fps, 16-bit, stereo, little endian
audioFmt = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
44100, 16, 2, 4, 44100, false);
// 8000 fps, 32-bit, stereo
// audioFmt = new AudioFormat(
// AudioFormat.Encoding.PCM_SIGNED,
// 8000, 24, 2, 6, 8000, false);
Info info = new DataLine.Info(SourceDataLine.class,
audioFmt);
SourceDataLine sdl = (SourceDataLine)AudioSystem.getLine(info);
int bufferSize = 16 * 1024;
byte buffer = new byte[bufferSize];
sdl.open(audioFmt, bufferSize);
sdl.start();
int numBytesRead = 0;
while((numBytesRead = ais.read(buffer)) != -1)
{
sdl.write(buffer, 0, numBytesRead);
}
}
catch (IOException | UnsupportedAudioFileException
| LineUnavailableException ex)
{
ex.printStackTrace();
}
}
private static void createAndShowGUI()
{
JFrame frame = new TriggerSound_SDL();
frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main(String args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
This code, with some small tweaks should let you at least test the different formats.
EDIT:
I'm seeing where your goal is to make a DAW!
In that case, you will want to convert the bytes to PCM data. Can I suggest you borrow some code from AudioCue? I basically wrote it to be a Clip-substitute, and part of that involved making the PCM data available for manipulation. Some techniques for mixing, playing back at different frequencies, multithreading can be found in it.
Okay, please see my message below but thanks for all your advice. I'll see what I can pinch from AudioCue. Surely, the way i'm doing it now is the best way to convert the bytes for volume & frequency control etc, because I have an array of raw data?
– Ivaan
Jun 30 at 9:12
When you say convert bytes to PCM data, what do you mean? Is that for exporting tracks? So, once my audio is processed, I can then export a byte array back to a .wav file? Is that what you mean?
– Ivaan
Jun 30 at 9:13
PCM is pulse code modulation. Term is often used in context of converting analog to digital. I use it as a shorthand for the float values representing the signal, ranging from -1 to 1. With 16-bit, two bytes are assembled to get a single value (range -32767 to 32767 or same as a Short), then divide by 32767 to "normalize" the data points. This makes it much easier to do accurate signal processing. When all processing is done (including any final stage of mixing signals together) we multiply signal by 32767 and convert the short to two bytes. With 24-bit, three bytes are used instead of two.
– Phil Freihofner
Jul 1 at 5:40
Thanks for all the advice guys. I will be getting rid of the ByteOutputStream and just use the AudioInputStream, I now understand what I was doing was unnecessary!! Thanks for the advice all! I have indeed tried using AudioCue but it is not low level enough for what I want to do!
One more thing guys. Previously, I created a multitrack media player which is using the Clip class. To play all the audio tracks together, I was looping through a list of Clips and playing them. However, this means that all tracks may be playing a tiny amount after each other due to the processing of the loop. Also, Clip class created a new thread per audio. I do not wants 100 threads running on 100 tracks, I want one thread for my audio output. I am still trying to work out how to start all tracks at the same time without a loop....(im guessing AudioCue have nailed the concurrent cues).
Does anyone know the best way to play multiple audio tracks in to one output? Do I need to route/bus all my audio tracks in to one output and somehow write all data from audio files in to one output buffer then play this output in a thread?
Thanks!!
Best way to play multiple audio tracks in one output: simple addition of PCM. Yes, add the values. Good free book on digital audio signal processing: dspguide.com But also, check out the code in audiocue.AudioMixer and AudioMixerTrack interface. You need the PCM, prob best as floats in range -1 to 1. Clip does not allow access to PCM. AudioCue has example of how it can be done. Intention was that you can examine code for ideas and algorithms. There is a contact email on github audiocue if you want to ask me anything about the code directly. Or ask at Java-gaming.org audio board.
– Phil Freihofner
Jul 1 at 5:29
Hi Phil, I am looking through the code now. Getting some ideas from the code which is great, however my experience is lacking in order to understand some of the implemented concepts. I know that I need to do things low level so I can add as much control as possible. I couldn't find volume control in audio cue? Is that also changing PCM values? I will keep looking. Thanks for all the advice so far!
– Ivaan
yesterday
Yes! Simplest is to have a factor that ranges from 0 to 1 and multiply it against the PCM values. Biggest danger is when abruptly changing from one volume factor to another. This can create a "discontinuity" in the signal that elicits a "click". With 44100 fps, I have taken to dividing a volume change into 1024 steps and increment from starting to target value over 1024 frames. This should be smooth enough not to click. Other volume issue is that hearing is not linear, so often we use a power function to make 0 to 1 correspond better to sense of volume. Do check out the DSP guide I linked.
– Phil Freihofner
yesterday
Amazing, cheers Phil. Very useful information !!
– Ivaan
21 hours ago
I don't know if you knew, but SourceDataLine.write can be called on multiple byte arrays. So you can write more than one audio file at once to the buffer.... I don't know whether this actually does the pcm addition for you? However, it doesn't actually write two arrays in to one array. Playback works, I am now getting playback of multiple audio files... I am guessing this is not the best way to do things and doesn't give me the control of the output data...
– Ivaan
1 hour ago
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
Why presume the format as opposed to getting that information from the loaded sound?– Andrew Thompson
Jun 28 at 13:50