欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

实例解析使用Java实现基本的音频播放器的编写要点

程序员文章站 2024-03-08 10:03:16
 java音频播放,因为必须依赖到本地环境,所以java在音频处理方面优势不大,或者说打从java体系开发时就没太多的考虑音频播放因素,要知道最早的java 1....

 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) ...{
      }
    }