Android SoundPool 使用和封装

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;
    }

你可能感兴趣的:(Android SoundPool 使用和封装)