目录
1,概述
2,SoundPool 的创建
3,资源加载与播放
4,播放控制
5,播放音量调节
6,资源释放
7,注意事项
8,实例
1,概述
最近工作接触到Android 中的 SoundPool 类,使用后发觉很是有意思,记录一下以备后查。
Android 开发中,难免会碰到音视频相关的处理。SoundPool 是 Android 提供的一个API类,用来播放简短的音频,使用简单但功能相对强大。只需花很少的气力,就可以完成音频的播放、暂停、恢复及停止等操作。从名字上也可以看出,它是一个“pool”,我们可以加载多个音频资源到内存,进行管理与播放,比如控制同时播放流的最大数目。加载资源到内存是需要花费少许时间的,因此我们需要监听加载资源完毕的事件,在加载完毕后才能进行播放,以免发生不可预期的错误。
除了以上介绍外,SoundPool 还有诸多其他功能,诸如调节左右声道的音量值、调整播放的语速、设置播放的优先级以及播放的次数等等,说起来还是挺有意思的。接下来述说 SoundPool 的具体使用。
2,SoundPool 的创建
使用 SoundPool 首先需创建它。SoundPool 的创建方式在不同版本中会有所不同,为了更好的兼容性,应该对api版本进行判断,再对应的进行创建。在Android 5.0 以前 ,直接使用它的构造方法即可,而在这之后,则需要使用Builder模式来创建。
创建完 SoundPool 后,通过setOnLoadCompleteListener设置监听,用来监听资源加载完毕的事件发生。这主要是为了播放做准备.通过名字可猜测到,当音频资源加载完成后,会回调设置的监听的onLoadComplete方法,在这个方法里,可以进行播放音频。
/**
* 创建SoundPool ,注意 api 等级
*/
private void createSoundPoolIfNeeded() {
if (mSoundPool == null) {
// 5.0 及 之后
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = null;
audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mSoundPool = new SoundPool.Builder()
.setMaxStreams(16)
.setAudioAttributes(audioAttributes)
.build();
} else { // 5.0 以前
mSoundPool = new SoundPool(16, AudioManager.STREAM_MUSIC, 0); // 创建SoundPool
}
mSoundPool.setOnLoadCompleteListener(this); // 设置加载完成监听
}
}
Android 5.0 之前,直接通过SoundPool的构造方法来创建,有三个参数:
1.maxStreams 同时播放流的最大数量,当播放的流的数目大于此值,则会选择性停止优先级较低的流
2.streamType 流类型,比如 STREAM_MUSIC
3.srcQuality 采样率转换器质量,目前没有什么作用,默认填充0
如下是方法原型,注释做了详细的说明:
/**
* Constructor. Constructs a SoundPool object with the following
* characteristics:
*
* @param maxStreams the maximum number of simultaneous streams for this
* SoundPool object
* @param streamType the audio stream type as described in AudioManager
* For example, game applications will normally use
* {@link AudioManager#STREAM_MUSIC}.
* @param srcQuality the sample-rate converter quality. Currently has no
* effect. Use 0 for the default.
* @return a SoundPool object, or null if creation failed
* @deprecated use {@link SoundPool.Builder} instead to create and configure a
* SoundPool instance
*/
public SoundPool(int maxStreams, int streamType, int srcQuality) {...}
5.0 之后,使用Builder模式进行构造。Builder可以设置多个参数。
1. setMaxStreams和之前maxStreams参数相同。
2. setAudioAttributes用来设置audio 属性,此值要么不设,要么设置不为null的值,否则会导致异常产生。
如下代码摘录自 SoundPool.Builder 类,可以发现给 setAudioAttributes 传 null 是会抛出异常的,如果不设置的话,在build方法中则会默认创建一个,usage设置为USAGE_MEDIA
public Builder setAudioAttributes(AudioAttributes attributes)
throws IllegalArgumentException {
if (attributes == null) {
throw new IllegalArgumentException("Invalid null AudioAttributes");
}
mAudioAttributes = attributes;
return this;
}
public SoundPool build() {
if (mAudioAttributes == null) {
mAudioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
}
return new SoundPool(mMaxStreams, mAudioAttributes);
}
AudioAttributes 也是使用Builder模式来构造的,setUsage用来设置用途(比如是游戏还是媒体),setContentType用来设置内容类型(比如是视频还是音乐)
通过setUsage方法可知它支持的usage类型,还是挺多的:
/**
* Sets the attribute describing what is the intended use of the the audio signal,
* such as alarm or ringtone. ...
*/
public Builder setUsage(@AttributeUsage int usage) {
switch (usage) {
case USAGE_UNKNOWN:
case USAGE_MEDIA:
case USAGE_VOICE_COMMUNICATION:
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
case USAGE_ALARM:
case USAGE_NOTIFICATION:
case USAGE_NOTIFICATION_RINGTONE:
case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case USAGE_NOTIFICATION_EVENT:
case USAGE_ASSISTANCE_ACCESSIBILITY:
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANCE_SONIFICATION:
case USAGE_GAME:
case USAGE_VIRTUAL_SOURCE:
case USAGE_ASSISTANT:
mUsage = usage;
break;
default:
mUsage = USAGE_UNKNOWN;
}
return this;
}
通过setContentType方法可知支持的contentType:
/**
* Sets the attribute describing the content type of the audio signal, such as speech,or music.
*/
public Builder setContentType(@AttributeContentType int contentType) {
switch (contentType) {
case CONTENT_TYPE_UNKNOWN:
case CONTENT_TYPE_MOVIE:
case CONTENT_TYPE_MUSIC:
case CONTENT_TYPE_SONIFICATION:
case CONTENT_TYPE_SPEECH:
mContentType = contentType;
break;
default:
mUsage = CONTENT_TYPE_UNKNOWN;
}
return this;
}
3,资源加载与播放
播放不是一蹴而就的,如前所述,需要在资源加载完毕后,才能调用play方法进行播放。因此,直接调用play方法是不可取的,而且可能造成难以预料的错误。
因此我把这个方法取为 playMayWait, 当资源没加载时,首先需要调用 SoundPool 的load 方法进行加载,并返回一个mSoundId,它代表一个已加载的资源的id,可以用来播放音效。
加载音频资源异步执行,此过程是需要时间的,当加载成功后,会调用 onLoadComplete 方法,在之前代码已经设置过相关的监听 。当再次进行播放,我们直接调用onLoadComplete方法,复用已加载的资源(在没有释放之前)
/**
* 播放:如果资源还没有加载,则可能会有一小段等待时间
*/
private void playMayWait() {
createSoundPoolIfNeeded();
// mSoundId is invalid ,load from res raw for once before mSoundPool is released
if (mSoundId == DEFAULT_INVALID_SOUND_ID) {
mSoundId = mSoundPool.load(getApplicationContext(), R.raw.autotest, 1); // 加载音频资源
} else {
// reuse the loaded res
if (mStreamID == DEFAULT_INVALID_STREAM_ID)
onLoadComplete(mSoundPool, 0, 0); // manually call this method when there is a valid mSoundId
}
}
此处的load方法是从res/raw 下加载的,需要注意的是,通过改方式加载的资源会忽略后缀名,因此不同类型的资源不能重名。实际上, 提供了多个load 方法,以方便不同的需求:
// Load the sound from the specified path. 根据路径加载
public int load(String path, int priority)
// Load the sound from the specified APK resource 使用应用的资源
public int load(Context context, int resId, int priority)
// Load the sound from an asset file descriptor 使用 assert 文件描述符
public int load(AssetFileDescriptor afd, int priority) {
// Load the sound from a FileDescriptor. 使用文件描述符
public int load(FileDescriptor fd, long offset, long length, int priority)
通过 unload 方法来卸载之前加载的资源 ,参数为已加载过的资源 id
/**
* Unload a sound from a sound ID.
*
* Unloads the sound specified by the soundID. This is the value
* returned by the load() function. Returns true if the sound is
* successfully unloaded, false if the sound was already unloaded.
*
* @param soundID a soundID returned by the load() function
* @return true if just unloaded, false if previously unloaded
*/
public native final boolean unload(int soundID);
onLoadComplete方法是用来播放的合理位置,它的调用预示着资源已经就绪,可以进行播放了,调用 SoundPool 的 play 方法即可进行播放.
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
if (mSoundPool != null) {
if (mStreamID == DEFAULT_INVALID_STREAM_ID)
mStreamID = mSoundPool.play(mSoundId, mCruLeftVolume, mCurRightVolume, 16, -1, 1.0f);
}
}
SoundPool 的 play 方法参数较多,不过都比较简单. 当活跃的stream数量大于maxStreams,调用该方法可导致另一个播放声音被停止:
这个方法依据之前通过load方法加载得到的sound ID , 来播放一个指定的音频文件的声音.
通过注释就比较好的理解参数的内容:
1.soundID load方法返回的值,指向某个已加载的音频资源
2.leftVolume\rightVolume 用来这种左右声道的值.范围 0.0f ~ 1.0f
3.priority 流的优先级
4.loop 循环播放的次数, -1 表示无限循环
5.rate 播放的速率 , 2 表示2倍速度
返回值streamID , 返回0则播放失败,否则成功。通过改id可以进一步进行控制。比如 pause\resume
/**
* Play a sound from a sound ID.
*
* Play the sound specified by the soundID. This is the value
* returned by the load() function. Returns a non-zero streamID
* if successful, zero if it fails. The streamID can be used to
* further control playback. Note that calling play() may cause
* another sound to stop playing if the maximum number of active
* streams is exceeded. A loop value of -1 means loop forever,
* a value of 0 means don't loop, other values indicate the
* number of repeats, e.g. a value of 1 plays the audio twice.
* The playback rate allows the application to vary the playback
* rate (pitch) of the sound. A value of 1.0 means play back at
* the original frequency. A value of 2.0 means play back twice
* as fast, and a value of 0.5 means playback at half speed.
*
* @param soundID a soundID returned by the load() function
* @param leftVolume left volume value (range = 0.0 to 1.0)
* @param rightVolume right volume value (range = 0.0 to 1.0)
* @param priority stream priority (0 = lowest priority)
* @param loop loop mode (0 = no loop, -1 = loop forever)
* @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
* @return non-zero streamID if successful, zero if failed
*/
public final int play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate) {
baseStart();
return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
}
4,播放控制
streamID是对流的特定实例的引用,通过 SoundPool 的 pause\resume\stop 方法, 就可以对播放进行控制。
1. pause方法用来暂停指定mStreamID的流 (暂停播放)
2. resume方法用来恢复指定mStreamID的流 (恢复播放)
3. stop方法用来停止指定mStreamID的流 (停止播放)
public void pause(View view) {
if (mSoundPool != null) {
mSoundPool.pause(mStreamID);
}
}
public void resume(View view) {
if (mSoundPool != null) {
mSoundPool.resume(mStreamID);
}
}
public void stop(View view) {
if (mSoundPool != null) {
mSoundPool.stop(mStreamID);
mStreamID = DEFAULT_INVALID_STREAM_ID;
}
}
5,播放音量调节
通过 SoundPool 的 setVolume 方法就可以设置指定 mStreamID 的流的左右声道的音量值.
如下,通过SeekBar 进行动态调节
private OnSeekBarChangeListenerAdapter seekBarListener = new OnSeekBarChangeListenerAdapter() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
super.onProgressChanged(seekBar, progress, fromUser);
if(mSoundPool == null) return;
float volume = progress * 1.0f / seekBar.getMax();
switch (seekBar.getId()) {
case R.id.id_sk_left_vloume: // 设置左 volume
mCruLeftVolume = volume;
mSoundPool.setVolume(mStreamID, mCruLeftVolume, mCurRightVolume);
break;
case R.id.id_sk_right_vloume: // 设置又 volume
mCurRightVolume = volume;
mSoundPool.setVolume(mStreamID, mCruLeftVolume, mCurRightVolume);
break;
}
}
};
6,资源释放
通过unload 方法来卸载之前load的资源 , 并通过 release 方法释放SoundPool占用的资源
/**
* 释放资源
*/
private void releaseSoundPool() {
if (mSoundPool != null) {
mSoundPool.autoPause();
mSoundPool.unload(mSoundId);
mSoundId = DEFAULT_INVALID_SOUND_ID;
mSoundPool.release();
mSoundPool = null;
}
}
7,注意事项
尽管 SoundPool 使用比较简单,但是还是有许多需要注意的地方:
1.并不是所有的音频格式都支持,可能有些格式是不支持的。
2.尽量使用占用内存较小的音频文件,时长也不要过长,因为 SoundPool 也只会播放很短的时间。
3.加载资源的数目也需要注意(256限制),过多可能会导致问题。
4.播放之前一定要确保资源以及加载完毕了,否则可能会出现异常情况。
5.设置流的优先级的问题,当同时播放的活动流的数目超过设置的maxStreams值的时候,会根据优先级来停止优先级较低的流,如果有多个具有相同低优先级的流,它将选择要停止的最旧流,并且该流不再有效;如果要播放的流的优先级最低,则会播放失败,返回的streamID为零ID。
6.可以设置播放的重复次数,当设置为-1 则表示会无限循环下去,此时若要停止,需要显示的调用stop() 方法。
7.可以设置播放速率,设置范围为0.5 ~ 2.0 , 正常为 1.0。
8,实例
如下示例,实现 SoundPool 的播放及控制功能,以及其他调节功能,如下图,具体代码见最后链接: