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

作者:cping 时间:2023-11-29 14:54:26 

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


标签:Java,音频
0
投稿

猜你喜欢

  • Springboot全局异常捕获及try catch区别解析

    2022-03-02 17:44:07
  • 辨析Java中的String与StringBuffer及StringBuilder字符串类

    2023-08-21 19:59:50
  • java实现点击按钮事件弹出子窗口

    2023-11-17 14:54:45
  • Android定时器实现的几种方式整理及removeCallbacks失效问题解决

    2022-10-04 13:21:50
  • Android用户注册界面简单设计

    2023-07-13 02:59:50
  • 详解Java反射创建对象

    2022-12-10 03:52:23
  • 关于springboot集成阿里云短信的问题

    2023-08-23 09:46:15
  • Java后端Cookie实现(时间戳)代码实例

    2022-05-17 09:39:52
  • Spring的IOC控制反转详解

    2023-08-24 02:50:50
  • spring cache注解@Cacheable缓存穿透详解

    2023-12-23 13:41:25
  • flutter使用tauri实现一个一键视频转4K软件

    2022-10-23 05:46:42
  • 简单实现安卓里百度地图持续定位

    2023-07-29 07:59:22
  • Android自定义垂直拖动seekbar进度条

    2023-03-21 13:19:02
  • Android RecyclerView实现滑动删除

    2022-11-20 19:50:42
  • Mybatis环境配置及测试详解

    2023-11-24 07:06:25
  • C语言数据结构之单链表与双链表的增删改查操作实现

    2023-01-24 07:27:35
  • Android开源框架的SlidingFragment的使用示例

    2021-06-18 09:37:31
  • 官网项目Jetpack Startup库学习

    2021-10-03 16:33:21
  • Java java.sql.Timestamp时间戳案例详解

    2023-11-10 13:50:47
  • Java版画板的实现方法

    2023-01-06 22:31:44
  • asp之家 软件编程 m.aspxhome.com