Revised/Summary:
I'm using a plugin to decode an MP3 audio file. I'd like to provide a ProgressMonitor to provide feedback to the user. The logic of constructing an AudioInputStream that decodes the MP3 format AudioFile is as follows:
readAudioFile(File pAudioFile) throws UnsupportedAuioFileException, IOException {
AudioInputStream nativeFormatStream = AudioSystem.getAudioInputStream(pAudioFile);
AudioInputStream desiredFormatStream = AudioSystem.getAudioInputStream(AUDIO_OUTPUT_FORMAT,nativeFormatStream);
int bytesRead, bufferLength;
byte[] rawAudioBuffer[bufferLength=4096];
bytesRead=desiredFormatStream.read(rawAudioBuffer,0,bufferLength));
...
}
First attempt was to wrap the audio File with a ProgressMontorInputStream, then get the AudioInputStream from that:
readAudioFile(File pAudioFile) throws UnsupportedAuioFileException, IOException {
ProgressMonitorInputStream monitorStream = new ProgressMonitorInputStream(COMP,"Decoding",new FileInputStream(pAudioFile);
AudioInputStream nativeFormatStream = AudioSystem.getAudioInputStream(monitorStream);
AudioInputStream desiredFormatStream = AudioSystem.getAudioInputStream(AUDIO_OUTPUT_FORMAT,nativeFormatStream);
int bytesRead, bufferLength;
byte[] rawAudioBuffer[bufferLength=4096];
bytesRead=desiredFormatStream.read(rawAudioBuffer,0,bufferLength));
...
}
While it builds, upon execution I get the following when constructing the AudioInputStream from the ProgressMonitorInputStream:
java.io.IOException: mark/reset not supported
Comments below confirm that the AudioInputStream requires the InputStream it wraps to support the mark() and reset() methods, which apparently ProgressMonitorInputStream does not.
Another suggestion below is to wrap the ProgressMonitorInputStream with a BufferedInputStream (which does support mark/reset). So then I have:
readAudioFile(File pAudioFile) throws UnsupportedAuioFileException, IOException {
ProgressMonitorInputStream monitorStream = new ProgressMonitorInputStream(COMP,"Decoding",new FileInputStream(pAudioFile);
AudioInputStream nativeFormatStream = AudioSystem.getAudioInputStream(new BufferedInputStream(monitorStream));
AudioInputStream desiredFormatStream = AudioSystem.getAudioInputStream(AUDIO_OUTPUT_FORMAT,nativeFormatStream);
int bytesRead, bufferLength;
byte[] rawAudioBuffer[bufferLength=4096];
bytesRead=desiredFormatStream.read(rawAudioBuffer,0,bufferLength));
...
}
Now this builds and executes without error. However, the ProgressMonitor never appears, despite aggressive settings for setMillisToPopup(10) and setMillisToDecideToPopup(10); My theory is that the time to actually read the undecoded audio into memory is still faster than 10mSec. The time is actually spent decoding that raw audio after reading from disk. So the next step is to wrap the undecoded AudioInputStream with the ProgressMonitorInputStream before constructing the decoding AudioInputStream:
readAudioFile(File pAudioFile) throws UnsupportedAuioFileException, IOException {
AudioInputStream nativeFormatStream = AudioSystem.getAudioInputStream(pAudioFile);
AudioInputStream desiredFormatStream = AudioSystem.getAudioInputStream(AUDIO_OUTPUT_FORMAT,new BufferedInputStream(new ProgressMonitorInputStream(COMP,"Decoding",nativeFormatStream);
int bytesRead, bufferLength;
byte[] rawAudioBuffer[bufferLength=4096];
bytesRead=desiredFormatStream.read(rawAudioBuffer,0,bufferLength));
...
}
I seem to be kicking the can down the road but not making progress. Is there any workaround for this problem? Is there an alternative way to providing a ProgressMonitor for the decoding process? My (unsatisfying) fallback is displaying a busy cursor. Any suggestions for other ways to accomplish the goal - providing visual feedback to the user with at least an estimate of time remaining to complete the decoding?
Related
I am trying to obtain an AudioInputStream from a file in a specific format. When I open the file in fileStream I get a positive frameLength meaning the file can be read and the AudioInputStream effectively has data. Now I want to convert it with AudioSystem with AudioSystem.getAudioInputStream(format, fileStream); and the obtained stream audioStream has a frameLength of -1. I even made sure the conversion is supported and it sure is.
public void openFile() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
AudioInputStream fileStream = AudioSystem.getAudioInputStream(audioFile);
AudioFormat format = new AudioFormat(sampleRate, 8, 1, true, true);
boolean supported = AudioSystem.isConversionSupported(format, fileStream.getFormat());
if(supported) {
audioStream = AudioSystem.getAudioInputStream(format, fileStream);
System.out.println("Opened file: " + audioFile.getName());
}else{
System.out.println("Couldn't open file: " + audioFile.getName());
throw new IOException();
}
}
I am completely stuck and I haven't found anyone with the same exact problem. I welcome any suggestions of different libraries but I would prefer to keep using this one as I'm used to it.
Okay figured it out, I'm dumb.
A frameLength of -1 is, according to the AudioSystem constants, NOT_SPECIFIED which means that, when converting audio, you lose that information.
I was thinking that was the problem because of another unrelated problem.
I´m learning how to play sound in java but with advanced controls.
I´ve found one problem: The javax.sound.sampled.AudioInputStream doesn´t support Mp3 files, and i´m running out of ideas to find how to get control of panning.
I managed to play an Mp3 file using javazoom.jl.player.advanced.AdvancedPlayer, but it doesn´t have a panning control, or i haven´t founded it.
My actual code opens a file, if the format is compatible with AudioInputStream, it plays only the right channel. If the format doesn´t, it plays using AdvancedPlayer.
Do you know a way to get panning control of mp3 files?
My code here:
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.advanced.AdvancedPlayer;
import javax.sound.sampled.*;
import javax.swing.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class AudioPlayerExample2 {
private static final int BUFFER_SIZE = 4096;
public static void main(String[] args) throws IOException, LineUnavailableException, JavaLayerException {
JFileChooser fileChooser = new JFileChooser();
fileChooser.showOpenDialog(null);
new AudioPlayerExample2().play(fileChooser.getSelectedFile());
}
void play(File file) throws IOException, LineUnavailableException, JavaLayerException {
AudioInputStream audioStream;
try {
audioStream = AudioSystem.getAudioInputStream(file);
AudioFormat format = audioStream.getFormat();
System.err.println(format.toString());
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
audioLine.open(format);
audioLine.start();
FloatControl pan = (FloatControl) audioLine.getControl(FloatControl.Type.PAN);
byte[] bytesBuffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = audioStream.read(bytesBuffer)) != -1) {
pan.setValue((float) (1));
audioLine.write(bytesBuffer, 0, bytesRead);
}
audioLine.drain();
audioLine.close();
audioStream.close();
} catch (UnsupportedAudioFileException e) {
FileInputStream fis = new FileInputStream(file);
AdvancedPlayer player = new AdvancedPlayer(fis);
player.play();
}
}
}
Panning and volume controls are system dependent and can sometimes be a bit flaky even if they are in place. For example, if you change the volume or pan setting too much at once, the discontinuity causes a click.
One solution is to go in there on a per-frame basis and make the changes yourself. For example, see "Manipulating the Audio Data Directly" at the end of the tutorial Processing Audio with Controls.
For an example, check out the code from the next tutorial on the trail: Using Files and Format Converters. Look under the heading "Reading Sound Files" and look for the comment in the code "\ Here, do something useful..."
I invite you to also take a look at the code I wrote and have made available, a class called AudioCue that has real time panning as well as real time volume and pitch playback controls. I've added smoothing (1024 steps for panning changes) to help mitigate the possibility of discontinuities.
It will be up to you to take the mp3 file and decode it into an array of audio data. I think that the javazoom libraries made available on github should give you enough code access to figure out how to do this (I did it for ogg/vorbis decoding). Once you have a float array of audio data (stereo, signed floats ranging from -1 to 1), this can be directly loaded into AudioCue.
First of all, thanks to Andrew Thompson and Phil Freihofner, I feel very good about being part of this community and having someone to trust. You really make feel happy :)
I leave here the full code that does exactly what I wanted.
As the JavaZoom MP3 SPI Documentation says: Make sure that JLayer, Tritonus and MP3SPI librairies are available in your CLASSPATH.
import javax.sound.sampled.*;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException,
UnsupportedAudioFileException, LineUnavailableException {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
String path = chooser.getSelectedFile().getAbsolutePath();
System.err.println(path);
File file = new File(path);
AudioInputStream baseStream = AudioSystem.getAudioInputStream(file);
AudioFormat baseFormat = baseStream.getFormat();
System.err.println(baseFormat.toString());
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16, baseFormat.getChannels(), baseFormat.getChannels() * 2,
baseFormat.getSampleRate(), true);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
AudioInputStream stream = AudioSystem.getAudioInputStream(format, baseStream);
SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
audioLine.open(format);
audioLine.start();
FloatControl pan = (FloatControl) audioLine.getControl(FloatControl.Type.PAN);
pan.setValue(1);
int BUFFER_SIZE = 4096;
byte[] buffer = new byte[BUFFER_SIZE];
int read = -1;
while((read = stream.read(buffer)) != -1){
audioLine.write(buffer, 0, read);
}
audioLine.drain();
audioLine.close();
}
}
This question is probably not even going to be asked correctly, but I promise you I'm doing my best. Here's the scenario: I wrote a little Java app that receives an audio stream from a server. Now, when I redirect the binary stream to a file, and pipe that file to mplayer, the audio is played correctly. What I want now though is to play the audio from my own app. Here's what I got so far:
The codec mplayer uses to play the stream:
AUDIO: 22050 Hz, 2 ch, s16le, 0.0 kbit/0.00% (ratio: 0->88200)
Selected audio codec: [ffaac] afm: ffmpeg (FFmpeg AAC (MPEG-2/MPEG-4 Audio))
What I coded so far:
public class StreamPlayer {
public final AudioFormat audioFormat;
public final DataLine.Info info;
public final SourceDataLine soundLine;
public StreamPlayer() throws LineUnavailableException {
audioFormat = new AudioFormat(22050, 16, 2, true, true);
info = new DataLine.Info(SourceDataLine.class, audioFormat, 1500);
soundLine = (SourceDataLine) AudioSystem.getLine(info);
}
public void startSoundLine() throws LineUnavailableException {
soundLine.open(audioFormat);
soundLine.start();
}
public void playStream(byte[] buffer) {
soundLine.write(buffer, 0, buffer.length);
}
}
and I call the playStream function for every received packet. No errors are reported, and no sound is heard. Am I even close to doing this, or off by a long shot?
P.S. I found some third party libraries on google, but I'd really like to keep them as a last resort.
Thank you!
Get your Bytes into a ByteArrayInputStream and feed this into the streamplayer instead...
ByteArrayInputStream audioStream = new ByteArrayInputStream(buffer);
I have been tearing my hair out trying to solve this problem, I have this method in a calculator I've been working on:
public void error_sound() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream AIS = AudioSystem.getAudioInputStream(calculator.class.getResourceAsStream("/resources/Error.wav"));
AudioFormat format = AIS.getFormat();
SourceDataLine playbackLine = AudioSystem.getSourceDataLine(format);
playbackLine.open(format);
playbackLine.start();
int bytesRead = 0;
byte[] buffer = new byte[128000];
while (bytesRead != -1) {
bytesRead = AIS.read(buffer, 0, buffer.length);
if (bytesRead >= 0)
playbackLine.write(buffer, 0, bytesRead);
}
playbackLine.drain();
playbackLine.close();
}
This code works on JRE6 but not on JRE7. If anyone could suggest a way to make the above work on JRE7 I'd be eternally grateful?
It appears that Sun dropped the "Java Sound Audio Engine" in JRE 1.7 and this is the only thing I can put it down to?
"It appears that Sun dropped the "Java Sound Audio Engine" in JRE 1.7 and this is the only thing I can put it down to?"
Nope, that would have been noticed by a lot of people, including me. Your comments indicate there is a problem seeking in the resource input stream. That can either be caused by different audio systems or different implementations of getAudioStream().
You could just try wrapping the resource stream into a BufferedInputStream:
InputStream raw = calculator.class.getResourceAsStream("/resources/Error.wav");
InputStream bis = new BufferedInputStream(raw, 20000);
AudioInputStream ais = AudioSystem.getAudioInputStream(bis);
(Thats based on the idea thet BufferedInputStream supports mark / reset)
You should really add some proper error handling to the code (check if the resource is there etc. and proper error logging/reporting). It really helps in the long run if problems are reported clearly.
EDIT: Re-reading you problem description, its clear you are running the code from eclipse, and on the other computer you run from a jar-file. The problem is your code doesn't cope with the latter. Wrapping it into a BufferedInputStream should fix that (you may need to increase the buffer size though).
Edit2: Try this for repeating sound:
public void error_sound() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream AIS = ...
AudioFormat format = ...
SourceDataLine playbackLine = ...
playbackLine.open(format);
playbackLine.start();
int repeats = 5;
while (true) {
// playloop
int bytesRead = 0;
byte[] buffer = new byte[128000];
while (bytesRead != -1) {
bytesRead = AIS.read(buffer, 0, buffer.length);
if (bytesRead >= 0)
playbackLine.write(buffer, 0, bytesRead);
}
--repeats;
if (repeats <= 0) {
// done, stop playing
break;
} else {
// repeat one more time, reset audio stream
AIS = ...
}
}
playbackLine.drain();
playbackLine.close();
}
The only complicated thing is that you need the audio stream to get the format and that you also need to create it anew in every loop iteration to read it from the beginning. Everything else stays the same.
try {
//String location = dir1.getCanonicalPath()+"\\app_yamb_test1\\mySound.au";
//displayMessage(location);
AudioInputStream audio2 = AudioSystem.getAudioInputStream(getClass().getResourceAsStream("mySound.au"));
Clip clip2 = AudioSystem.getClip();
clip2.open(audio2);
clip2.start();
} catch (UnsupportedAudioFileException uae) {
System.out.println(uae);
JOptionPane.showMessageDialog(null, uae.toString());
} catch (IOException ioe) {
System.out.println("Couldn't find it");
JOptionPane.showMessageDialog(null, ioe.toString());
} catch (LineUnavailableException lua) {
System.out.println(lua);
JOptionPane.showMessageDialog(null, lua.toString());
}
This code works fine when I run the application from netbeans. The sound plays and there are no exceptions. However, when I run it from the dist folder, the sound does not play and I get the java.io.IOException: mark/reset not supported in my message dialog.
How can I fix this?
The documentation for AudioSystem.getAudioInputStream(InputStream) says:
The implementation of this method may
require multiple parsers to examine
the stream to determine whether they
support it. These parsers must be able
to mark the stream, read enough data
to determine whether they support the
stream, and, if not, reset the
stream's read pointer to its original
position. If the input stream does not
support these operation, this method
may fail with an IOException.
Therefore, the stream you provide to this method must support the optional mark/reset functionality. Decorate your resource stream with a BufferedInputStream.
//read audio data from whatever source (file/classloader/etc.)
InputStream audioSrc = getClass().getResourceAsStream("mySound.au");
//add buffer for mark/reset support
InputStream bufferedIn = new BufferedInputStream(audioSrc);
AudioInputStream audioStream = AudioSystem.getAudioInputStream(bufferedIn);
After floundering about for a while and referencing this page many times, I stumbled across this which helped me with my problem. I was initially able to load a wav file, but subsequently only could play it once, because it could not rewind it due to the "mark/reset not supported" error. It was maddening.
The linked code reads an AudioInputStream from a file, then puts the AudioInputStream into a BufferedInputStream, then puts that back into the AudioInputStream like so:
audioInputStream = AudioSystem.getAudioInputStream(new File(filename));
BufferedInputStream bufferedInputStream = new BufferedInputStream(audioInputStream);
audioInputStream = new AudioInputStream(bufferedInputStream, audioInputStream.getFormat(), audioInputStream.getFrameLength());
And then finally it converts the read data to a PCM encoding:
audioInputStream = convertToPCM(audioInputStream);
With convertToPCM defined as:
private static AudioInputStream convertToPCM(AudioInputStream audioInputStream)
{
AudioFormat m_format = audioInputStream.getFormat();
if ((m_format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) &&
(m_format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED))
{
AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
m_format.getSampleRate(), 16,
m_format.getChannels(), m_format.getChannels() * 2,
m_format.getSampleRate(), m_format.isBigEndian());
audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
}
return audioInputStream;
}
I believe they do this because BufferedInputStream handles mark/reset better than audioInputStream. Hope this helps somebody out there.
Just came across this question from someone else with the same problem who referenced it. Looks like this issue arose with Java 7.
Oracle Bug database, #7095006
A test, executed when InputStream is the argument to the getAudioInputStream() method, is triggering the error. The existence of Mark/Reset capabilities in the audio resource file have no bearing on whether the Clip will load and play. Given that, there is no reason to prefer an InputStream as the argument, when a URL or File suffice.
If we substitute a URL as the argument, this needless test is not executed. Revising the OP code:
AudioInputStream ais = AudioSystem.getAudioInputStream(getClass().getResource(fileName));
Details can be seen in the API, in the description text for the two forms.
AudioSystem.getAudioInputStream(InputStream)
AudioSystem.getAudioInputStream(URL)
The problem is that you're input stream has to support the methods mark and reset. At least if mark is supported you can test with: AudioInputStream#markSupported.
So you should maybe use a different InputStream.