Android SDK 提供的3套音频播放的API之玩转SoundPool

前言

Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool,AudioTrack,本文重点说下SoundPool。

和MediaPlayer一样,SoundPool也可以用来播放音频文件。然而,与MediaPlayer不同的是,SoundPool更适合快速音效,而不是需要流媒体的较长的音频文件。

参见SoundPool的官方文档其有如下主要特性:

1、对比MediaPlayer低延迟播放;

SoundPool库使用MediaPlayer服务将音频解码为原始的16位PCM单声道或立体声流。这允许应用程序附带压缩流,而不必承受CPU负载和播放期间解压缩的延迟。

2、可以支持多个音频同时播放;

在构造SoundPool对象时,maxStreams参数设置单个SoundPool一次可以播放的最大流数量。SoundPool会追踪活动流的数量。如果超过了流的最大数量,SoundPool将自动停止先前播放的流,首先根据优先级,限制流的最大数量有助于限制CPU加载和减少音频混音的可能性

3、可以支持无限循环播放;

可以通过设置一个非零的循环值来循环声音。值为-1将导致声音永远循环。在这种情况下,应用程序必须通过调用stop()函数来停止声音。任何其他非零的值将导致声音重复指定的次数,例如,一个值为3将导致声音总共播放4次。

4、适用于短音频的播放(如游戏场景提示音等);

加载逻辑遍历声音列表,并调用相应的SoundPool.load()函数。这通常应该在过程的早期完成,以便在需要回放音频之前有时间将音频解压为原始PCM格式。

如果你用过SVGAPlayer,了解其内部播放动画的提示声音的实现就是使用的SoundPool,感兴趣的可以看看,这里不过多讲。

1、SoundPool的使用

1.1、准备音频资源

将准备的音频文放入assets文件夹下或者res下的raw文件夹下:

  • assets下可以再新建文件夹批量加载,而raw只能同级存放单个加载;

  • 在assets内部单个文件超过1m时可能存在bug,在raw资源目录下不会存在;

  • SoundPool的音频文件大小不能超过1M同时时间超过5-6秒可能会出错。

1.2、SoundPool的构造方法

SoundPool(int maxStreams, int streamType, int srcQuality)

  • 参数maxStreams指定支持多少个声音,SoundPool对象中允许同时存在的最多的流的数量,该值太大就会报错AudioFlinger could not create track, status: -12 ,就听不到声音,可根据需求设定

  • 参数streamType指定声音类型,可以在AudioManager中定义流类型为STREAM_VOICE_CALL, STREAM_SYSTEM, STREAM_RING,STREAM_MUSIC 和STREAM_ALARM四种类型。

  • 参数srcQuality指定声音品质(采样率变换质量),一般直接设置为0!

运用方式:

private static final int MAX_SOUNDS = 1;
private SoundPool soundPool;
//第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);

但是上面的构造方法在api 21 被废弃了。API 21 以后使用SoundPool.Builder创建SoundPool对象实例:

private static final int MAX_SOUNDS = 1;
private SoundPool soundPool;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    soundPool = new SoundPool.Builder().setMaxStreams(MAX_SOUNDS).build();
} else {
    //第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
    soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
}

1.3、加载音频资源

int load(Context context, int resId, int priority)

Load the sound from the specified APK resource.

从APK资源载入,resId:如音频文件 R.raw.xxx

int soundID = soundPool.load(appContext, R.raw.tip, 1);
int load(String path, int priority)

Load the sound from the specified path.从音频文件路径加载

int load(AssetFileDescriptor afd, int priority)

Load the sound from an asset file descriptor.

从asset 文件中加载

 AssetFileDescriptor fileDescriptor = assetManager.openFd("sounds/tip.mp3");
 int soundId = soundPool.load(fileDescriptor, 1);
int load(FileDescriptor fd, long offset, long length, int priority)

Load the sound from a FileDescriptor.

如存放在sd卡中的音频 FileDescriptor从文件中加载

1.4、播放音频资源

play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)

用法方式:

int streamId = soundPool.play(
    soundID,  //声音id
    1, //左声道:0.0f ~ 1.0f
    1, //右声道:0.0f ~ 1.0f
    1, //播放优先级:0表示最低优先级
    repeatTime, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
    1);//播放速度:1是正常,范围从0~2
关于播放控制的几个重要方法
// 通过流id暂停指定音频播放
final void pause(int streamID)

//恢复指定音频播放
final void resume(int streamID)

//停止指定音频播放
final void stop(int streamID)

//卸载指定音频
final boolean unload(int soundID)

//暂停所有音频的播放
final void autoPause()

//恢复所有暂停的音频播放
final void autoResum()

//设置指定id的音频循环播放次数
final void setLoop(int streamID, int loop)

//设置加载监听(因为加载是异步的,需要监听加载,完成后再播放)
void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)

//设置优先级(同时播放个数超过最大值时,优先级低的先被移除)
final void setPriority(int streamID, int priority)

//设置指定音频的播放速率,0.5~2.0(rate>1:加快播放,反之慢速播放)
final void setRate(int streamID, float rate)

//释放所有资源
final void release()

2、SoundPool的封装

SoundPool的封装
  • 为了使我们的代码更加清晰,我们将创建一个类,用于在Activity/Fragment之外加载和播放我们的声音。

  • 首先,我们创建一个名为SoundPlayer的类。我们需要一些方法来隐藏从外部类访问和加载声音文件的逻辑。

这是SoundPlayer的实现。您可以根据需要更改/添加一些代码,但这是基本要点。

完整代码如下:

import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;

import java.util.HashMap;

/**
 * 本地提示音播放器
 * SoundPool用于播放声音短,文件小的音频,延时短
 * @author: heiyulong
 * @date: 2021/4/16
 */
public class SoundPlayer {
    private static final String TAG = "SoundPlayer";
    private static final int MAX_SOUNDS = 3;
    private Context appContext;
    private SoundPool soundPool;
    private HashMap soundMap = new HashMap<>();

    public SoundPlayer(Context appContext) {
        // 版本兼容
        this.appContext = appContext;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            soundPool = new SoundPool.Builder().setMaxStreams(MAX_SOUNDS).build();
        } else {
            //第一个参数是可以支持的声音数量,第二个是声音类型,第三个是声音品质
            soundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
        }
    }

    /**
     * 播放音频
     * @param resId 音频文件 R.raw.xxx
     * @param repeatTime  循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
     */
    public void play(int resId, int repeatTime) {
        int soundID = soundPool.load(appContext, resId, 1);
        // 该方法防止sample not ready错误
        soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
            int streamId = soundPool.play(
                    soundID,  //声音id
                    1, //左声道:0.0f ~ 1.0f
                    1, //右声道:0.0f ~ 1.0f
                    1, //播放优先级:0表示最低优先级
                    repeatTime, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
                    1);//播放速度:1是正常,范围从0~2
            soundMap.put(resId,streamId);
        });
    }
    /**
     * 播放音频
     * @param resId 音频文件 R.raw.xxx
     */
    public void play(int resId) {
        int soundID = soundPool.load(appContext, resId, 1);
        // 该方法防止sample not ready错误
        soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
            int streamId = soundPool.play(
                    soundID,  //声音id
                    1, //左声道:0.0f ~ 1.0f
                    1, //右声道:0.0f ~ 1.0f
                    1, //播放优先级:0表示最低优先级
                    0, //循环模式:0表示循环一次,-1表示一直循环,其他表示数字+1表示当前数字对应的循环次
                    1);//播放速度:1是正常,范围从0~2
            soundMap.put(resId,streamId);
        });
    }

    /**
     * 暂停
     * @param resId
     */
    public void pause(int resId) {
        if (soundPool != null) {
            Integer mStreamID = soundMap.get(resId);
            if(mStreamID != null){
                soundPool.pause(mStreamID);
            }
        }
    }

    /**
     * 继续
     * @param resId
     */
    public void resume(int resId) {
        if (soundPool != null) {
            Integer mStreamID = soundMap.get(resId);
            if(mStreamID != null){
                soundPool.resume(mStreamID);
            }
        }
    }

    /**
     * 停止
     * @param resId
     */
    public void stop(int resId) {
        if (soundPool != null) {
            Integer mStreamID = soundMap.get(resId);
            if(mStreamID != null){
                soundPool.stop(mStreamID);
            }
        }
    }


    /**
     * 资源释放
     */
    public void release() {
        Log.d(TAG, "Cleaning resources..");
        if (soundPool != null) {
            soundPool.autoPause();
            soundPool.release();
        }
    }

}

这个类的核心组件包括:

  • 用于构建SoodPool的构造方法

  • SoundPool用于播放我们的声音文件。

  • SoundPool用于控制暂停&资源释放的方法

注意:
  1. 您还将注意到的常量:MAX_SOUNDS。它指定我们SoundPool允许同时播放的声音的最大数量。

  2. 一旦我们的类被实例化SoundPool就会被构造。通过plyaer(int resId)播放指定音频资源。

  3. 页面销毁时要记得释放资源release().

推荐阅读

heiyulong,公众号:Android进化之路Android SDK 提供的3套音频播放的API之玩转MediaPlayer

你可能感兴趣的:(java,android,安卓,spring,boot,ndk)