欢迎关注:「Android进化之路」
前言
Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool,AudioTrack,本文重点说下SoundPool。
和MediaPlayer一样,SoundPool也可以用来播放音频文件。然而,与MediaPlayer不同的是,SoundPool更适合快速音效,而不是需要流媒体的较长的音频文件。
参见SoundPool的官方文档其有如下主要特性:
1、对比MediaPlayer低延迟播放;
SoundPool库使用MediaPlayer服务将音频解码为原始的16位PCM单声道或立体声流。这允许应用程序附带压缩流,而不必承受CPU负载和播放期间解压缩的延迟。
2、可以支持多个音频同时播放;
在构造SoundPool对象时,maxStreams参数设置单个SoundPool一次可以播放的最大流数量。SoundPool会追踪活动流的数量。如果超过了流的最大数量,SoundPool将自动停止先前播放的流,首先根据优先级,限制流的最大数量有助于限制CPU加载和减少音频混音的可能性
3、可以支持无限循环播放;
可以通过设置一个非零的循环值来循环声音。值为-1将导致声音永远循环。在这种情况下,应用程序必须通过调用stop()函数来停止声音。任何其他非零的值将导致声音重复指定的次数,例如,一个值为3将导致声音总共播放4次。
4、适用于短音频的播放(如游戏场景提示音等);
加载逻辑遍历声音列表,并调用相应的SoundPool.load()函数。这通常应该在过程的早期完成,以便在需要回放音频之前有时间将音频解压为原始PCM格式。
如果你用过SVGAPlayer,了解其内部播放动画的提示声音的实现就是使用的SoundPool,感兴趣的可以看看,这里不过多讲。
1、SoundPool的使用
1.1、准备音频资源
将准备的音频文放入assets文件夹下或者res下的raw文件夹下:
- assets下可以再新建文件夹批量加载,而raw只能同级存放单个加载;
- 在assets内部单个文件超过1m时可能存在bug,在raw资源目录下不会存在;
- SoundPool的音频文件大小不能超过1M同时时间超过5-6秒可能会出错。
1.2、SoundPool的构造方法
SoundPool(int maxStreams, int streamType, int srcQuality)
- 参数maxStreams指定支持多少个声音,SoundPool对象中允许同时存在的最多的流的数量,该值太大就会报错AudioFlinger could not create track, status: -12 ,就听不到声音,可根据需求设定
- 参数streamType指定声音类型,可以在AudioManager中定义流类型为STREAM_VOICE_CALL, STREAM_SYSTEM, STREAM_RING,STREAM_MUSIC 和STREAM_ALARM四种类型。
- 参数srcQuality指定声音品质(采样率变换质量),一般直接设置为0!
运用方式:
private static final int MAX_SOUNDS = 1;
private SoundPool soundPool;
//第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
复制代码
但是上面的构造方法在api 21 被废弃了。API 21 以后使用SoundPool.Builder创建SoundPool对象实例:
private static final int MAX_SOUNDS = 1;
private SoundPool soundPool;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
soundPool = new SoundPool.Builder().setMaxStreams(MAX_SOUNDS).build();
} else {
//第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
}
复制代码
1.3、加载音频资源
int load(Context context, int resId, int priority)
Load the sound from the specified APK resource.
从APK资源载入,resId:如音频文件 R.raw.xxx
int soundID = soundPool.load(appContext, R.raw.tip, 1);
复制代码
int load(String path, int priority)
Load the sound from the specified path.
从音频文件路径加载
int load(AssetFileDescriptor afd, int priority)
Load the sound from an asset file descriptor.
从asset 文件中加载
AssetFileDescriptor fileDescriptor = assetManager.openFd("sounds/tip.mp3");
int soundId = soundPool.load(fileDescriptor, 1);
复制代码
int load(FileDescriptor fd, long offset, long length, int priority)
Load the sound from a FileDescriptor.
如存放在sd卡中的音频 FileDescriptor从文件中加载
1.4、播放音频资源
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
用法方式:
int streamId = soundPool.play(
soundID, //声音id
1, //左声道:0.0f ~ 1.0f
1, //右声道:0.0f ~ 1.0f
1, //播放优先级:0表示最低优先级
repeatTime, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
1);//播放速度:1是正常,范围从0~2
复制代码
关于播放控制的几个重要方法
// 通过流id暂停指定音频播放
final void pause(int streamID)
//恢复指定音频播放
final void resume(int streamID)
//停止指定音频播放
final void stop(int streamID)
//卸载指定音频
final boolean unload(int soundID)
//暂停所有音频的播放
final void autoPause()
//恢复所有暂停的音频播放
final void autoResum()
//设置指定id的音频循环播放次数
final void setLoop(int streamID, int loop)
//设置加载监听(因为加载是异步的,需要监听加载,完成后再播放)
void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
//设置优先级(同时播放个数超过最大值时,优先级低的先被移除)
final void setPriority(int streamID, int priority)
//设置指定音频的播放速率,0.5~2.0(rate>1:加快播放,反之慢速播放)
final void setRate(int streamID, float rate)
//释放所有资源
final void release()
复制代码
2、SoundPool的封装
SoundPool的封装
- 为了使我们的代码更加清晰,我们将创建一个类,用于在Activity/Fragment之外加载和播放我们的声音。
- 首先,我们创建一个名为SoundPlayer的类。我们需要一些方法来隐藏从外部类访问和加载声音文件的逻辑。
这是SoundPlayer的实现。您可以根据需要更改/添加一些代码,但这是基本要点。
完整代码如下:
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;
import java.util.HashMap;
/**
* 本地提示音播放器
* SoundPool用于播放声音短,文件小的音频,延时短
* @author: heiyulong
* @date: 2021/4/16
*/
public class SoundPlayer {
private static final String TAG = "SoundPlayer";
private static final int MAX_SOUNDS = 3;
private Context appContext;
private SoundPool soundPool;
private HashMap<Integer,Integer> soundMap = new HashMap<>();
public SoundPlayer(Context appContext) {
// 版本兼容
this.appContext = appContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
soundPool = new SoundPool.Builder().setMaxStreams(MAX_SOUNDS).build();
} else {
//第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
}
}
/**
* 播放音频
* @param resId 音频文件 R.raw.xxx
* @param repeatTime 循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
*/
public void play(int resId, int repeatTime) {
int soundID = soundPool.load(appContext, resId, 1);
// 该方法防止sample not ready错误
soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
int streamId = soundPool.play(
soundID, //声音id
1, //左声道:0.0f ~ 1.0f
1, //右声道:0.0f ~ 1.0f
1, //播放优先级:0表示最低优先级
repeatTime, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
1);//播放速度:1是正常,范围从0~2
soundMap.put(resId,streamId);
});
}
/**
* 播放音频
* @param resId 音频文件 R.raw.xxx
*/
public void play(int resId) {
int soundID = soundPool.load(appContext, resId, 1);
// 该方法防止sample not ready错误
soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
int streamId = soundPool.play(
soundID, //声音id
1, //左声道:0.0f ~ 1.0f
1, //右声道:0.0f ~ 1.0f
1, //播放优先级:0表示最低优先级
0, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
1);//播放速度:1是正常,范围从0~2
soundMap.put(resId,streamId);
});
}
/**
* 暂停
* @param resId
*/
public void pause(int resId) {
if (soundPool != null) {
Integer mStreamID = soundMap.get(resId);
if(mStreamID != null){
soundPool.pause(mStreamID);
}
}
}
/**
* 继续
* @param resId
*/
public void resume(int resId) {
if (soundPool != null) {
Integer mStreamID = soundMap.get(resId);
if(mStreamID != null){
soundPool.resume(mStreamID);
}
}
}
/**
* 停止
* @param resId
*/
public void stop(int resId) {
if (soundPool != null) {
Integer mStreamID = soundMap.get(resId);
if(mStreamID != null){
soundPool.stop(mStreamID);
}
}
}
/**
* 资源释放
*/
public void release() {
Log.d(TAG, "Cleaning resources..");
if (soundPool != null) {
soundPool.autoPause();
soundPool.release();
}
}
}
复制代码
这个类的核心组件包括:
- 用于构建SoodPool的构造方法
- SoundPool用于播放我们的声音文件。
- SoundPool用于控制暂停&资源释放的方法
注意:
- 您还将注意到的常量:MAX_SOUNDS。它指定我们SoundPool允许同时播放的声音的最大数量。
- 一旦我们的类被实例化SoundPool就会被构造。通过plyaer(int resId)播放指定音频资源。
- 页面销毁时要记得释放资源release().
欢迎关注:「Android进化之路」