五、播放音乐
TestBase类包含主要的播放逻辑。例如,当用户点击“播放”按钮,TestBase类中的play()方法开始执行。
public void play() {
if ((!stopped)
(paused)) return;
if (playerThread == null) {
playerThread = new Thread(this);
playerThread.start();
try { Thread.sleep(500);
} catch (Exception ex) {}
}
synchronized(synch) {
stopped = false;
synch.notifyAll();
}
}
play ()方法首先确认播放器当前已被终止播放,而不是暂停播放。然后它检查这是不是第一次调用play():如果是,则创建一个playerThread线程。我们用一个独立的线程负责音乐播放,这样,无论播放器正在读取文件、解码,还是正在把音频数据输出到扬声器,用户界面总是可操作的。
启动线程之后,play()方法锁定静态synch同步对象,将stopped标记设置为false,然后通知正在等待的线程(playerThread线程在开始播放音乐文件之前,会等待静态synch对象上的提醒通知)。
playerThread 线程启动后,它的run()方法开始运行。这个线程一直执行while循环,直到threadExit标记变成true为止。在while循环中,线程首先等待“开始播放”的信号(当用户点击“播放”按钮时),然后播放音乐。表二列出了描述播放器状态的各个标记及其含义。
public void run() {
while (! threadExit) {
waitforSignal();
if (! stopped)
playMusic();
}
}
playMusic ()方法利用JavaSound API播放当前选中的文件。首先要通过AudioSystem类获得一个AudioInputStream。然后,利用AudioInputStream 的getFormat()获知音频数据的格式。在此基础上,我们试图通过getLine()方法获得一个支持该种格式的SourceDataLine。如果要播放的是WAV文件,现在我们已经有了非压缩的PCM格式的音频数据,可以用line对象开始播放音频。
ais= AudioSystem.getAudioInputStream(new File(fileToPlay));
…
if (ais != null) {
baseFormat = ais.getFormat();
line = getLine(baseFormat);
...
}
如果音频数据是压缩格式的,如MP3或Ogg,必须先进行一次转换??把MP3/Ogg解码成PCM。解码主要包括三个步骤:
1、创建一个解压缩结果的定制AudioFormat(PCM编码),但保留和原压缩流一样的取样率、通道信息等。
2、创建一个AudioInputStream把原来的AudioInputStream转换成新的AudioFormat格式。
3、获得一个处理解码后格式的SourceDataLine。
如下所示:
AudioFormat decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false);
ais = AudioSystem.getAudioInputStream(decodedFormat, ais);
line = getLine(decodedFormat);
getLine ()方法的返回值是一个与参数中指定的AudioFormat兼容的SourceDataLine。如果不能获得兼容的SourceDataLine, getLine()返回null。在getLine()方法中,我们首先创建和填充一个DataLine.Info结构,调用 AudioSystem.getLine()方法,将info结构传递给AudioSystem类工厂。
private SourceDataLine getLine(AudioFormat audioFormat) {
SourceDataLine res = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,
audioFormat);
try {
res = (SourceDataLine) AudioSystem.getLine(info);
res.open(audioFormat);
}
catch (Exception e) {}
return res;
}
准备好AudioInputStream和SourceDataLine之后,playMusic()剩余的任务已经很简单:用一个循环从AudioInputStream读取数据,然后写入到SourceDataLine。
int inBytes = 0;
while ((inBytes != -1) && (!stopped) && (!threadExit)) {
try {
inBytes = ais.read(audioData, 0, BUFFER_SIZE);
}
catch (IOException e) { e.printStackTrace(); }
if (inBytes >= 0) {
int outBytes = line.write(audioData, 0, inBytes);
}
if (paused) waitforSignal();
}