高效的Java音乐播放类

来源:互联网 发布:程序员有多少种语言 编辑:程序博客网 时间:2024/05/17 11:33
高效的Java音乐播放类
   Java音频播放,因为必须依赖到本地环境,所以JAVA在音频处理方面优势不大,或者说打从Java体系开发时就没太多的考虑音频播放因素,要知道最早的Java 1.1版本中,没有后来的javax.sound包,音频只能通过Applet包调取……

  遗憾的是,在图形程序开发中,我们的程序却又难免要使用到背景音乐、效果音等配合图像操作,哎,这实在是Sun大神给我们开的一个不打不小的玩笑。万幸后来Sun大神开眼,提供了javax.sound包,才解救我们于水深火热当中~

 但是继之而来的问题是,在javax.sound包的使用中,如同Java多媒体工具类的通病般,并没有提供十分完善的释放机制。如果我们做Windows开发,调用MediaPlayer反复N次可能没也什么大碍,但在Java中,如果音频程序反复运行的话,极容易出现内存累计损耗的情况,以至于最后抛出一个java.lang.OutOfMemoryError,然后……程序就挂了,用户就傻了,我们就疯了……

这已经是“是可忍孰不可忍”的问题了,有鉴于此,所以在本人的Loonframework框架开发中,二次整合了sound下的相关方法,力求以最简单的代码,做出最完善的音频控制类。在Loonframework-game还没有大成的现在,先摘录一部分方法,以供各位看官——拍砖!

对应网络资源调用,在Loonframework中建立了自己的uri用类,基本内容如下:
(其中StreamHelper为Loonframework自己的流媒体控制类,getHttpStream方法请自行替换。)
package org.loon.framework.game.net;

import org.loon.framework.game.helper.StreamHelper;

/**
 * <p>
 * Title: LoonFramework
 * </p>
 * <p>
 * Description:Loonframework专用uri(统一资源标识符)
 * </p>
 * <p>
 * Copyright: Copyright (c) 2007
 * </p>
 * <p>
 * Company: LoonFramework
 * </p>
 * 
 * 
@author chenpeng
 * @email:ceponline@yahoo.com.cn
 * 
@version 0.1
 
*/

public class URI {

    
//传输协议类型
    public static final int _L_URI_HTTP = 1;

    
public static final int _L_URI_UDP = 2;

    
private String _uri;

    
private int _type;

    
/**
     * 析构函数,用于注入uri和type
     * 
     * 
@param uri
     * 
@param type
     
*/

    
public URI(String uri, int type) {
        _uri 
= new String(uri);
        _type 
= type;
    }


    
/**
     * 析构函数,用于注入uri
     * 
     * 
@param uri
     
*/

    
public URI(String uri) {
        _uri 
= new String(uri);
        _type 
= URI._L_URI_HTTP;
    }


    
/**
     * 返回uri所在位置资源的byte数组。
     * 
     * 
@return
     
*/

    
public byte[] getData() {
        
if (_uri == null{
            
return null;
        }

        
return StreamHelper.getHttpStream(_uri);
    }


    
public String getURI() {
        
return _uri;
    }


    
public int getType() {
        
return _type;
    }


}


在Loonframework框架中,定制了一个基础的SoundData类,用以统一管理音频数据源。

package org.loon.framework.game.sound;

import org.loon.framework.game.helper.StreamHelper;
import org.loon.framework.game.net.URI;

/**
 * <p>
 * Title: LoonFramework
 * </p>
 * <p>
 * Description:用以获得并缓存声音文件数据(更进一步内容操作请见Loonframework-game框架)
 * </p>
 * <p>
 * Copyright: Copyright (c) 2007
 * </p>
 * <p>
 * Company: LoonFramework
 * </p>
 * 
 * 
@author chenpeng
 * @email:ceponline@yahoo.com.cn
 * 
@version 0.1
 
*/

public class SoundData {

    
private byte[] _data;

    
private boolean _loop;

    
private int _type;

    
public static final int _L_SOUNDTYPE_MIDI = 1;

    
public static final int _L_SOUNDTYPE_WAV = 2;

    
/**
     * 析构函数,用以注入uri,type,loop
     * 
     * 
@param uri
     * 
@param type
     * 
@param loop
     
*/

    
public SoundData(URI uri, int type, boolean loop) {
        
if (uri != null{
            _data 
= uri.getData();
        }

        _type 
= type;
        _loop 
= loop;
    }

    
    
/**
     * 析构函数,用以注入data,type,loop
     * 
     * 
@param data
     * 
@param type
     * 
@param loop
     
*/

    
public SoundData(byte[] data, int type, boolean loop) {

        
if (data != null && data.length > 0{
            _data 
= new byte[data.length];
            
// 直接copy byte数组
            System.arraycopy(data, 0, _data, 0, _data.length);
        }

        _type 
= type;
        _loop 
= loop;
    }

    
    
/**
     * 析构函数,用以注入限定位置的resName,type,loop
     * 
@param resName
     * 
@param type
     * 
@param loop
     
*/

    
public SoundData(String resName, int type, boolean loop) {
        
this(StreamHelper.GetDataSource(resName),type,loop);
    }


    
public byte[] getData() {
        
return _data;
    }


    
public boolean getLoop() {
        
return _loop;
    }


    
public void setLoop(boolean loop) {
        _loop 
= loop;
    }


    
public int getType() {
        
return _type;
    }


}


Loonframework将音频播放相关方法,封装与SoundPlay之中,程序员可以不必理会javax.sound内部细节,而直接调用SoundPlay完成相关操作。

package org.loon.framework.game.sound;

import java.io.ByteArrayInputStream;

import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;

import org.loon.framework.game.net.URI;

/**
 * <p>
 * Title: LoonFramework
 * </p>
 * <p>
 * Description:用以进行声音文件操作(仅为Loonframework中部分方法,更详细请参见Loonframework-game框架)
 * </p>
 * <p>
 * Copyright: Copyright (c) 2007
 * </p>
 * <p>
 * Company: LoonFramework
 * </p>
 * 
 * 
@author chenpeng
 * @email:ceponline@yahoo.com.cn
 * 
@version 0.1
 
*/

public class SoundPlay implements MetaEventListener, Runnable {

    
private int _sleepTime;

    
private Clip _audio;

    
private Sequencer _midi;

    
private boolean _loop;

    
private int _soundType;

    
private boolean _playing;

    
private Thread _thread = null;

    
private boolean _isRun = false;

    
/**
     * 析构函数,初始化SoundPlay
     * 
     
*/

    
public SoundPlay() {

        _loop 
= false;
        _soundType 
= 0;
        _sleepTime 
= 1000;
        _playing 
= false;

    }


    
// 载入声音文件
    public boolean load(SoundData data) {
        reset();
        
if (data == null || data.getData() == null{
            
return false;
        }

        
return init(data.getData(), data.getType(), data.getLoop());
    }


    
/**
     * 直接播放url文件
     * 
     * 
@param uri
     * 
@param ftype
     * 
@param loop
     * 
@return
     
*/

    
public boolean load(URI uri, int ftype, boolean loop) {

        
// 刷新数据
        reset();
        
if (uri == null{
            
return false;
        }

        
// 获得SoundData
        SoundData data = new SoundData(uri, ftype, loop);
        
if (data == null || data.getData() == null{
            
return false;
        }

        
return init(data.getData(), data.getType(), data.getLoop());

    }


    
/**
     * 初始化sound相关数据
     * 
     * 
@param data
     * 
@param ftype
     * 
@param loop
     * 
@return
     
*/

    
private boolean init(byte[] data, int ftype, boolean loop) {
        
boolean result = false;

        ByteArrayInputStream bis 
= null;

        
try {
            bis 
= new ByteArrayInputStream(data);
        }
 catch (Exception e) {
            bis 
= null;
        }


        
if (bis == null{
            
return false;
        }


        
// 判断类型
        switch (ftype) {

        
// MIDI
        case SoundData._L_SOUNDTYPE_MIDI:

            
// 当MIDI不存在时
            if (_midi == null{

                
try {
                    
// 获得Sequencer
                    _midi = MidiSystem.getSequencer();
                    _midi.open();

                }
 catch (Exception ex) {
                    _midi 
= null;
                }


                
if (_midi != null{
                    _midi.addMetaEventListener(
this);
                }


            }


            
// 当MIDI依旧未获得时
            if (_midi != null{
                
// 重新创建Sequence
                Sequence sc = null;

                
try {
                    sc 
= MidiSystem.getSequence(bis);
                }
 catch (Exception e) {
                    sc 
= null;
                }


                
if (sc != null{

                    
try {

                        _midi.setSequence(sc);

                        
// 获得是否循环播放
                        _loop = loop;

                        
// 获得是否载入
                        result = true;

                    }
 catch (Exception ee) {
                    }


                    
// 获得声音类型
                    _soundType = SoundData._L_SOUNDTYPE_MIDI;

                }


            }


            
try {
                bis.close();
            }
 catch (Exception ee) {
            }


            
break;

        
// Wav
        case SoundData._L_SOUNDTYPE_WAV:

            AudioFileFormat type 
= null;

            
// 获得Audio
            try {
                type 
= AudioSystem.getAudioFileFormat(bis);
            }
 catch (Exception e) {
                type 
= null;
            }


            
// 关闭流
            try {
                bis.close();
            }
 catch (Exception ex) {
            }


            
if (type == null{
                
return false;
            }


            
// 根据指定信息构造数据行的信息对象
            DataLine.Info di = new DataLine.Info(Clip.class, type.getFormat());

            
// 转为Clip
            try {
                _audio 
= (Clip) AudioSystem.getLine(di);
            }
 catch (Exception e) {
            }


            
// 播放文件
            try {

                _audio.open(type.getFormat(), data, 
0, data.length);

                _loop 
= loop;

                result 
= true;

            }
 catch (Exception e) {
            }


            
// 获得文件类型
            _soundType = SoundData._L_SOUNDTYPE_WAV;

            
break;

        }


        
return result;
    }


    
public boolean play(SoundData data) {

        
if (!load(data)) {
            
return false;
        }


        
return play();

    }


    
public boolean play() {

        
switch (_soundType) {

        
case SoundData._L_SOUNDTYPE_MIDI:

            
try {

                _midi.start();

                _playing 
= true;

                _soundType 
= SoundData._L_SOUNDTYPE_MIDI;

            }
 catch (Exception ee) {
            }


            
break;

        
case SoundData._L_SOUNDTYPE_WAV:

            
if (_audio != null{

                
if (_loop) {

                    
// 设定循环
                    _audio.setLoopPoints(0-1);
                    _audio.setFramePosition(
0);

                    _audio.loop(Clip.LOOP_CONTINUOUSLY);

                }
 else {

                    
// 强制设定播放位置至0
                    _audio.setFramePosition(0);

                    _audio.start();

                }


                _playing 
= true;

            }


            
break;

        }


        
return _playing;

    }


    
/**
     * 自动播放,循环停止后结束。
     * 
     * 
@param data
     * 
@return
     
*/

    
public boolean AutoPlay(SoundData data) {
        
if (!load(data)) {
            
return false;
        }

        
return AutoPlay();
    }


    
/**
     * 自动播放,循环停止后结束。
     * 
     * 
@return
     
*/

    
public boolean AutoPlay() {
        _isRun 
= true;
        _thread 
= new Thread(this);
        _thread.start();
        
return _playing;
    }


    
/**
     * 停止播放
     
*/

    
public void stop() {

        
if (_audio != null && _audio.isActive()) {
            
try {
                _audio.stop();
            }
 catch (Exception e) {
            }

        }


        
if (_midi != null{
            _midi.stop();
        }

        _playing 
= false;
        _isRun 
= false;
    }


    
/**
     * 释放数据
     * 
     
*/

    
public void reset() {

        stop();

        _loop 
= false;
        _soundType 
= 0;

        
if (_midi != null{

            _midi.close();

            _midi 
= null;

        }


        
if (_audio != null && _audio.isOpen()) {

            _audio.close();

            _audio 
= null;

        }

        _isRun 
= false;
        _thread 
= null;
    }


    
/**
     * 设定MetaMessage
     
*/

    
public void meta(MetaMessage meta) {
        
// 判断是否循环播放MIDI
        if (_loop && _soundType == SoundData._L_SOUNDTYPE_MIDI
                
&& meta.getType() == 47{

            
if (_midi != null && _midi.isOpen()) {
                _midi.setMicrosecondPosition(
0);
                _midi.start();

            }

        }


    }


    
public void run() {
        
while (_isRun) {
            play();
            
// 因为播放类型唯一,所以只会返回一个_playing结果,以此判定。
            if (_midi != null{
                _playing 
= _midi.isRunning();
            }

            
if (_audio != null{
                _playing 
= _audio.isRunning();
            }

            
// 当播放停止
            if (!_playing) {
                
// 释放
                reset();
            }

            
try {
                Thread.sleep(_sleepTime);
            }
 catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }


    
public int getSleepTime() {
        
return _sleepTime;
    }


    
/**
     * 设定AutoPlay线程循环时间。
     * 
     * 
@param time
     
*/

    
public void setSleepTime(int time) {
        _sleepTime 
= time;
    }

}


这时我们需要面对的,仅是封装为实体的SoundData数据和SoundPlay操作,而不必和繁复的javax.sound再打交道。

调用方法如下:

package org.test;

import org.loon.framework.game.helper.StreamHelper;
import org.loon.framework.game.net.URI;
import org.loon.framework.game.sound.SoundData;
import org.loon.framework.game.sound.SoundPlay;

/**
 * <p>Title: LoonFramework</p>
 * <p>Description:SoundPlay播放测试</p>
 * <p>Copyright: Copyright (c) 2007</p>
 * <p>Company: LoonFramework</p>
 * 
@author chenpeng  
 * @email:ceponline@yahoo.com.cn 
 * 
@version 0.1
 
*/

public class SoundPlayTest {

    
static void selectPlay(int ftype){
        SoundData data
=null;
        
        
switch(ftype){
        
//通过loonframework下uri从网络播放音乐
        case 0:
            data
=new SoundData(new URI("http://looframework.sourceforge.net/midi/谁是大英雄.mid"),SoundData._L_SOUNDTYPE_MIDI,false);
            
break;
        
//通过本地资源下音乐文件的byte[]对象播放音乐
        case 1:
            
byte[] bytes=StreamHelper.GetResourceData("/midi/谁是大英雄.mid");
            data
=new SoundData(bytes,SoundData._L_SOUNDTYPE_MIDI,false);
            
break;
            
//通过音乐文件路径播放音乐    
        case 2:
            data
=new SoundData("C:/谁是大英雄.mid",SoundData._L_SOUNDTYPE_MIDI,false);
            
break;
        }

        SoundPlay play
=new SoundPlay();
        
//AutoPlay与Play方法的区别在于,AutoPlay播放完毕会自动停止并释放资源,play需手动中止。
        
//play.play(data);
        play.AutoPlay(data);
    }

    
    
public static void main(String[]args){
        selectPlay(
2);
    }

    
}




更详细方法,会待Loonframework-game完全公布后,再进行解释。

另:由于StreamHelper关联其他Loonframework中方法,暂不给出,inputStream转byte[]可用如下写法:

//is为获得的inputStream

   ByteArrayOutputStream byteArrayOutputStream 
= new ByteArrayOutputStream();
//用于承接byte[]
        byte[] arrayByte = null;
        
try {
            
// 每次传输大小为4096
            byte[] bytes = new byte[4096];
            bytes 
= new byte[is.available()];
            
int read;
            
while ((read = is.read(bytes)) >= 0{
                byteArrayOutputStream.write(bytes, 
0, read);
            }

            arrayByte 
= byteArrayOutputStream.toByteArray();
        }
 catch (IOException e) {
            
return null;
        }
 finally {
            
try {
                
if (byteArrayOutputStream != null{
                    byteArrayOutputStream.close();
                    byteArrayOutputStream 
= null;
                }

                
if (is != null{
                    is.close();
                    is 
= null;
                }


            }
 catch (IOException e) {
            }

        }
 
原创粉丝点击