0.Thanks To
android获取系统铃声并播放
Android开发之SoundPool使用详解
Google API 文档
1.概述
Android中除了MediaPlayer播放音频之外还提供了SoundPool来播放音效,SoundPool使用音效池的概念来管理多个短促的音效,例如它可以开始就加载20个音效,以后在程序中按音效的ID进行播放。
SoundPool主要用于播放一些较短的声音片段,与MediaPlayer相比,SoundPool的优势在于CPU资源占用量低和反应延迟小。另外,SoundPool还支持自行设置声音的品质、音量、 播放比率等参数。
2.基本用法
方法解读:
SoundPool(int maxStreams, int streamType, int srcQuality)
:构造器,其初始化一个SoundPool,maxStreams:指定同时可以播放的音频流个数
streamType:指定声音的类型,简单来说,就是播放的时候,以哪种声音类型的音量播放。如:STREAM_ALARM ,是警报的声音类型。
srcQuality:the sample-rate converter quality. Currently has no effect. Use 0 for the default.音频的质量,现在是没有效果,设置为0代表默认。
int load(Context context, int resld, int priority)
:加载音频,其提供不同的加载方式,可以从res/raw中加载,或者是从StringPath中加载,并指定优先级。优先级越高当然越优先播放。加载完成后返回一个资源ID,代表这个音频在SoundPool池中的ID,之后的播放play需要指定这个ID才能播放。int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
:该方法的第一个参数指定播放哪个声音,就是上面load后返回的ID
leftVolume 、
rightVolume 指定左、右的音量:
priority 指定播放声音的优先级,数值越大,优先级越高;
loop 指定是否循环, 0 为不循环, -1 为循环;
rate 指定播放的比率,数值可从 0.5 到 2 , 1 为正常比率。
onLoadComplete(SoundPool soundPool, int sampleId, int status)
:加载完成的回调。虽然是加载一个很小的音频,但还是需要一点时间。所以,就有这个回调。sampleId就是音频的ID,用于标识哪个音频,status,加载完成的状态,0为成功。
简单示例:
//定义一个HashMap用于存放音频流的ID
HashMap musicId = new HashMap();
//初始化soundPool,设置可容纳12个音频流,音频流的质量为5,
SoundPool soundPool = new SoundPool(12, 0,5);
//通过load方法加载指定音频流,并将返回的音频ID放入musicId中
musicId.put(1, soundPool.load(this, R.raw.awooga, 1));
musicId.put(2, soundPool.load(this, R.raw.evillaugh, 1));
musicId.put(3, soundPool.load(this, R.raw.jackinthebox, 1));
//播放指定的音频流
soundPool.play(musicId.get(1),1,1, 0, 0, 1);
3.封装,部分方法说明
封装的目的是为了更方便地调用。
我们先声明一些东西:在SoundPool构造方法的streamType,指定声音的类型,简单来说,就是播放的时候,以哪种声音类型的音量播放。
我设计为,在初始化的时候就指定这个streamType,并使用Intdef去指定类型。
public final static int TYPE_MUSIC = AudioManager.STREAM_MUSIC;
public final static int TYPE_ALARM = AudioManager.STREAM_ALARM;
public final static int TYPE_RING = AudioManager.STREAM_RING;
@IntDef({TYPE_MUSIC, TYPE_ALARM, TYPE_RING})
@Retention(RetentionPolicy.SOURCE)
public @interface TYPE {}
- 提供三种构造器:
默认的构造器是加载一个音频,和使用MUSIC类型的声音。
public SoundPoolHelper() {
this(1,TYPE_MUSIC);
}
public SoundPoolHelper(int maxStream) {
this(maxStream,TYPE_ALARM);
}
public SoundPoolHelper(int maxStream,@TYPE int streamType) {
soundPool = new SoundPool(maxStream,streamType,1);
this.maxStream = maxStream;
ringtoneIds = new HashMap<>();
}
- 提供三个加载方法:
这里提供了一个默认的loadDefault,加载的是系统的,但如果加载不到,会加载一个本地的:R.raw.reminder。
自定义的一个load,需要传入一个String key作为SoundPool的一个音频映射。
后面的play方法需要传入key来指定播放哪一个音频。
/**
* 加载音频资源
* @param context 上下文
* @param resId 资源ID
* @return this
*/
public SoundPoolHelper load(Context context,@NonNull String ringtoneName, @RawRes int resId) {
if (maxStream==0)
return this;
maxStream--;
ringtoneIds.put(ringtoneName,soundPool.load(context,resId,1));
return this;
}
/**
* 加载默认的铃声
* @param context 上下文
* @return this
*/
public SoundPoolHelper loadDefault(Context context) {
Uri uri = getSystemDefaultRingtoneUri(context);
if (uri==null)
load(context,"default", R.raw.reminder);
else
load(context,"default",ConvertUtils.uri2Path(context,uri));
return this;
}
/**
* 加载铃声
* @param context 上下文
* @param ringtoneName 自定义铃声名称
* @param ringtonePath 铃声路径
* @return this
*/
public SoundPoolHelper load(Context context, @NonNull String ringtoneName, @NonNull String ringtonePath) {
if (maxStream==0)
return this;
maxStream--;
ringtoneIds.put(ringtoneName,soundPool.load(ringtonePath,1));
return this;
}
- 上面的加载默认的音频,究竟是加载哪一个音频?可以通过以下方法提前指定:
默认的话,是加载:RING_TYPE_ALARM
public final static int RING_TYPE_MUSIC = RingtoneManager.TYPE_ALARM;
public final static int RING_TYPE_ALARM = RingtoneManager.TYPE_NOTIFICATION;
public final static int RING_TYPE_RING = RingtoneManager.TYPE_RINGTONE;
@IntDef({RING_TYPE_MUSIC, RING_TYPE_ALARM, RING_TYPE_RING})
@Retention(RetentionPolicy.SOURCE)
public @interface RING_TYPE {}
/**
* 设置RingtoneType,这只是关系到加载哪一个默认音频
* 需要在load之前调用
* @param ringtoneType ringtoneType
* @return this
*/
public SoundPoolHelper setRingtoneType(@RING_TYPE int ringtoneType) {
NOW_RINGTONE_TYPE = ringtoneType;
return this;
}
4.SoundPoolHelper源码
package com.chestnut.Common.Helper;
import android.content.Context;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.media.SoundPool;
import android.net.Uri;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.RawRes;
import com.chesnut.Common.R;
import com.chestnut.Common.utils.ConvertUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
/**
*
* author: Chestnut
* blog : http://www.jianshu.com/u/a0206b5f4526
* time : 2017/6/22 17:24
* desc : 封装了SoundPool
* thanks To: http://flycatdeng.iteye.com/blog/2120043
* http://www.2cto.com/kf/201408/325318.html
* https://developer.android.com/reference/android/media/SoundPool.html
* dependent on:
* update log:
* 1. 2017年6月28日10:39:46
* 1)修复了当play指定的RingtoneName为空的时候,触发的一个bug
* 2)增加了一个默认的铃声,当找不到系统的默认铃声时候,会默认加载一个我们提供的一个默认铃声
*
*/
public class SoundPoolHelper {
/*常量*/
public final static int TYPE_MUSIC = AudioManager.STREAM_MUSIC;
public final static int TYPE_ALARM = AudioManager.STREAM_ALARM;
public final static int TYPE_RING = AudioManager.STREAM_RING;
@IntDef({TYPE_MUSIC, TYPE_ALARM, TYPE_RING})
@Retention(RetentionPolicy.SOURCE)
public @interface TYPE {}
public final static int RING_TYPE_MUSIC = RingtoneManager.TYPE_ALARM;
public final static int RING_TYPE_ALARM = RingtoneManager.TYPE_NOTIFICATION;
public final static int RING_TYPE_RING = RingtoneManager.TYPE_RINGTONE;
@IntDef({RING_TYPE_MUSIC, RING_TYPE_ALARM, RING_TYPE_RING})
@Retention(RetentionPolicy.SOURCE)
public @interface RING_TYPE {}
/*变量*/
private SoundPool soundPool;
private int NOW_RINGTONE_TYPE = RingtoneManager.TYPE_NOTIFICATION;
private int maxStream;
private Map ringtoneIds;
/*方法*/
public SoundPoolHelper() {
this(1,TYPE_MUSIC);
}
public SoundPoolHelper(int maxStream) {
this(maxStream,TYPE_ALARM);
}
public SoundPoolHelper(int maxStream,@TYPE int streamType) {
soundPool = new SoundPool(maxStream,streamType,1);
this.maxStream = maxStream;
ringtoneIds = new HashMap<>();
}
/**
* 设置RingtoneType,这只是关系到加载哪一个默认音频
* 需要在load之前调用
* @param ringtoneType ringtoneType
* @return this
*/
public SoundPoolHelper setRingtoneType(@RING_TYPE int ringtoneType) {
NOW_RINGTONE_TYPE = ringtoneType;
return this;
}
/**
* 加载音频资源
* @param context 上下文
* @param resId 资源ID
* @return this
*/
public SoundPoolHelper load(Context context,@NonNull String ringtoneName, @RawRes int resId) {
if (maxStream==0)
return this;
maxStream--;
ringtoneIds.put(ringtoneName,soundPool.load(context,resId,1));
return this;
}
/**
* 加载默认的铃声
* @param context 上下文
* @return this
*/
public SoundPoolHelper loadDefault(Context context) {
Uri uri = getSystemDefaultRingtoneUri(context);
if (uri==null)
load(context,"default", R.raw.reminder);
else
load(context,"default",ConvertUtils.uri2Path(context,uri));
return this;
}
/**
* 加载铃声
* @param context 上下文
* @param ringtoneName 自定义铃声名称
* @param ringtonePath 铃声路径
* @return this
*/
public SoundPoolHelper load(Context context, @NonNull String ringtoneName, @NonNull String ringtonePath) {
if (maxStream==0)
return this;
maxStream--;
ringtoneIds.put(ringtoneName,soundPool.load(ringtonePath,1));
return this;
}
/**
* int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) :
* 1)该方法的第一个参数指定播放哪个声音;
* 2) leftVolume 、
* 3) rightVolume 指定左、右的音量:
* 4) priority 指定播放声音的优先级,数值越大,优先级越高;
* 5) loop 指定是否循环, 0 为不循环, -1 为循环;
* 6) rate 指定播放的比率,数值可从 0.5 到 2 , 1 为正常比率。
*/
public void play(@NonNull String ringtoneName, boolean isLoop) {
if (ringtoneIds.containsKey(ringtoneName)) {
soundPool.play(ringtoneIds.get(ringtoneName),1,1,1,isLoop?-1:0,1);
}
}
public void playDefault() {
play("default",false);
}
/**
* 释放资源
*/
public void release() {
if (soundPool!=null)
soundPool.release();
}
/**
* 获取系统默认铃声的Uri
* @param context 上下文
* @return uri
*/
private Uri getSystemDefaultRingtoneUri(Context context) {
try {
return RingtoneManager.getActualDefaultRingtoneUri(context, NOW_RINGTONE_TYPE);
} catch (Exception e) {
return null;
}
}
}
5.SoundPoolHelper调用示例
用完记得:release。
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
private boolean OpenLog = true;
private SoundPoolHelper soundPoolHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化,指定是最大4个Stream流,使用默认的Stream:TYPE_MUSIC
soundPoolHelper = new SoundPoolHelper(4,SoundPoolHelper.TYPE_MUSIC)
.setRingtoneType(SoundPoolHelper.RING_TYPE_MUSIC)
//加载默认音频,因为上面指定了,所以其默认是:RING_TYPE_MUSIC
//happy1,happy2
.loadDefault(MainActivity.this)
.load(MainActivity.this,"happy1",R.raw.happy1)
.load(MainActivity.this,"happy2",R.raw.happy2)
.load(MainActivity.this,"reminder",R.raw.reminder);
findViewById(R.id.button0).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
soundPoolHelper.playDefault();
}
});
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
soundPoolHelper.play("qq",false);
}
});
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
soundPoolHelper.play("happy2",false);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
soundPoolHelper.release();
}
}
PS:有个方法是另外一个工具类的。。。其是把uri转成string
/**
* 把 Uri 转变 为 真实的 String 路径
* @param context 上下文
* @param uri URI
* @return 转换结果
*/
public static String uri2Path(Context context, Uri uri) {
if ( null == uri ) return null;
String scheme = uri.getScheme();
String data = null;
if ( scheme == null )
data = uri.getPath();
else if ( ContentResolver.SCHEME_FILE.equals( scheme ) ) {
data = uri.getPath();
} else if ( ContentResolver.SCHEME_CONTENT.equals( scheme ) ) {
Cursor cursor = context.getContentResolver().query( uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null );
if ( null != cursor ) {
if ( cursor.moveToFirst() ) {
int index = cursor.getColumnIndex( MediaStore.Images.ImageColumns.DATA );
if ( index > -1 ) {
data = cursor.getString( index );
}
}
cursor.close();
}
}
return data;
}