posts - 0,  comments - 6,  trackbacks - 0
小节4.5 Creating a Real-Time Echo Filter
小节 4.6 Emulating 3D Sound
小节4.5 Creating a Real-Time Echo Filter
由于以上小节内容我暂时没有用到,所以先跳过。

小节 4.7 创建声音管理器 Creating a Sound Manager

 

Whenever you play a new sound with the SimpleSoundPlayer, the following steps occur:

译:当用 SimpleSoundPlayer 播放一个新的声音时,会经过以下步骤:

1. Create a new thread.  创建一个新线程。

2. Create a new byte buffer. 创建一个新字节缓冲区。

3. Create, open, and start a new Line. 创建、打开并启动一个新Line

That's a lot of object creation. Plus, a new thread must start and native resources that operate the Line must be allocated. All this adds up to one thing: lag. It takes too long from the time you request to play a sound to the time when the sound is actually played.

译:以上过程会创建很多对象,外加上必须启动一个新线程和分配用于操作Line的被本地资源。所有这些加到一起导致一件事情:延迟。从请求播放声音到声音确实被播放经过了过长的时间。

Although the lag might not matter in some instances, such as when you play a startup sound, other times the lag is very noticeable, such as when you're blasting a gun several times a second. You want to eliminate the lag as much as possible.

虽然在有些情况下延迟没有什么问题,如当你播放一个启动声音时。但有时延迟是个很明显的问题,例如当你每秒钟连续射击时,你想尽可能的消除延迟。

To reduce the lag, rework the sound-playing architecture. Instead of starting a new thread for every sound played, you'll use the ThreadPool class from Chapter 1, "Java Threads." This keeps several threads ready and waiting to be assigned a sound to play.

译:为了减少延迟,需要重构声音播放体系结构。用第一章“Java 线程”讲到的ThreadPool类采用的线程池机制,使用若干准备好的线程,等待分配一个声音来播放,而不是每播放一个声音都启动一个新的线程。

Second, each thread in the thread pool can contain its own buffer and Line object, so you don't have to allocate those every time a sound is played.

译:第二,每一个线程池中的线程都拥有它们自己的缓冲区和Line对象,从而避免每次播放声音时都分配一个缓冲区和Line对象。

The drawback to this technique is that every line must be assigned an AudioFormat beforehand, so you can play only one type of audio format. This shouldn't be a problem—just pick an audio format that you want to use for your game, and make sure all your sounds have that same format.

译:这个技巧的缺点是每一个line必须被预先指派一个 AudioFormat ,所以你只能播放一种声频格式。这并不是什么问题——为你的游戏选择一个你想使用音频格式并确保所有的音频文件都是这种音频格式就可以了。

Sound 类 The Sound Class 

Now let's implement this architecture. First you'll create a simple class that contains a sound sample, called Sound, in Listing 4.9.

译:现在让我们来实现这个体系结构。首先你要创建一个包含一个声音样本的简单类,叫做Sound,请看代码清单 4.9 

Listing 4.9 Sound.java

package com.brackeen.javagamebook.sound;

/**

    The Sound class is a container for sound samples. The sound

samples are format-agnostic and are stored as a byte array.

Sound 类是声音样本的容器。声音样本是格式无关的,存放成字节数组

*/

public class Sound {

    private byte[] samples;

    /**

        Create a new Sound object with the specified byte array.

        The array is not copied. 

用指定字节数组创建一个新的Sound对象并不复制数组。

    */

    public Sound(byte[] samples) {

        this.samples = samples;

    }

    /**

        Returns this Sound's objects samples as a byte array.

        以字节数组返回Sound对象的样本

    */

    public byte[] getSamples() {

        return samples;

    }

}

The Sound class is a wrapper class for a byte buffer that contains sound samples. It doesn't serve much of a purpose other than making the code more understandable and hiding the contents of a sound, in case you want to change it later.

译:Sound 类是一个包含声音样本的字节缓冲区的包装类。该类的主要目的是增加代码可读性和隐藏音频数据的内容,以便将来能够进行扩展。

SoundManager   /  The SoundManager Class

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对象。SoundPlayerRunnable接口的一个实现,因此它可以被线程池中的线程作为一个任务来执行。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

 

posted on 2010-11-27 01:08 whistler 阅读(606) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航:
 
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

留言簿(2)

我参与的团队

文章档案(22)

搜索

  •  

最新评论