基于SoundPool的金额语音播报(无SDK,一次解码,可控制语速)

金额语音播报,做过支付行业的人都接触过这关功能,实现方法有很多种

比较常见的如 TTS(系统自带声音),语音引擎SDK(一些收费,这里不讨论),MP3(资源文件顺序播报)

TTS

最简单的实现方法吧,不过声音的改变比较困难,需要下载声音引擎

参考上一篇博客《金钱转中文字符串》得到中文字符串

然后使用tts进行播报

 textToSpeech = TextToSpeech(this,TextToSpeech.OnInitListener {
            if (it == TextToSpeech.SUCCESS){
                textToSpeech.language = Locale.CHINESE
                textToSpeech .speak("收款"+MoneyUtil.readString("30.57"),TextToSpeech.QUEUE_FLUSH,null,"1")
            }
        })

一个简单的语音播报就这样实现了

基于MP3资源实现

简书 Android 语音播报实现方案(无SDK)写得非常棒,不过这里使用的是Mediaplayer播放器,这里做了一些改进

由于是基于Mediaplayer,基本是监听播放完成和解码完成的监听器,然后播放完成后立刻解码下一个音频文件。

这种每次需要播放时才去解码语音文件进行播放,文字和文字加的语速间隔是不可控的。比如 一些低端机解码慢

资源准备

https://ai.baidu.com/tech/speech/tts_online 

百度AI语音合成,也可以自己录~

准备 0~9 的语音 点,千,百,十,亿,元,***收款成功,等语音

SoundPool

android专门用来播放短时长提示音,缺点是短~,无法知道啥时候播放完成优点的轻量级,一次load,后面即可实时播放

基于资源的语音播报,一般一个音频大概是0.5s-1s,应该没人会把1 拖到一秒种那么长吧,SoundPool刚好符合需求,短

1、SoundPool的初始化

 /**
     * SoundPool ID ArrayMap
     * 数据量小使用ArrayMap比HashMap性能更好
     * */
    private var soundId = ArrayMap<String,Int>()

/**
     * 初始化,第一次使用时自动初始化,无需手动调用
     * @param context
     * @param moneyString 播报的金额中文串(通过MoneyUtil获得)
     * @param autoPlay 初始化后是否自动播放
     * */
    private fun init(context: Context,moneyString:String ="",autoPlay:Boolean=false){

        var audioAttributes =  AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build();
        var assets = context.assets
        mSoundPool = SoundPool.Builder().setAudioAttributes(audioAttributes).setMaxStreams(22).build()

        mSoundPool.setOnLoadCompleteListener { _, sampleId, status ->
            if (status == 0 &&sampleId == soundId.size){ //所有资源初始化成功
                isPrepare = true //解码成功
                if (autoPlay){ //是否自动播放
                    playVoicePool(moneyString,context)
                }
            }
        }
        soundId.clear()
        soundId["sound/tts_1.mp3"] = mSoundPool.load(assets.openFd("sound/tts_1.mp3"),1)
        soundId["sound/tts_2.mp3"] = mSoundPool.load(assets.openFd("sound/tts_2.mp3"),1)
        soundId["sound/tts_3.mp3"] = mSoundPool.load(assets.openFd("sound/tts_3.mp3"),1)
        soundId["sound/tts_4.mp3"] = mSoundPool.load(assets.openFd("sound/tts_4.mp3"),1)
        soundId["sound/tts_5.mp3"] = mSoundPool.load(assets.openFd("sound/tts_5.mp3"),1)
        soundId["sound/tts_6.mp3"] = mSoundPool.load(assets.openFd("sound/tts_6.mp3"),1)
        soundId["sound/tts_7.mp3"] = mSoundPool.load(assets.openFd("sound/tts_7.mp3"),1)
        soundId["sound/tts_8.mp3"] = mSoundPool.load(assets.openFd("sound/tts_8.mp3"),1)
        soundId["sound/tts_9.mp3"] = mSoundPool.load(assets.openFd("sound/tts_9.mp3"),1)
        soundId["sound/tts_0.mp3"] = mSoundPool.load(assets.openFd("sound/tts_0.mp3"),1)
        soundId["sound/tts_dot.mp3"] = mSoundPool.load(assets.openFd("sound/tts_dot.mp3"),1)
        soundId["sound/tts_hundred.mp3"] = mSoundPool.load(assets.openFd("sound/tts_hundred.mp3"),1)
        soundId["sound/tts_receipt.mp3"] = mSoundPool.load(assets.openFd("sound/tts_receipt.mp3"),1)
        soundId["sound/tts_success.mp3"] = mSoundPool.load(assets.openFd("sound/tts_success.mp3"),1)
        soundId["sound/tts_ten_million.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten_million.mp3"),1)
        soundId["sound/tts_ten_thousand.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten_thousand.mp3"),1)
        soundId["sound/tts_ten.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten.mp3"),1)
        soundId["sound/tts_thousand.mp3"] = mSoundPool.load(assets.openFd("sound/tts_thousand.mp3"),1)
        soundId["sound/tts_yuan.mp3"] = mSoundPool.load(assets.openFd("sound/tts_yuan.mp3"),1)
    }

这里可以第一次播放时自动初始化,也可以程序开始直接初始化,都可

2、进行语音播报

fun playVoicePool(moneyString: String,context: Context) {
        thread { //异步线程
            synchronized(this){ //同步锁
                if (isPrepare){ //判断SoudPool是否加载完成
                    var soundList = getListSound(moneyString) //通过monneyString获取播放的音频
                    for (i in soundList){
                        if (soundId[i]!=null){ 
                            mSoundPool.play(soundId[i]!!,1f,1f,1,0,1f) //最后一个参数是语速
                            if (i.contains("tts_success")){ //tts_succes为***收款,间隔长一点
                                Thread.sleep(1100)
                            }else{
                                Thread.sleep(400) // 1,2 十,百,千,亿等等
                            }
                        }
                    }
                }else{
                    init(context,moneyString,true) //还未初始化则初始化
                }

            }
        }
    }

3、根据MoneyString获取对应的语音播放链表

/**
     * 根据@link MoneyUtil生成的中文串来获取链表
     * @param moneyString 播报的金额中文串(通过MoneyUtil获得)
     * @return
     * */
    private fun getListSound(moneyString: String):LinkedList<String>{
        var soundList = LinkedList<String>()
        var moneyChar = moneyString.toCharArray()

        soundList.add("sound/tts_success.mp3")

        for (i in moneyChar){
            if (NUM.contains(i)){
                soundList.add(getStringFormNUM(i))
            }
            if (CHIN_NUM.contains(i)){
                soundList.add(getStringFormCHINNUM(i))
            }
        }
        return soundList
    }

    /**
     * 获取中文字符资源
     * @param char
     * */
    private fun getStringFormCHINNUM(char: Char):String{
        var chineseNumPath =""
        when(char){
            '元' -> chineseNumPath = "sound/tts_yuan.mp3"
            '拾' -> chineseNumPath = "sound/tts_ten.mp3"
            '佰' -> chineseNumPath = "sound/tts_hundred.mp3"
            '仟' -> chineseNumPath = "sound/tts_thousand.mp3"
            '万' -> chineseNumPath = "sound/tts_ten_thousand.mp3"
            '亿' -> chineseNumPath = "sound/tts_ten_million.mp3"
            '点' -> chineseNumPath = "sound/tts_dot.mp3"
        }
        return chineseNumPath
    }

    /**
     * 获取数字资源
     * @param char
     * */
    private fun getStringFormNUM(char:Char):String{
        return String.format("sound/tts_%s.mp3",char)
    }

/**
     * 释放资源
     * */
    fun destroyPool(){
        if (mSoundPool!=null){
            mSoundPool.autoPause()
            mSoundPool.release()
            mSoundPool == null
        }
    }

使用单例子模式封装代码(完整代码)

/**
 * @author HuangJiaHeng
 * @date 2020/6/5.
 * 金额语音播报类

 * playVoicePool SoundPool 可以有效防止卡顿
 */
class VoiceSpeak {

    /**
     * SoundPool 使用SoundPool方式才初始化
     * */
    private lateinit var mSoundPool:SoundPool
    /**
     * SoundPool ID ArrayMap
     * 数据量小使用ArrayMap比HashMap性能更好
     * */
    private var soundId = ArrayMap<String,Int>()
    /**
     * SoundPool是否加载完成
     * */
    private var isPrepare = false

    companion object{

        private val NUM = arrayListOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
        private val CHIN_NUM = arrayListOf('元', '拾', '佰', '仟', '万', '亿','点')

        val util by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
            VoiceSpeak()
        }
    }

    /**
     * 释放资源
     * */
    fun destroyPool(){
        if (mSoundPool!=null){
            mSoundPool.autoPause()
            mSoundPool.release()
            mSoundPool == null
        }
    }

    /**
     * 以池的方式播放,可以有效减少资源解码卡顿情况 (同步锁)
     * @param moneyString 播报的金额中文串(通过MoneyUtil获得)
     * @param context
     * */
    fun playVoicePool(moneyString: String,context: Context) {
        thread {
            synchronized(this){
                if (isPrepare){
                    var soundList = getListSound(moneyString)
                    for (i in soundList){
                        if (soundId[i]!=null){
                            mSoundPool.play(soundId[i]!!,1f,1f,1,0,1f)
                            if (i.contains("tts_success")){
                                Thread.sleep(1100)
                            }else{
                                Thread.sleep(400)
                            }
                        }
                    }
                }else{
                    init(context,moneyString,true)
                }

            }
        }
    }


    /**
     * 根据@link MoneyUtil生成的中文串来获取链表
     * @param moneyString 播报的金额中文串(通过MoneyUtil获得)
     * @return
     * */
    private fun getListSound(moneyString: String):LinkedList<String>{
        var soundList = LinkedList<String>()
        var moneyChar = moneyString.toCharArray()

        soundList.add("sound/tts_success.mp3")

        for (i in moneyChar){
            if (NUM.contains(i)){
                soundList.add(getStringFormNUM(i))
            }
            if (CHIN_NUM.contains(i)){
                soundList.add(getStringFormCHINNUM(i))
            }
        }
        return soundList
    }

    /**
     * 获取中文字符资源
     * @param char
     * */
    private fun getStringFormCHINNUM(char: Char):String{
        var chineseNumPath =""
        when(char){
            '元' -> chineseNumPath = "sound/tts_yuan.mp3"
            '拾' -> chineseNumPath = "sound/tts_ten.mp3"
            '佰' -> chineseNumPath = "sound/tts_hundred.mp3"
            '仟' -> chineseNumPath = "sound/tts_thousand.mp3"
            '万' -> chineseNumPath = "sound/tts_ten_thousand.mp3"
            '亿' -> chineseNumPath = "sound/tts_ten_million.mp3"
            '点' -> chineseNumPath = "sound/tts_dot.mp3"
        }
        return chineseNumPath
    }

    /**
     * 获取数字资源
     * @param char
     * */
    private fun getStringFormNUM(char:Char):String{
        return String.format("sound/tts_%s.mp3",char)
    }

    /**
     * 初始化,第一次使用时自动初始化,无需手动调用
     * @param context
     * @param moneyString 播报的金额中文串(通过MoneyUtil获得)
     * @param autoPlay 初始化后是否自动播放
     * */
    private fun init(context: Context,moneyString:String ="",autoPlay:Boolean=false){

        var audioAttributes =  AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build();
        var assets = context.assets
        mSoundPool = SoundPool.Builder().setAudioAttributes(audioAttributes).setMaxStreams(22).build()

        mSoundPool.setOnLoadCompleteListener { _, sampleId, status ->
            if (status == 0 &&sampleId == soundId.size){
                isPrepare = true
                if (autoPlay){
                    playVoicePool(moneyString,context)
                }
            }
        }
        soundId.clear()
        soundId["sound/tts_1.mp3"] = mSoundPool.load(assets.openFd("sound/tts_1.mp3"),1)
        soundId["sound/tts_2.mp3"] = mSoundPool.load(assets.openFd("sound/tts_2.mp3"),1)
        soundId["sound/tts_3.mp3"] = mSoundPool.load(assets.openFd("sound/tts_3.mp3"),1)
        soundId["sound/tts_4.mp3"] = mSoundPool.load(assets.openFd("sound/tts_4.mp3"),1)
        soundId["sound/tts_5.mp3"] = mSoundPool.load(assets.openFd("sound/tts_5.mp3"),1)
        soundId["sound/tts_6.mp3"] = mSoundPool.load(assets.openFd("sound/tts_6.mp3"),1)
        soundId["sound/tts_7.mp3"] = mSoundPool.load(assets.openFd("sound/tts_7.mp3"),1)
        soundId["sound/tts_8.mp3"] = mSoundPool.load(assets.openFd("sound/tts_8.mp3"),1)
        soundId["sound/tts_9.mp3"] = mSoundPool.load(assets.openFd("sound/tts_9.mp3"),1)
        soundId["sound/tts_0.mp3"] = mSoundPool.load(assets.openFd("sound/tts_0.mp3"),1)
        soundId["sound/tts_dot.mp3"] = mSoundPool.load(assets.openFd("sound/tts_dot.mp3"),1)
        soundId["sound/tts_hundred.mp3"] = mSoundPool.load(assets.openFd("sound/tts_hundred.mp3"),1)
        soundId["sound/tts_success.mp3"] = mSoundPool.load(assets.openFd("sound/tts_success.mp3"),1)
        soundId["sound/tts_ten_million.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten_million.mp3"),1)
        soundId["sound/tts_ten_thousand.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten_thousand.mp3"),1)
        soundId["sound/tts_ten.mp3"] = mSoundPool.load(assets.openFd("sound/tts_ten.mp3"),1)
        soundId["sound/tts_thousand.mp3"] = mSoundPool.load(assets.openFd("sound/tts_thousand.mp3"),1)
        soundId["sound/tts_yuan.mp3"] = mSoundPool.load(assets.openFd("sound/tts_yuan.mp3"),1)
    }
}

使用:

        VoiceSpeak.util.playVoicePool(MoneyUtil.readString("30.57"),this)
        VoiceSpeak.util.playVoicePool(MoneyUtil.readString("108"),this)
        VoiceSpeak.util.playVoicePool(MoneyUtil.readString("556.78"),this)
        VoiceSpeak.util.playVoicePool(MoneyUtil.readString("104850000.56"),this)

githubDemo :这么简单就不要demo了吧 【滑稽】

 资源放在 assets/sound目录,文件名和路径要和代码里面一致

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SoundPool类是Android中用于播放短音频的类,它可以实现快速加载音频资源并进行播放,适用于播放音效、提示音等。下面是关于SoundPool类的实验小结: 1. 创建SoundPool对象 首先需要创建一个SoundPool对象,可以使用SoundPool的构造函数进行创建。例如,可以使用以下代码创建一个SoundPool对象: ``` SoundPool soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); ``` 其中,第一个参数表示SoundPool对象的最大容量,第二个参数表示使用的音频流类型,第三个参数表示音频品质,可以设置为0。 2. 加载音频资源 在播放音频之前,需要将音频资源加载到SoundPool中。可以使用SoundPool的load方法进行加载。例如,加载一个名为sound1的音频资源: ``` int soundId = soundPool.load(context, R.raw.sound1, 1); ``` 其中,第一个参数表示上下文对象,第二个参数表示音频资源的ID,第三个参数表示优先级。 3. 播放音频 加载音频资源后,可以使用SoundPool的play方法进行播放。例如,播放上一步加载的音频资源: ``` soundPool.play(soundId, 1, 1, 0, 0, 1); ``` 其中,第一个参数表示音频资源的ID,第二个参数表示左声道音量,第三个参数表示右声道音量,第四个参数表示优先级,第五个参数表示循环次数(0表示不循环,-1表示无限循环),第六个参数表示播放速度。 4. 释放资源 最后,需要在不使用SoundPool时释放资源,可以使用SoundPool的release方法进行释放。例如: ``` soundPool.release(); ``` 总之,SoundPool类是Android多媒体应用开发中一个非常实用的类,可以方便快捷地实现音频播放功能。但需要注意的是,SoundPool适用于短音频,对于长音频的播放建议使用MediaPlayer类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值