Android PigMediaStudio 音频处理封装依赖库创建过程记录(二)(SoundPool封装)(学习笔记,不更新)

开发PigMediaStudio依赖库过程记录(二)

在最近的工作中,接触了许多关于MediaPlayer、SoundPool、TextToSpeach的使用,希望能够封装一个MediaPlayer、TextToSpeach功能兼具的library。由此篇博客开始,记录这个依赖库的创建过程。
为什么取这个名字?因为我觉得猪猪憨憨很可爱。

说明: 这算是个学习过程记录,已经写好的代码会随进度而有一定的改变,尤其是方法和变量的命名,在完成功能过程中不会很在意,等完成后会选择恰当方式重新命名,如果需要直接参考,请直接查看当前进度的最后结果
github地址
https://github.com/fytuuu/PigMediaStudio

文章链接:


我们接下来进行SoundPool封装

首先对SoundPool有一些感性的认识:

  1. SoundPool轻量,更适合游戏等 短促、密集 的音效播放
  2. 可以给App添加提示音,比如最典型的酷狗音乐打开时候播放“哈喽,酷狗”
  3. SoundPool可以从apk中导入资源,也可以导入文件资源(前提拿到读写权限)
  4. SoundPool利用MediaPlayer服务 为音频解码为一个原始16位PCM流,这个特性使得应用程序可以进行流压缩,减少播放音频时解压带来的CPU负载和延迟
  5. SoundPool顾名思义,使用音效池管理多个音频流,如果超过流的最大数目(这个数目由开发者定),SoundPool会基于优先级自动停止先前正在播放的流
  6. SoundPool还支持设置语音的品质、音量、播放比率等参数
  7. SoundPool的 load()方法 每个资源最多只有1M空间,所以不适合播放背景音乐等长音效、高品质音效
  8. SoundPool无法监听播放进度,只支持(全部)暂停、(全部)继续播放、播放

首先对SoundPool的常用方法有个了解:

1.构造方法

在 API 21 (Android 5.0) 之前可以使用下面这个构造方法

SoundPool(int maxStreams, int streamType, int srcQuality)
  • maxStreams
    指定支持多少个声音,SoundPool 对象中允许同时存在的最大流的数量

  • streamType
    指定声音类型,在 AudioManager 定义了以下几种流类型
    STREAM_VOICE_CALL
    STREAM_SYSTEM
    STREAM_RING
    STREAM_MUSIC
    STREAM_ALARM

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

但在 API 21(Android 5.0) 及以上版本,这个构造方法被废弃了,取而代之的是使用 SoundPool.Builder() 方法

SoundPool.Builder spb = new SoundPool.Builder();
spb.setMaxStreams(10);
spb.setAudioAttributes(null);    //转换音频格式
SoundPool sp = spb.build();      //创建SoundPool对象

因此在使用过程中要对系统的版本进行判断,以选择合适的方法

// 版本大于等于 21 (Anndroid 5.0)
SoundPool sp = null;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
    SoundPool.Builder spb = new SoundPool.Builder();
    spb.setMaxStreams(10);
    spb.setAudioAttributes(null);    //转换音频格式
    sp = spb.build();      //创建SoundPool对象 
} else {
    sp = SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
}

2.load()方法 – 加载音频资源/本地音频文件

load() 重载方法,每种方法都会返回一个声音的 ID (int类型,从1开始)

load(Context context, int resId, int priority)
load(String path, int priority)
load(FileDescriptor fd, long offset, long length, int priority)
load(AssetFileDescriptor afd, int priority)
  • context 上下文
  • resId 资源id
  • priority 没什么用的一个参数,建议设置为1,保持和未来的兼容性
  • path 文件路径
  • FileDescriptor 文件描述符
  • AssetFileDescriptor 从 asset 目录读取某个资源文件,用法
  • AssetFileDescriptor descriptor = assetManager.openFd(“kugou.mp3”)

注意一下load方法的官方提示

/**
     * Load the sound from the specified APK resource.
     *
     * Note that the extension is dropped. For example, if you want to load
     * a sound from the raw resource file "explosion.mp3", you would specify
     * "R.raw.explosion" as the resource ID. Note that this means you cannot
     * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
     * directory.
     *
     * @param context the application context
     * @param resId the resource ID
     * @param priority the priority of the sound. Currently has no effect. Use
     *                 a value of 1 for future compatibility.
     * @return a sound ID. This value can be used to play or unload the sound.
     */
     public int load(Context context, int resId, int priority) {...}

3.play() --播放音频,设置属性

play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
  • soundID load() 返回的声音 ID 号 (从1开始)
  • leftVolume 左声道音量设置
  • rightVolume 右声道音量设置
  • priority 指定播放声音的优先级,数值越高,优先级越大。
  • loop 指定是否循环
    -1 表示无限循环
    0 表示不循环
    其它值表示要重复播放的次数
  • rate 指定播放速率,播放速率的取值范围是 0.5 至 2.0
    1.0 的播放率可以使声音按照其原始频率
    2.0 的播放速率,可以使声音按照其原始频率的两倍播放
    0.5的播放率,则播放速率是原始频率的一半

4.release() – 用于释放资源

release(int soundID)
release() 方法用于释放所有 SoundPool 对象占据的内存和资源

核心是load方法,play()之前必须load好,load完成会返回一个ID(int类型,从1开始), 这个ID对应指向这个资源文件,play()的时候需要我们将这个资源文件的ID作为参数。
所以我们完成load时候,将资源/文件的某种标志,如文件名或者文件内容作为key,将load返回的ID作为value,往后通过key,把value取出来交给play()会比较方便使用。

那我们开始码起

1. 创建PigSoundPlayer类,用于控制SoundPool

首先我们准备好用于储存资源、文件的HashMap(暂时不考虑key的唯一性问题)。我们给PigSoundPlayer初始化的同时也给SoundPool的属性进行设置。如果不填入,那么默认值为maxStream为1,quality为5,attribute为null。

public class PigSoundPlayer {
    private final static String TAG = "PigSoundPlayer";
    //用于存储资源文件
    private static HashMap<String, Integer> resourceMap = new HashMap<>();
    //音效池
    private static SoundPool soundPool;
    //单例
    private static PigSoundPlayer pigSoundPlayer;
    //上下文管理者
    private static ContextManager contextManager;
    //构造器私有
    private PigSoundPlayer(){}
    //初始化
    public static void initSoundPlayer(int maxStreams, AudioAttributes attributes, int quality){
		
        if (pigSoundPlayer == null) pigSoundPlayer = new PigSoundPlayer();
		//控制maxStream的错误值
        if (maxStreams<=0) maxStreams = 1;
		//根据不同版本进行SoundPool的初始化
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            SoundPool.Builder spb  = new SoundPool.Builder();
            spb.setMaxStreams(maxStreams);
            if (attributes!=null )spb.setAudioAttributes(attributes);
            soundPool = spb.build();
        }else{
            soundPool = new SoundPool(maxStreams, AudioManager.STREAM_SYSTEM,quality);
        }
    }

    public static void initSoundPlayer(int maxStreams, int quality){
        initSoundPlayer(maxStreams,null,quality);
    }

    public static void initSoundPlayer(int maxStreams, AudioAttributes attributes){
        initSoundPlayer(maxStreams,attributes,5);
    }

    public static void initSoundPlayer(int maxStreams){
       initSoundPlayer(maxStreams,null);
    }

    public static void initSoundPlayer(){
        initSoundPlayer(1);
    }

2. 我们再添加一个内部类Loader进行load操作

Loader每个方法中的第一个参数tag,是resourceMap的第一个参数,方便通过字符串标志来从map中找到对应的资源id,当然也可以直接通过返回值获得id

public static class Loader{
        private Loader(){}

        public int load(String tag,int resId,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(contextManager.getApplicationContext(),
                        resId,
                        priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,String path,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(path,priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,FileDescriptor fd,long offset,long length,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(fd,offset,length,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

        public int load(String tag, AssetFileDescriptor afd,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(afd,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

    }

3. 接下来我们把Loader的方法和播放的方法加进去

其中play()方法左右声道声音大小一般都会是1.0f,所以给了个简单的方法,只需要给id和loop或者只给id、loop和rate即可

public class PigSoundPlayer{
	//...前文代码省略
	//预存(预加载)者
    private static Loader loader;

	//获取对应处理者
    public static Loader getLoader(Context context){
        if(contextManager == null){
            contextManager = new ContextManager(context);
        }
        if (loader == null){
            loader = new Loader();
        }
        return loader;
    }

     public static int play(int soundId,int loop){
        return play(soundId,1.0f,1.0f,1,loop,1.0f);
    }
    
    public static int play(int soundId,int loop,float rate){
        return play(soundId,1.0f,1.0f,1,loop,rate);
    }

    public static int play(int soundId,float leftVolume,float rightVolume,int priority,int loop,float rate){
        if (pigSoundPlayer == null){
            return 0;
        }
        return soundPool.play(soundId, leftVolume, rightVolume, priority, loop, rate);
    }
    
    public static int play(String tag,int loop){
        return play(tag,1.0f,1.0f,1,loop,1.0f);
    }

    public static int play(String tag,int loop,float rate){
        return play(tag,1.0f,1.0f,1,loop,rate);
    }

    public static int play(String tag,float leftVolumn,float rightVolume,int priority,int loop,float rate){
        if (pigSoundPlayer == null){
            return 0;
        }
        int resid = 0;
        if(resourceMap.get(tag)!=null){
            resid = resourceMap.get(tag);
            return soundPool.play(resid,leftVolumn,rightVolume,priority,loop,rate);
        }else{
            return 0;
        }
    }

    public static class Loader{
        private Loader(){}

        public int load(String tag,int resId,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(contextManager.getApplicationContext(),
                        resId,
                        priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,String path,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(path,priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,FileDescriptor fd,long offset,long length,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(fd,offset,length,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

        public int load(String tag, AssetFileDescriptor afd,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(afd,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

    }
}

其中的ContextManager比较简单,这里直接贴代码

public class ContextManager {
    private Context context;
    private PigSoundPlayer pigSoundPlayer;

    public ContextManager(Context context){
        this.context = context.getApplicationContext();
    }

    public Context getApplicationContext(){
        return context;
    }

    public void setContext(Context context){
        this.context  = context;
    }
}

到此为止,我们的PigSoundPlayer就可以使用了
首先在Application的onCreate中,或者在播放或者预存之前,调用PigSoundPlayer.initSoundPlayer()方法进行初始化
往后,如果需要加载资源,那么就调用PigSoundPlayer.getLoader(getApplicationContext()).load(file.getAbsolutePath,1)
如果需要播放,那么就调用
int stremId = PigSoundPlayer.play(id,0);

4. 我们再考虑一下暂停和继续播放问题

我们看一下SoundPool中的暂停和继续方法
首先是暂停的方法,用于暂停streamID对应的playback stream,其中,streamIDplay()方法的返回值,每个play()方法返回的streamID对应的就是当前播放音频的stream

 /**
     * Pause a playback stream.
     *
     * Pause the stream specified by the streamID. This is the
     * value returned by the play() function. If the stream is
     * playing, it will be paused. If the stream is not playing
     * (e.g. is stopped or was previously paused), calling this
     * function will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void pause(int streamID);

继续播放的方法,传入的参数streamID同样是play()方法的返回值,如果这个streamID的stream之前被暂停了,那么将会继续播放,如果没有被暂停,那么调用这个方法没有效果。

	 /**
     * Resume a playback stream.
     *
     * Resume the stream specified by the streamID. This
     * is the value returned by the play() function. If the stream
     * is paused, this will resume playback. If the stream was not
     * previously paused, calling this function will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void resume(int streamID);

全部暂停,auto自动的意思顾名思义就是给所有被自动暂停的stream打上flag,当调用autoResume()的时候,所有被autoPause()的stream都会继续播放。

	/**
     * Pause all active streams.
     *
     * Pause all streams that are currently playing. This function
     * iterates through all the active streams and pauses any that
     * are playing. It also sets a flag so that any streams that
     * are playing can be resumed by calling autoResume().
     */
    public native final void autoPause();

这里我没有试过,我估计是只会继续播放那些被 autoPause() 暂停的stream

	 /**
     * Resume all previously active streams.
     *
     * Automatically resumes all streams that were paused in previous
     * calls to autoPause().
     */
    public native final void autoResume();

停止,关闭资源

	/**
     * Stop a playback stream.
     *
     * Stop the stream specified by the streamID. This
     * is the value returned by the play() function. If the stream
     * is playing, it will be stopped. It also releases any native
     * resources associated with this stream. If the stream is not
     * playing, it will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void stop(int streamID);

当然还有一些setVolume setPriority setLoop setRate等方法,不一一贴出
这里注意一下,rate范围是 0f 到 2.0f
我们把这些方法封装到我们的PigSoundPlayer

	//...
	
    //暂停
    public static void pause(int streamID){
        soundPool.pause(streamID);
    }
    //继续播放
    public static void resume(int streamId){
        soundPool.resume(streamId);
    }
    //全部暂停播放
    public static void autoPause(){
        soundPool.autoPause();
    }
    //全部继续播放
    public static void autoResume(){
        soundPool.autoResume();
    }
    //停止
    public static void stop(int streamID){
        soundPool.stop(streamID);
    }
    //设置音量
    public static void setVolume(int streamID, int leftVolume, int rightVolume){
        soundPool.setVolume(streamID,leftVolume,rightVolume);
    }
    //设置Priority
    public static void setPriority(int streamID, int priority){
        soundPool.setPriority(streamID,priority);
    }
    //设置Rate
    public static void setRate(int streamID, float rate){
        soundPool.setRate(streamID,rate);
    }
    //设置loop
    public static void setLoop(int streamID,int loop){
        soundPool.setLoop(streamID,loop);
    }
    
	//...
原创文章 23 获赞 28 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43093006/article/details/99355667
今日推荐