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 來創(chuàng)建 SoundManager
類。除了實(shí)現(xiàn)SimpleSoundPlayer 類似的功能外,如從一個(gè)文件加載一個(gè)聲音,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 是一個(gè)線程池,控制每個(gè)線程
一次播放一個(gè)音頻。這樣做可以讓 SoundManager 輕松限制同時(shí)播放音頻的數(shù)量。
<p>擴(kuò)展該類的一些想法:
<ul>
<li>添加一個(gè)setMasterVolume()方法,使用 Controls 來設(shè)置每一個(gè)Line對(duì)象的音量
<li>如果播放一個(gè)聲音延遲了500毫秒就不要再播放了
</ul>
*/
public class SoundManager extends ThreadPool {
private AudioFormat playbackFormat; //音頻格式
private ThreadLocal localLine; //存放 Line 對(duì)象的線程局部變量
private ThreadLocal localBuffer; //存放字節(jié)緩沖區(qū)的線程局部變量
private Object pausedLock; //暫停同步鎖對(duì)象
private boolean paused; //暫停標(biāo)志 true 暫停;false 非暫停
/**
Creates a new SoundManager using the maximum number of
simultaneous sounds.
使用最大同時(shí)播放音頻的數(shù)量創(chuàng)建一個(gè)新的 SoundManager
*/
public SoundManager(AudioFormat playbackFormat) {
this(playbackFormat,
getMaxSimultaneousSounds(playbackFormat));
}
/**
Creates a new SoundManager with the specified maximum
number of simultaneous sounds.
使用參數(shù)指定的最大同時(shí)播放音頻的數(shù)量創(chuàng)建一個(gè)新的 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
//通知線程池中的線程可以啟動(dòng)了
synchronized (this) {
notifyAll();
}
}
/**
Gets the maximum number of simultaneous sounds with the
specified AudioFormat that the default mixer can play.
取得默認(rèn)混頻器能夠播放參數(shù)指定音頻格式的音頻的最大同時(shí)播放數(shù)量。
*/
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. 關(guān)閉之前執(zhí)行清理
*/
protected void cleanUp() {
// signal to unpause 結(jié)束暫停狀態(tài)
setPaused(false);
// close the mixer (stops any running sounds)
//關(guān)閉混頻器(停止播放正在播放的任何音頻)
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.
設(shè)置暫停狀態(tài)。音頻播放可能不會(huì)立即暫停。
*/
public void setPaused(boolean paused) {
if (this.paused != paused) {
synchronized (pausedLock) {
this.paused = paused;
if (!paused) {
// restart sounds啟動(dòng)音頻播放
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. 從文件系統(tǒng)載入音頻。如果發(fā)生錯(cuò)誤,則返回 null 。
*/
public Sound getSound(String filename) {
return getSound(getAudioInputStream(filename));
}
/**
Loads a Sound from an AudioInputStream. 從一個(gè)音頻輸入流載入音頻
*/
public Sound getSound(AudioInputStream audioStream) {
if (audioStream == null) {
return null;
}
// get the number of bytes to read 獲得音頻的字節(jié)數(shù)
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. 通過文件系統(tǒng)的音頻文件創(chuàng)建一個(gè)音頻輸入流
*/
public AudioInputStream getAudioInputStream(String filename) {
try {
// open the source file 打開源文件
AudioInputStream source =
AudioSystem.getAudioInputStream(new File(filename));
// convert to playback format轉(zhuǎn)換成指定播放格式
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.
使用可選的音頻過濾器播放音頻,并指定是否循環(huán)播放。該方法立即返回。
*/
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. 通過一個(gè)輸入流播放音頻。該方法立即返回。
*/
public InputStream play(InputStream is) {
return play(is, null);
}
/**
Plays a sound from an InputStream with an optional
sound filter. This method returns immediately.
通過一個(gè)輸入流使用可選的音頻過濾器播放音頻。該方法立即返回。
*/
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.
指示一個(gè)池化線程已經(jīng)啟動(dòng)。創(chuàng)建線程的Line對(duì)象和字節(jié)緩沖區(qū)
*/
protected void threadStarted() {
// wait for the SoundManager constructor to finish
//等待 SoundManager 構(gòu)造器執(zhí)行完畢
synchronized (this) {
try {
wait();
}
catch (InterruptedException ex) { }
}
// use a short, 100ms (1/10th sec) buffer for filters that
// change in real-time
//對(duì)實(shí)時(shí)改變的過濾使用短小的,100毫秒(1/10秒)緩沖區(qū)
int bufferSize = playbackFormat.getFrameSize() *
Math.round(playbackFormat.getSampleRate() / 10);
// create, open, and start the line創(chuàng)建,打開并啟動(dòng)Line對(duì)象
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對(duì)象不可用,結(jié)束當(dāng)前線程
Thread.currentThread().interrupt();
return;
}
line.start();
// create the buffer 創(chuàng)建一個(gè)字節(jié)緩沖區(qū)
byte[] buffer = new byte[bufferSize];
// set this thread's locals
//將Line對(duì)象和字節(jié)緩沖區(qū)保存在線程局部變量中
localLine.set(line);
localBuffer.set(buffer);
}
/**
Signals that a PooledThread has stopped. Drains and
closes the Thread's Line.
SoundPlayer 類是 PooledThreads 要運(yùn)行的任務(wù)。它從 ThreadLocal
獲取線程的Line對(duì)象和字節(jié)緩沖區(qū),并通過輸入流來播放音頻。
*/
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 要運(yùn)行的任務(wù)。它從 ThreadLocal
獲取線程的Line對(duì)象和字節(jié)緩沖區(qū),并通過輸入流來播放音頻。
<p> 本類只能用于PooledThread 調(diào)用時(shí)
*/
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和緩沖區(qū)
SourceDataLine line = (SourceDataLine)localLine.get();
byte[] buffer = (byte[])localBuffer.get();
if (line == null || buffer == null) {
// the line is unavailable line對(duì)象不可用
return;
}
// copy data to the line拷貝數(shù)據(jù)到line
try {
int numBytesRead = 0;
while (numBytesRead != -1) {
// if paused, wait until unpaused如果暫停,等待暫停結(jié)束
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 類有一個(gè)內(nèi)部類,SoundPlayer,負(fù)責(zé)拷貝音頻數(shù)據(jù)到一個(gè)Line對(duì)象。SoundPlayer是Runnable接口的一個(gè)實(shí)現(xiàn),因此它可以被線程池中的線程作為一個(gè)任務(wù)來執(zhí)行。SoundPlayer 比 SimpleSoundPlayer更強(qiáng)的一點(diǎn)是當(dāng)SoundManager 處于暫停狀態(tài)時(shí),它可以停止復(fù)制數(shù)據(jù)。在暫停狀態(tài),SoundPlayer 調(diào)用 wait()方法,使線程轉(zhuǎn)為閑置等待狀態(tài)直到線程被通知繼續(xù)運(yùn)行。SoundManager 結(jié)束暫停時(shí)會(huì)通知所有正在等待的線程。
線程局部變量 / 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中,一件你想要完成的事是確保每一個(gè)線程都有它自己的Line對(duì)象和字節(jié)緩沖區(qū),以便你可以重復(fù)使用它們,而不用每次播放音頻時(shí)都創(chuàng)建新的對(duì)象。為了給線程池中的每一個(gè)線程提供獨(dú)有的Line對(duì)象和字節(jié)緩沖區(qū),你需要利用線程局部變量。
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.
譯: 局部變量是代碼塊中聲明的變量,而線程局部變量是每一個(gè)線程都擁有不同的值的變量。在本例中,SoundManager 類有localLine 和 localBuffer 兩個(gè)線程局部變量。每個(gè)線程訪問這些變量只能獲取它們自己獨(dú)有的
Line 對(duì)象和 字節(jié)緩沖區(qū),一個(gè)線程不能訪問另外一個(gè)線程的線程局部變量。線程局部變量通過 ThreadLocal 類創(chuàng)建。
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 類。你需要一種方式在線程啟動(dòng)時(shí)創(chuàng)建線程局部變量,在線程終止時(shí)清理局部變量。為了做到這一點(diǎn),在 PooledThread 類中,當(dāng)每一個(gè)線程啟動(dòng)和終止時(shí),我們通知ThreadPool 類:
public void run() {
// signal that this thread has started指示線程已經(jīng)啟動(dòng)
threadStarted();
while (!isInterrupted()) {
// get a task to run 獲取要運(yùn)行的任務(wù)
Runnable task = null;
try {
task = getTask();
}
catch (InterruptedException ex) { }
// if getTask() returned null or was interrupted,
// close this thread. 如果 getTask() 返回 null或 中斷時(shí)關(guān)閉線程
if (task == null) {
break;
}
// run the task, and eat any exceptions it throws
// 運(yùn)行任務(wù),捕捉拋出的任何異常
try {
task.run();
}
catch (Throwable t) {
uncaughtException(this, t);
}
}
// signal that this thread has stopped
// 指示這個(gè)線程已經(jīng)停止
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() 方法創(chuàng)建一個(gè) Line
和一個(gè)
新的字節(jié)緩沖區(qū),并將它們保存到線程局部變量。在 threadStopped() 方法中,Line 被排空并關(guān)閉。
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 類還提供了更簡(jiǎn)單的音頻播放方法。這個(gè)音頻播放方法負(fù)責(zé)創(chuàng)建 ByteArrayInputStreams 和
FilteredSoundStreams,因此你只需要傳遞一個(gè)
Sound 對(duì)象和一個(gè)可選的SoundFilter 參數(shù)給它就行了。
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.
譯:這樣我們就完成了一個(gè)簡(jiǎn)單小巧的新音頻管理器。到此為止,你已經(jīng)學(xué)會(huì)了播放音頻,使用音頻過濾器,甚至是仿真 3D 音頻。接下來我們將繼續(xù)另一個(gè)主題:音樂。
學(xué)軟件開發(fā),到蜂鳥科技!
超強(qiáng)的師資力量 、完善的課程體系 、超低的培訓(xùn)價(jià)格 、真實(shí)的企業(yè)項(xiàng)目。
網(wǎng)址:www.ntcsoft.com
電話:0371-63839606
鄭州軟件開發(fā)興趣小組群:38236716