目录

1,概述

2,SoundPool 的创建

3,资源加载与播放

4,播放控制

5,播放音量调节

6,资源释放

7,注意事项

8,实例


1,概述

最近工作接触到Android 中的 SoundPool 类,使用后发觉很是有意思,记录一下以备后查。

Android 开发中,难免会碰到音视频相关的处理。SoundPool 是 Android 提供的一个API类,用来播放简短的音频,使用简单但功能相对强大。只需花很少的气力,就可以完成音频的播放、暂停、恢复及停止等操作。从名字上也可以看出,它是一个“pool”,我们可以加载多个音频资源到内存,进行管理与播放,比如控制同时播放流的最大数目。加载资源到内存是需要花费少许时间的,因此我们需要监听加载资源完毕的事件,在加载完毕后才能进行播放,以免发生不可预期的错误。

除了以上介绍外,SoundPool 还有诸多其他功能,诸如调节左右声道的音量值、调整播放的语速、设置播放的优先级以及播放的次数等等,说起来还是挺有意思的。接下来述说 SoundPool 的具体使用。

2,SoundPool 的创建

使用 SoundPool 首先需创建它。SoundPool 的创建方式在不同版本中会有所不同,为了更好的兼容性,应该对api版本进行判断,再对应的进行创建。在Android 5.0 以前 ,直接使用它的构造方法即可,而在这之后,则需要使用Builder模式来创建。

创建完 SoundPool 后,通过setOnLoadCompleteListener设置监听,用来监听资源加载完毕的事件发生。这主要是为了播放做准备.通过名字可猜测到,当音频资源加载完成后,会回调设置的监听的onLoadComplete方法,在这个方法里,可以进行播放音频。

/**
         * 创建SoundPool ,注意 api 等级
         */
        private void createSoundPoolIfNeeded() {
            if (mSoundPool == null) {
                // 5.0 及 之后
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                    AudioAttributes audioAttributes = null;
                    audioAttributes = new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_MEDIA)
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build();
    
                    mSoundPool = new SoundPool.Builder()
                            .setMaxStreams(16)
                            .setAudioAttributes(audioAttributes)
                            .build();
                } else { // 5.0 以前
                    mSoundPool = new SoundPool(16, AudioManager.STREAM_MUSIC, 0);  // 创建SoundPool
                }
    
                mSoundPool.setOnLoadCompleteListener(this);  // 设置加载完成监听
            }
        }

Android 5.0 之前,直接通过SoundPool的构造方法来创建,有三个参数:

1.maxStreams 同时播放流的最大数量,当播放的流的数目大于此值,则会选择性停止优先级较低的流
2.streamType 流类型,比如 STREAM_MUSIC
3.srcQuality 采样率转换器质量,目前没有什么作用,默认填充0

如下是方法原型,注释做了详细的说明:

/**
     * Constructor. Constructs a SoundPool object with the following
     * characteristics:
     *
     * @param maxStreams the maximum number of simultaneous streams for this
     *                   SoundPool object
     * @param streamType the audio stream type as described in AudioManager
     *                   For example, game applications will normally use
     *                   {@link AudioManager#STREAM_MUSIC}.
     * @param srcQuality the sample-rate converter quality. Currently has no
     *                   effect. Use 0 for the default.
     * @return a SoundPool object, or null if creation failed
     * @deprecated use {@link SoundPool.Builder} instead to create and configure a
     *     SoundPool instance
     */
    public SoundPool(int maxStreams, int streamType, int srcQuality) {...}

5.0 之后,使用Builder模式进行构造。Builder可以设置多个参数。

    1. setMaxStreams和之前maxStreams参数相同。
    2. setAudioAttributes用来设置audio 属性,此值要么不设,要么设置不为null的值,否则会导致异常产生。

如下代码摘录自 SoundPool.Builder 类,可以发现给 setAudioAttributes 传 null 是会抛出异常的,如果不设置的话,在build方法中则会默认创建一个,usage设置为USAGE_MEDIA

public Builder setAudioAttributes(AudioAttributes attributes)
                throws IllegalArgumentException {
            if (attributes == null) {
                throw new IllegalArgumentException("Invalid null AudioAttributes");
            }
            mAudioAttributes = attributes;
            return this;
        }

        public SoundPool build() {
            if (mAudioAttributes == null) {
                mAudioAttributes = new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA).build();
            }
            return new SoundPool(mMaxStreams, mAudioAttributes);
        }

AudioAttributes 也是使用Builder模式来构造的,setUsage用来设置用途(比如是游戏还是媒体),setContentType用来设置内容类型(比如是视频还是音乐)

通过setUsage方法可知它支持的usage类型,还是挺多的:

/**
     * Sets the attribute describing what is the intended use of the the audio signal,
     * such as alarm or ringtone. ...
     */
    public Builder setUsage(@AttributeUsage int usage) {
        switch (usage) {
            case USAGE_UNKNOWN:
            case USAGE_MEDIA:
            case USAGE_VOICE_COMMUNICATION:
            case USAGE_VOICE_COMMUNICATION_SIGNALLING:
            case USAGE_ALARM:
            case USAGE_NOTIFICATION:
            case USAGE_NOTIFICATION_RINGTONE:
            case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
            case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
            case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
            case USAGE_NOTIFICATION_EVENT:
            case USAGE_ASSISTANCE_ACCESSIBILITY:
            case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
            case USAGE_ASSISTANCE_SONIFICATION:
            case USAGE_GAME:
            case USAGE_VIRTUAL_SOURCE:
            case USAGE_ASSISTANT:
                 mUsage = usage;
                 break;
            default:
                 mUsage = USAGE_UNKNOWN;
        }
        return this;
    }

通过setContentType方法可知支持的contentType:

/**
     * Sets the attribute describing the content type of the audio signal, such as speech,or music.
     */
    public Builder setContentType(@AttributeContentType int contentType) {
        switch (contentType) {
            case CONTENT_TYPE_UNKNOWN:
            case CONTENT_TYPE_MOVIE:
            case CONTENT_TYPE_MUSIC:
            case CONTENT_TYPE_SONIFICATION:
            case CONTENT_TYPE_SPEECH:
                 mContentType = contentType;
                 break;
            default:
                 mUsage = CONTENT_TYPE_UNKNOWN;
        }
        return this;
    }

3,资源加载与播放

播放不是一蹴而就的,如前所述,需要在资源加载完毕后,才能调用play方法进行播放。因此,直接调用play方法是不可取的,而且可能造成难以预料的错误。

因此我把这个方法取为 playMayWait, 当资源没加载时,首先需要调用 SoundPool 的load 方法进行加载,并返回一个mSoundId,它代表一个已加载的资源的id,可以用来播放音效。

加载音频资源异步执行,此过程是需要时间的,当加载成功后,会调用 onLoadComplete 方法,在之前代码已经设置过相关的监听 。当再次进行播放,我们直接调用onLoadComplete方法,复用已加载的资源(在没有释放之前)

/**
     * 播放:如果资源还没有加载,则可能会有一小段等待时间
     */
    private void playMayWait() {
        createSoundPoolIfNeeded();

        // mSoundId is invalid ,load from res raw for once before mSoundPool is released
        if (mSoundId == DEFAULT_INVALID_SOUND_ID) { 
            mSoundId = mSoundPool.load(getApplicationContext(), R.raw.autotest, 1); // 加载音频资源
        } else {
            // reuse the loaded res
            if (mStreamID == DEFAULT_INVALID_STREAM_ID)
                onLoadComplete(mSoundPool, 0, 0);  // manually call this method when there is a valid mSoundId
        }
    }

此处的load方法是从res/raw 下加载的,需要注意的是,通过改方式加载的资源会忽略后缀名,因此不同类型的资源不能重名。实际上, 提供了多个load 方法,以方便不同的需求:

// Load the sound from the specified path.  根据路径加载
public int load(String path, int priority)

// Load the sound from the specified APK resource  使用应用的资源
public int load(Context context, int resId, int priority)

// Load the sound from an asset file descriptor   使用 assert  文件描述符
public int load(AssetFileDescriptor afd, int priority) {

// Load the sound from a FileDescriptor.   使用文件描述符
public int load(FileDescriptor fd, long offset, long length, int priority)

通过 unload 方法来卸载之前加载的资源 ,参数为已加载过的资源 id

/**
     * Unload a sound from a sound ID.
     *
     * Unloads the sound specified by the soundID. This is the value
     * returned by the load() function. Returns true if the sound is
     * successfully unloaded, false if the sound was already unloaded.
     *
     * @param soundID a soundID returned by the load() function
     * @return true if just unloaded, false if previously unloaded
     */
    public native final boolean unload(int soundID);

onLoadComplete方法是用来播放的合理位置,它的调用预示着资源已经就绪,可以进行播放了,调用 SoundPool 的 play 方法即可进行播放.

@Override
    public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
        if (mSoundPool != null) {
            if (mStreamID == DEFAULT_INVALID_STREAM_ID)
                mStreamID = mSoundPool.play(mSoundId, mCruLeftVolume, mCurRightVolume, 16, -1, 1.0f);
        }
    }

SoundPool 的 play 方法参数较多,不过都比较简单. 当活跃的stream数量大于maxStreams,调用该方法可导致另一个播放声音被停止:
这个方法依据之前通过load方法加载得到的sound ID , 来播放一个指定的音频文件的声音.
通过注释就比较好的理解参数的内容:

  1.soundID load方法返回的值,指向某个已加载的音频资源
  2.leftVolume\rightVolume 用来这种左右声道的值.范围 0.0f ~ 1.0f
  3.priority 流的优先级
  4.loop 循环播放的次数, -1 表示无限循环
  5.rate 播放的速率 , 2 表示2倍速度

返回值streamID , 返回0则播放失败,否则成功。通过改id可以进一步进行控制。比如 pause\resume

/**
     * Play a sound from a sound ID.
     *
     * Play the sound specified by the soundID. This is the value
     * returned by the load() function. Returns a non-zero streamID
     * if successful, zero if it fails. The streamID can be used to
     * further control playback. Note that calling play() may cause
     * another sound to stop playing if the maximum number of active
     * streams is exceeded. A loop value of -1 means loop forever,
     * a value of 0 means don't loop, other values indicate the
     * number of repeats, e.g. a value of 1 plays the audio twice.
     * The playback rate allows the application to vary the playback
     * rate (pitch) of the sound. A value of 1.0 means play back at
     * the original frequency. A value of 2.0 means play back twice
     * as fast, and a value of 0.5 means playback at half speed.
     *
     * @param soundID a soundID returned by the load() function
     * @param leftVolume left volume value (range = 0.0 to 1.0)
     * @param rightVolume right volume value (range = 0.0 to 1.0)
     * @param priority stream priority (0 = lowest priority)
     * @param loop loop mode (0 = no loop, -1 = loop forever)
     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
     * @return non-zero streamID if successful, zero if failed
     */
    public final int play(int soundID, float leftVolume, float rightVolume,
            int priority, int loop, float rate) {
        baseStart();
        return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
    }

4,播放控制

streamID是对流的特定实例的引用,通过 SoundPool 的 pause\resume\stop 方法, 就可以对播放进行控制。

  1. pause方法用来暂停指定mStreamID的流 (暂停播放)
  2. resume方法用来恢复指定mStreamID的流 (恢复播放)
  3. stop方法用来停止指定mStreamID的流 (停止播放)

public void pause(View view) {
        if (mSoundPool != null) {
            mSoundPool.pause(mStreamID);
        }
    }

    public void resume(View view) {
        if (mSoundPool != null) {
            mSoundPool.resume(mStreamID);
        }
    }

    public void stop(View view) {
        if (mSoundPool != null) {
            mSoundPool.stop(mStreamID);
            mStreamID = DEFAULT_INVALID_STREAM_ID;
        }
    }

5,播放音量调节

通过 SoundPool 的 setVolume 方法就可以设置指定 mStreamID 的流的左右声道的音量值.
如下,通过SeekBar 进行动态调节

private OnSeekBarChangeListenerAdapter seekBarListener = new OnSeekBarChangeListenerAdapter() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            super.onProgressChanged(seekBar, progress, fromUser);
            if(mSoundPool == null) return;
            float volume = progress * 1.0f / seekBar.getMax();
            switch (seekBar.getId()) {
                case R.id.id_sk_left_vloume:  // 设置左 volume
                    mCruLeftVolume = volume;
                    mSoundPool.setVolume(mStreamID, mCruLeftVolume, mCurRightVolume);
                    break;
                case R.id.id_sk_right_vloume: // 设置又 volume
                    mCurRightVolume = volume;
                    mSoundPool.setVolume(mStreamID, mCruLeftVolume, mCurRightVolume);
                    break;
            }
        }
    };

6,资源释放

通过unload 方法来卸载之前load的资源 , 并通过 release 方法释放SoundPool占用的资源

/**
     * 释放资源
     */
    private void releaseSoundPool() {
        if (mSoundPool != null) {
            mSoundPool.autoPause();
            mSoundPool.unload(mSoundId);
            mSoundId = DEFAULT_INVALID_SOUND_ID;
            mSoundPool.release();
            mSoundPool = null;
        }
    }

7,注意事项

尽管 SoundPool 使用比较简单,但是还是有许多需要注意的地方:

  1.并不是所有的音频格式都支持,可能有些格式是不支持的。
  2.尽量使用占用内存较小的音频文件,时长也不要过长,因为 SoundPool 也只会播放很短的时间。
  3.加载资源的数目也需要注意(256限制),过多可能会导致问题。
  4.播放之前一定要确保资源以及加载完毕了,否则可能会出现异常情况。
  5.设置流的优先级的问题,当同时播放的活动流的数目超过设置的maxStreams值的时候,会根据优先级来停止优先级较低的流,如果有多个具有相同低优先级的流,它将选择要停止的最旧流,并且该流不再有效;如果要播放的流的优先级最低,则会播放失败,返回的streamID为零ID。
  6.可以设置播放的重复次数,当设置为-1 则表示会无限循环下去,此时若要停止,需要显示的调用stop() 方法。
  7.可以设置播放速率,设置范围为0.5 ~ 2.0 , 正常为 1.0。

8,实例

如下示例,实现 SoundPool 的播放及控制功能,以及其他调节功能,如下图,具体代码见最后链接:

ANDROID soundpool的使用 安卓soundpool_资源加载