Next create the SoundManager class in Listing
4.10. Along with implementing similar functions to SimpleSoundPlayer, such as loading a sound from a file, SoundManager reduces the lag and gives you the capability to pause
playing sounds.
译:下一步通过代码清单4.10 来创建 SoundManager
类。除了实现SimpleSoundPlayer 类似的功能外,如从一个文件加载一个声音,SoundManager 减少了延迟,并听提供了暂停声音播放的功能。
Listing 4.10 SoundManager.java
package com.brackeen.javagamebook.sound;
import java.io.*;
import javax.sound.sampled.*;
import javax.sound.midi.*;
import com.brackeen.javagamebook.util.ThreadPool;
import com.brackeen.javagamebook.util.LoopingByteInputStream;
/**
The SoundManager class manages sound playback. The
SoundManager is a ThreadPool, with each thread playing back
one sound at a time. This allows the SoundManager to
easily limit the number of simultaneous sounds being played.
<p>Possible ideas to extend this class
<ul>
<li>add a setMasterVolume() method, which uses Controls to
set the volume for each line.
<li>don't play a sound if more than, say, 500ms have passed
since the request to play
</ul>
译:SoundManager 类管理音频播放。SoundManager 是一个线程池,控制每个线程
一次播放一个音频。这样做可以让 SoundManager 轻松限制同时播放音频的数量。
<p>扩展该类的一些想法:
<ul>
<li>添加一个setMasterVolume()方法,使用 Controls 来设置每一个Line对象的音量
<li>如果播放一个声音延迟了500毫秒就不要再播放了
</ul>
*/
public class SoundManager extends ThreadPool {
private AudioFormat playbackFormat; //音频格式
private ThreadLocal localLine; //存放 Line 对象的线程局部变量
private ThreadLocal localBuffer; //存放字节缓冲区的线程局部变量
private Object pausedLock; //暂停同步锁对象
private boolean paused; //暂停标志 true 暂停;false 非暂停
/**
Creates a new SoundManager using the maximum number of
simultaneous sounds.
使用最大同时播放音频的数量创建一个新的 SoundManager
*/
public SoundManager(AudioFormat playbackFormat) {
this(playbackFormat,
getMaxSimultaneousSounds(playbackFormat));
}
/**
Creates a new SoundManager with the specified maximum
number of simultaneous sounds.
使用参数指定的最大同时播放音频的数量创建一个新的 SoundManager
*/
public SoundManager(AudioFormat playbackFormat,
int maxSimultaneousSounds)
{
super(maxSimultaneousSounds);
this.playbackFormat = playbackFormat;
localLine = new ThreadLocal();
localBuffer = new ThreadLocal();
pausedLock = new Object();
// notify threads in pool it's okay to start
//通知线程池中的线程可以启动了
synchronized (this) {
notifyAll();
}
}
/**
Gets the maximum number of simultaneous sounds with the
specified AudioFormat that the default mixer can play.
取得默认混频器能够播放参数指定音频格式的音频的最大同时播放数量。
*/
public static int getMaxSimultaneousSounds(
AudioFormat playbackFormat)
{
DataLine.Info lineInfo = new DataLine.Info(
SourceDataLine.class, playbackFormat);
Mixer mixer = AudioSystem.getMixer(null);
return mixer.getMaxLines(lineInfo);
}
/**
Does any clean up before closing. 关闭之前执行清理
*/
protected void cleanUp() {
// signal to unpause 结束暂停状态
setPaused(false);
// close the mixer (stops any running sounds)
//关闭混频器(停止播放正在播放的任何音频)
Mixer mixer = AudioSystem.getMixer(null);
if (mixer.isOpen()) {
mixer.close();
}
}
public void close() {
cleanUp();
super.close();
}
public void join() {
cleanUp();
super.join();
}
/**
Sets the paused state. Sounds may not pause immediately.
设置暂停状态。音频播放可能不会立即暂停。
*/
public void setPaused(boolean paused) {
if (this.paused != paused) {
synchronized (pausedLock) {
this.paused = paused;
if (!paused) {
// restart sounds启动音频播放
pausedLock.notifyAll();
}
}
}
}
/**
Returns the paused state. 返回是否暂停 true 暂停;false 非暂停
*/
public boolean isPaused() {
return paused;
}
/**
Loads a Sound from the file system. Returns null if an
error occurs. 从文件系统载入音频。如果发生错误,则返回 null 。
*/
public Sound getSound(String filename) {
return getSound(getAudioInputStream(filename));
}
/**
Loads a Sound from an AudioInputStream. 从一个音频输入流载入音频
*/
public Sound getSound(AudioInputStream audioStream) {
if (audioStream == null) {
return null;
}
// get the number of bytes to read 获得音频的字节数
int length = (int)(audioStream.getFrameLength() *
audioStream.getFormat().getFrameSize());
// read the entire stream 读取输入流
byte[] samples = new byte[length];
DataInputStream is = new DataInputStream(audioStream);
try {
is.readFully(samples);
}
catch (IOException ex) {
ex.printStackTrace();
}
// return the samples返回音频样本
return new Sound(samples);
}
/**
Creates an AudioInputStream from a sound from the file
system. 通过文件系统的音频文件创建一个音频输入流
*/
public AudioInputStream getAudioInputStream(String filename) {
try {
// open the source file 打开源文件
AudioInputStream source =
AudioSystem.getAudioInputStream(new File(filename));
// convert to playback format转换成指定播放格式
return AudioSystem.getAudioInputStream(
playbackFormat, source);
}
catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
ex.printStackTrace();
}
catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return null;
}
/**
Plays a sound. This method returns immediately.
播放音频。该方法立即返回。
*/
public InputStream play(Sound sound) {
return play(sound, null, false);
}
/**
Plays a sound with an optional SoundFilter, and optionally
looping. This method returns immediately.
使用可选的音频过滤器播放音频,并指定是否循环播放。该方法立即返回。
*/
public InputStream play(Sound sound, SoundFilter filter,
boolean loop)
{
InputStream is;
if (sound != null) {
if (loop) {
is = new LoopingByteInputStream(
sound.getSamples());
}
else {
is = new ByteArrayInputStream(sound.getSamples());
}
return play(is, filter);
}
return null;
}
/**
Plays a sound from an InputStream. This method
returns immediately. 通过一个输入流播放音频。该方法立即返回。
*/
public InputStream play(InputStream is) {
return play(is, null);
}
/**
Plays a sound from an InputStream with an optional
sound filter. This method returns immediately.
通过一个输入流使用可选的音频过滤器播放音频。该方法立即返回。
*/
public InputStream play(InputStream is, SoundFilter filter) {
if (is != null) {
if (filter != null) {
is = new FilteredSoundStream(is, filter);
}
runTask(new SoundPlayer(is));
}
return is;
}
/**
Signals that a PooledThread has started. Creates the
Thread's line and buffer.
指示一个池化线程已经启动。创建线程的Line对象和字节缓冲区
*/
protected void threadStarted() {
// wait for the SoundManager constructor to finish
//等待 SoundManager 构造器执行完毕
synchronized (this) {
try {
wait();
}
catch (InterruptedException ex) { }
}
// use a short, 100ms (1/10th sec) buffer for filters that
// change in real-time
//对实时改变的过滤使用短小的,100毫秒(1/10秒)缓冲区
int bufferSize = playbackFormat.getFrameSize() *
Math.round(playbackFormat.getSampleRate() / 10);
// create, open, and start the line创建,打开并启动Line对象
SourceDataLine line;
DataLine.Info lineInfo = new DataLine.Info(
SourceDataLine.class, playbackFormat);
try {
line = (SourceDataLine)AudioSystem.getLine(lineInfo);
line.open(playbackFormat, bufferSize);
}
catch (LineUnavailableException ex) {
// the line is unavailable - signal to end this thread
// Line对象不可用,结束当前线程
Thread.currentThread().interrupt();
return;
}
line.start();
// create the buffer 创建一个字节缓冲区
byte[] buffer = new byte[bufferSize];
// set this thread's locals
//将Line对象和字节缓冲区保存在线程局部变量中
localLine.set(line);
localBuffer.set(buffer);
}
/**
Signals that a PooledThread has stopped. Drains and
closes the Thread's Line.
SoundPlayer 类是 PooledThreads 要运行的任务。它从 ThreadLocal
获取线程的Line对象和字节缓冲区,并通过输入流来播放音频。
*/
protected void threadStopped() {
SourceDataLine line = (SourceDataLine)localLine.get();
if (line != null) {
line.drain();
line.close();
}
}
/**
The SoundPlayer class is a task for the PooledThreads to
run. It receives the thread's Line and byte buffer from
the ThreadLocal variables and plays a sound from an
InputStream.
<p>This class only works when called from a PooledThread.
SoundPlayer 类是 PooledThreads 要运行的任务。它从 ThreadLocal
获取线程的Line对象和字节缓冲区,并通过输入流来播放音频。
<p> 本类只能用于PooledThread 调用时
*/
protected class SoundPlayer implements Runnable {
private InputStream source;
public SoundPlayer(InputStream source) {
this.source = source;
}
public void run() {
// get line and buffer from ThreadLocals从线程局部变量获取line和缓冲区
SourceDataLine line = (SourceDataLine)localLine.get();
byte[] buffer = (byte[])localBuffer.get();
if (line == null || buffer == null) {
// the line is unavailable line对象不可用
return;
}
// copy data to the line拷贝数据到line
try {
int numBytesRead = 0;
while (numBytesRead != -1) {
// if paused, wait until unpaused如果暂停,等待暂停结束
synchronized (pausedLock) {
if (paused) {
try {
pausedLock.wait();
}
catch (InterruptedException ex) {
return;
}
}
}
// copy data
numBytesRead =
source.read(buffer, 0, buffer.length);
if (numBytesRead != -1) {
line.write(buffer, 0, numBytesRead);
}
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
The SoundManager class
extends the ThreadPool class, which we moved to the com.brackeen.javagamebook.util package.
译: SoundManager 类继承了被我们移到了 com.brackeen.javagamebook.util 包下的ThreadPool 类。
The SoundManager class has an
inner class, SoundPlayer, which does the work of copying sound data to a Line. SoundPlayer is
an implementation of the Runnable interface, so it can be used as a task for a thread in the thread
pool. An addition to SoundPlayer over SimpleSoundPlayer is that it stops copying data if the SoundManager is in the
paused state. If it is in the paused state, SoundPlayer calls wait(), which
causes the thread to wait idly until it is notified. SoundManager
notifies all waiting threads when it is unpaused.
译: SoundManager 类有一个内部类,SoundPlayer,负责拷贝音频数据到一个Line对象。SoundPlayer是Runnable接口的一个实现,因此它可以被线程池中的线程作为一个任务来执行。SoundPlayer 比 SimpleSoundPlayer更强的一点是当SoundManager 处于暂停状态时,它可以停止复制数据。在暂停状态,SoundPlayer 调用 wait()方法,使线程转为闲置等待状态直到线程被通知继续运行。SoundManager 结束暂停时会通知所有正在等待的线程。
线程局部变量 / Thread-Local Variables
One thing you wanted
to accomplish in SoundManager is to make sure each thread has its own
Line and byte buffer so you can reuse them without having to create new objects
every time a sound is played. To give each thread in the thread pool its own
Line and byte buffer, you'll take advantage of thread-local variables.
译: 在SoundManager中,一件你想要完成的事是确保每一个线程都有它自己的Line对象和字节缓冲区,以便你可以重复使用它们,而不用每次播放音频时都创建新的对象。为了给线程池中的每一个线程提供独有的Line对象和字节缓冲区,你需要利用线程局部变量。
Whereas local
variables are variables that are local to a block of code, thread-local
variables are variables that have a different value for every thread. In this
example, the SoundManager class has the thread-local variables, localLine and localBuffer. Each
thread that accesses these variables can have its own Line and byte buffer, and
no other thread can access another thread's local variables. Thread-local
variables are created with the ThreadLocal
class.
译: 局部变量是代码块中声明的变量,而线程局部变量是每一个线程都拥有不同的值的变量。在本例中,SoundManager 类有localLine 和 localBuffer 两个线程局部变量。每个线程访问这些变量只能获取它们自己独有的
Line 对象和 字节缓冲区,一个线程不能访问另外一个线程的线程局部变量。线程局部变量通过 ThreadLocal 类创建。
For thread-local
variables to work, you need to cheat a little here and update the ThreadPool class. You need a way to create the thread-local
variables when a thread starts and to do any cleanup of the thread-local
variables when the thread dies. To do this, in PooledThread, signal the ThreadPool class when each thread starts and stops:
译: 为了能够使用上述线程局部变量,我们需要修改 ThreadPool 类。你需要一种方式在线程启动时创建线程局部变量,在线程终止时清理局部变量。为了做到这一点,在 PooledThread 类中,当每一个线程启动和终止时,我们通知ThreadPool 类:
public void run() {
// signal that this thread has started指示线程已经启动
threadStarted();
while (!isInterrupted()) {
// get a task to run 获取要运行的任务
Runnable task = null;
try {
task = getTask();
}
catch (InterruptedException ex) { }
// if getTask() returned null or was interrupted,
// close this thread. 如果 getTask() 返回 null或 中断时关闭线程
if (task == null) {
break;
}
// run the task, and eat any exceptions it throws
// 运行任务,捕捉抛出的任何异常
try {
task.run();
}
catch (Throwable t) {
uncaughtException(this, t);
}
}
// signal that this thread has stopped
// 指示这个线程已经停止
threadStopped();
}
In the ThreadPool class, the threadStarted() and threadStopped()
methods don't do anything, but in SoundManager,
they're put to use. The threadStarted() method creates a new Line and a new byte
buffer, and adds them to the thread-local variables. In the threadStopped() method, the Line is drained and closed.
译:在 ThreadPool 类中, threadStarted() 方法和threadStopped()
方法什么都不做。但是在 SoundManager 类中,他们被覆盖使用。threadStarted() 方法创建一个 Line
和一个
新的字节缓冲区,并将它们保存到线程局部变量。在 threadStopped() 方法中,Line 被排空并关闭。
Besides reducing the
lag and enabling you to pause playing sounds, SoundManager provides easier methods for playing sound. It takes
care of the creation of ByteArrayInputStreams or FilteredSoundStreams, so all you have to do is pass it a Sound object and an optional SoundFilter.
译:除了减少延迟和允许暂停播放音频外,SoundManager 类还提供了更简单的音频播放方法。这个音频播放方法负责创建 ByteArrayInputStreams 和
FilteredSoundStreams,因此你只需要传递一个
Sound 对象和一个可选的SoundFilter 参数给它就行了。
That's it for the
nifty new sound manager. So far, you've learned to play sound, use sound
filters, and even emulate 3D sound. Now you'll move on to the other sound
topic: music.
译:这样我们就完成了一个简单小巧的新音频管理器。到此为止,你已经学会了播放音频,使用音频过滤器,甚至是仿真 3D 音频。接下来我们将继续另一个主题:音乐。
学软件开发,到蜂鸟科技!
超强的师资力量 、完善的课程体系 、超低的培训价格 、真实的企业项目。
网址:www.ntcsoft.com
电话:0371-63839606
郑州软件开发兴趣小组群:38236716