Android SoundPool 使用和封装

作者: CokeNello | 来源:发表于2017-06-28 12:05 被阅读2546次

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  : 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)增加了一个默认的铃声,当找不到系统的默认铃声时候,会默认加载一个我们提供的一个默认铃声
 * </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;
    }

相关文章

网友评论

    本文标题:Android SoundPool 使用和封装

    本文链接:https://www.haomeiwen.com/subject/bihucxtx.html