I am writing a music visualiser and am trying to play mp3 files in java using an mp3plugin (http://pscode.org/lib/mp3plugin.jar). However I've come across an error, probably due to my lack of experience using this plugin and coding music in general.
I have tested this code with a wide range of mp3 files, all with the same problem, I believe I can rule out with some confidence that the mp3 files are at fault.
My code below:
AudioInputStream audioIn;
try {
JS_MP3FileReader m = new JS_MP3FileReader();
audioIn = m.getAudioInputStream(new File(ex.trackname));
//audioIn = AudioSystem.getAudioInputStream(new File(ex.trackname));
// Get a sound clip resource.
Clip clip = AudioSystem.getClip();
// Open audio clip and load samples from the audio input stream.
clip.open(audioIn);
clip.start();
} catch (Exception e) {
e.printStackTrace();
}
The error I get:
javax.sound.sampled.LineUnavailableException: line with format MPEG1L3 48000.0 Hz, unknown bits per sample, stereo, unknown frame size, unknown frame rate, not supported.
at com.sun.media.sound.DirectAudioDevice$DirectDL.implOpen(Unknown Source)
at com.sun.media.sound.DirectAudioDevice$DirectClip.implOpen(Unknown Source)
at com.sun.media.sound.AbstractDataLine.open(Unknown Source)
at com.sun.media.sound.DirectAudioDevice$DirectClip.open(Unknown Source)
at com.sun.media.sound.DirectAudioDevice$DirectClip.open(Unknown Source)
at Logic.Run$1.run(Run.java:101)
at java.lang.Thread.run(Unknown Source)
(note the class instance I wrote that runs this code is called Run)
My project structure below, including the plugin file:
I use JavaSound directly to play mp3 audios and it works quite well
Heres a full working code (have fun):
package XXXXXXXXXXXXXX
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.*;
public class AudioPlayer
{
private int bufferSize = 4096; // Tamanho de buffer padrão 4k
private volatile boolean paused = false;
private final Object lock = new Object();
private SourceDataLine line;
private int secondsFade = 0;
private ArrayList<AudioPlayerListener> _listeners = new ArrayList<AudioPlayerListener>();
public void stop()
{
if(line != null)
{
line.stop();
line.close();
}
}
public boolean isPaused()
{
return this.paused;
}
public void pause()
{
if(!this.isPaused())
paused = true;
}
public void resume()
{
if(this.isPaused())
{
synchronized(lock){
lock.notifyAll();
paused = false;
}
}
}
public void play(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException, InterruptedException
{
AudioInputStream encoded = AudioSystem.getAudioInputStream(file);
AudioFormat encodedFormat = encoded.getFormat();
AudioFormat decodedFormat = this.getDecodedFormat(encodedFormat);
Long duration = null;
AudioInputStream currentDecoded = AudioSystem.getAudioInputStream(decodedFormat, encoded);
line = AudioSystem.getSourceDataLine(decodedFormat);
line.open(decodedFormat);
line.start();
boolean fezFadeIn = false;
boolean fezFadeOut = false;
byte[] b = new byte[this.bufferSize];
int i = 0;
Map properties = null;
try
{
properties = AudioUtil.getMetadata(file);
duration = (Long) properties.get("duration");
}
catch (Exception ex)
{
duration = 0L;
}
duration = duration < 0 ? 0 : duration;
synchronized(lock)
{
//Parametro que ativa ou não o fade de acordo com o tamanho do áudio
long paramFade = (secondsFade*2+1)*1000000;
//long paramFade = 0;
//Logger.getLogger(this.getClass().getName()).info("Arquivo: "+file+", DURACAO DO AUDIO: "+duration+", paramfade: "+paramFade);
while(true)
{
if(secondsFade > 0 && !fezFadeIn && duration >= paramFade)
{
fezFadeIn = true;
fadeInAsync(this.secondsFade);
}
if( secondsFade > 0 &&
duration > paramFade &&
!fezFadeOut &&
line.getMicrosecondPosition() >= duration - ((this.secondsFade+1)*1000000) )
{
this.fireAboutToFinish();
fadeOutAsync(this.secondsFade);
fezFadeOut = true;
}
if(paused == true)
{
line.stop();
lock.wait();
line.start();
}
i = currentDecoded.read(b, 0, b.length);
if(i == -1)
break;
line.write(b, 0, i);
}
}
if( !fezFadeOut && line.isOpen() )
this.fireAboutToFinish();
line.drain();
line.stop();
line.close();
currentDecoded.close();
encoded.close();
}
public synchronized void fadeInAsync(final int seconds)
{
if(line != null && line.isOpen())
{
Thread t = new Thread(new Fader(true, this, secondsFade));
t.start();
}
}
public synchronized void fadeOutAsync(final int seconds)
{
if(line != null && line.isOpen())
{
Thread t = new Thread(new Fader(false, this, secondsFade));
t.start();
}
}
public void setVolume(double value)
{
if(!line.isOpen())
return;
// value is between 0 and 1
value = (value<=0.0)? 0.0001 : ((value>1.0)? 1.0 : value);
try
{
float dB = (float)(Math.log(value)/Math.log(10.0)*20.0);
((FloatControl)line.getControl(FloatControl.Type.MASTER_GAIN)).setValue(dB);
}
catch(Exception ex)
{
}
}
public boolean isPlaying()
{
return (line != null && line.isOpen());
}
protected AudioFormat getDecodedFormat(AudioFormat format)
{
AudioFormat decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, // Encoding to use
format.getSampleRate(), // sample rate (same as base format)
16, // sample size in bits (thx to Javazoom)
format.getChannels(), // # of Channels
format.getChannels()*2, // Frame Size
format.getSampleRate(), // Frame Rate
false // Big Endian
);
return decodedFormat;
}
public int getBufferSize()
{
return bufferSize;
}
public void setBufferSize(int bufferSize)
{
if(bufferSize <= 0)
return;
this.bufferSize = bufferSize;
}
/**
* @return the secondsFade
*/
public int getSecondsFade() {
return secondsFade;
}
/**
* @param secondsFade the secondsFade to set
*/
public void setSecondsFade(int secondsFade) {
if(secondsFade < 0 || secondsFade > 10)
throw new IllegalArgumentException("Erro ao configurar cross-fade com valor em segundos: "+secondsFade);
this.secondsFade = secondsFade;
}
public void addAudioPlayerListener(AudioPlayerListener a)
{
this._listeners.add(a);
}
public void removeAudioPlayerListener(AudioPlayerListener a)
{
this._listeners.remove(a);
}
private void fireAboutToFinish()
{
for(AudioPlayerListener a : this._listeners)
a.aboutToFinish(this);
}
}
class Fader implements Runnable
{
private boolean fadeIn;
private int seconds=0;
private final AudioPlayer player;
private float increaseParam;
public Fader(boolean fadeIn, AudioPlayer player, int secondsToFade)
{
this.fadeIn = fadeIn;
this.seconds = secondsToFade;
this.player = player;
if(fadeIn)
increaseParam = 0.01F;
else
increaseParam = -0.01F;
}
@Override
public void run()
{
try
{
encapsulateRun();
}
catch(Exception ex)
{
if(fadeIn)
player.setVolume(1.0F);
else
player.setVolume(0.0F);
}
}
private void encapsulateRun() throws Exception
{
synchronized(player)
{
float per;
if(fadeIn)
{
Logger.getLogger(getClass().getName()).info("Fazendo fade in");
per = 0.0F;
}
else
{
Logger.getLogger(getClass().getName()).info("Fazendo fade out");
per = 1.0F;
}
player.setVolume(per);
if(fadeIn)
{
while(per < 1.00F)
{
per = per + increaseParam;
player.setVolume(per);
Thread.sleep(10*seconds);
}
}
else
{
while(per > 0.00F)
{
per = per + increaseParam;
player.setVolume(per);
Thread.sleep(10*seconds);
}
}
}
}
}
It also has support for Fade in and out mp3
All you need is to add 3 jars to your project. I have tested with those versions:
jl.1.0.1.jar (or newer)
mp3spi1.9.5.jar (or newer)
found at http://www.javazoom.net/projects.html
tritonus_share.jar
found at http://www.tritonus.org/
AudioUtil.java
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;
public class AudioUtil
{
private static ArrayList<FileMap> cache = new ArrayList<FileMap>();
private static int _CACHE_SIZE = 5;
public static Map<String, Object> getMetadata(File filename) throws Exception
{
FileMap fm = new FileMap(filename, null);
int index = cache.indexOf(fm);
if(index >= 0)
return cache.get(index).getMap();
AudioFileFormat format = AudioSystem.getAudioFileFormat(filename);
Map<String, Object> mapa = new HashMap<String, Object>();
mapa.putAll(format.properties());
if(mapa.get("author") == null && filename.getName().contains(" - "))
{
mapa = new HashMap<String, Object>();
String[] s = filename.getName().split(" - ");
mapa.put("author", s[0]);
s[1] = s[1].substring(0, s[1].length()-4);
mapa.put("title", s[1]);
}
if(mapa.get("author") == null)
{
mapa.put("author", "Desconhecido");
mapa.put("title", "Desconhecido");
}
Object o = format.properties().get("duration");
if(o == null)
mapa.put("duration", 0);
fm.setMap(mapa);
cache.add(fm);
while(cache.size() > _CACHE_SIZE)
cache.remove((int)0);
return mapa;
}
}
class FileMap
{
private File file;
private Map<String, Object> map;
public FileMap(File file, Map<String, Object> map)
{
this.file = file;
this.map = map;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
@Override
public boolean equals(Object obj)
{
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FileMap other = (FileMap) obj;
if (this.file != other.file && (this.file == null || !this.file.equals(other.file)))
{
return false;
}
return true;
}
}