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;

/**
 * <pre>
 *     author: Chestnut
 *     blog  : //www.greatytc.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)增加了一个默认的铃声,当找不到系统的默认铃声时候,会默认加载一个我们提供的一个默认铃声
 * </pre>
 */
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<String,Integer> 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;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345