实例解析使用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,loop
   * 
   * @param data
   * @param type
   * @param loop
   */
  public SoundData(byte[] data,boolean loop) ...{

    if (data != null && data.length > 0) ...{
      _data = new byte[data.length];
      // 直接copy byte数组
      System.arraycopy(data,_data,_data.length);
    }
    _type = type;
    _loop = loop;
  }
  
  /** *//**
   * 析构函数,用以注入限定位置的resName,loop
   * @param resName
   * @param type
   * @param loop
   */
  public SoundData(String resName,boolean loop) ...{
    this(StreamHelper.GetDataSource(resName),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.getLoop());

  }

  /** *//**
   * 初始化sound相关数据
   * 
   * @param data
   * @param ftype
   * @param loop
   * @return
   */
  private boolean init(byte[] data,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,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,false);
      break;
      //通过音乐文件路径播放音乐  
    case 2:
      data=new SoundData("C:/谁是大英雄.mid",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,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) ...{
      }
    }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越