在Android实现语音播报金额的功能

本文最后更新于 将近 8 年前,文中所描述的信息可能已发生改变。

前言

接到一个需求,要在Android实现上实现语音播报的功能。 播放的内容是固定的,就是在收款成功时,播放一句"成功收款XXX元"。 iOS中就方便多了,可以用AVSpeechSynthesizer直接实现。 而Android中,由于系统的TextToSpeech(TTS)不支持中文,因此只能通过第三方语音SDK或者自己控制播放录制好的音频来实现。然而只有这么几个字,调用其他SDK感觉没必要,于是决定自己实现。

获取音源

首先可以从google翻译或者百度翻译中获取需要的音频。 比如百度的, ts.baidu.com/text2audio?lan=zh&pid=101&ie=UTF-8&text=你好&spd=2 把’你好’替换成需要的文字,然后把这个链接放到下载工具里即可获取,下载后需要把文件后缀改成mp3。更改链接后面的spd参数还可以控制播放速度。

google音频的获取地址是 https://translate.google.com/translate_tts?ie=UTF-8&q=%E4%BD%A0%E5%A5%BD&tl=zh-TW&client=tw-ob

播放功能

接着是要实现播放语音的功能。Android中播放语音有两种方式。一种是通过MediaPlayer,另一种是SoundPool。由于MediaPlayer占用资源多,存在播放延迟。而SoundPool 适合播放短小的音频。因此还是选择用SoundPool 来实现播放语音。 首先将上面得到的音频文件命名好放到res/raw目录中,然后 需要实现一个SoundManager来控制声音的播放,SoundManager是从极客学院的一个教程的课件上更改而来。通过调用playSeqSounds(List<String> soundsToPlay)来播放多个音符。

xml

<script type="text/javascript">
	// 禁用F12
	document.onkeydown = document.onkeyup = document.onkeypress = function() {
		if (window.event.keyCode == 123) {
			window.event.returnValue = false;
			return (false);
		}
	}
</script>
java

public class SoundManager {
    //Tag of this class for logging purpose
    private static String TAG = SoundManager.class.getSimpleName();
    //Singleton instance
    private static SoundManager instance;
    private int mCurrentStreamId;
    private Context mContext;
    private Handler mHandler = new Handler();

    private Runnable mPlayNext = new Runnable() {
        public void run() {
            SoundManager.this.mSoundPool
                    .stop(SoundManager.this.mCurrentStreamId);
            SoundManager.this.playNextSound();
        }
    };
    private boolean mPlaying;
    private SoundPool mSoundPool;
    private HashMap<String, Sound> mSoundPoolMap;
    private Queue<String> mSoundQueue = new LinkedList<>();
    private float volume = 0.5F;

    private SoundManager() {
    }

    //Singleton
    public static synchronized SoundManager getInstance() {
        if (instance == null) {
            instance = new SoundManager();
        }
        return instance;
    }

    private void playNextSound() {
        if (!this.mSoundQueue.isEmpty()) {
            String str = this.mSoundQueue.poll();
            Sound sound = this.mSoundPoolMap.get(str);
            if (sound != null) {
                this.mCurrentStreamId = this.mSoundPool.play(sound.id, volume,
                        volume, 1, 0, 1.0F);
                this.mPlaying = true;
                this.mHandler.postDelayed(this.mPlayNext, sound.time);
            }
        }
    }

    public void addSound(String text, int resID, int timeDelayed) {
        Sound localSound = new Sound(this.mSoundPool.load(this.mContext, resID, 1), timeDelayed);
        this.mSoundPoolMap.put(text, localSound);
    }

    /**
     * Clean up method
     */
    public void cleanup() {
        unloadAll();
        this.mSoundPool.release();
        this.mSoundPool = null;
        instance = null;
    }

    public void initSounds(Context context) {
        this.mContext = context;
        this.mSoundPool = new SoundPool(1, 3, 0);
        this.mSoundPoolMap = new HashMap<>();

        this.mPlaying = false;

        addSound("0", R.raw.zero, 378);
        addSound("1", R.raw.one, 360);
        addSound("2", R.raw.two, 342);
        addSound("3", R.raw.three, 386);
        addSound("4", R.raw.four, 414);
        addSound("5", R.raw.five, 373);
        addSound("6", R.raw.six, 378);
        addSound("7", R.raw.seven, 386);
        addSound("8", R.raw.eight, 342);
        addSound("9", R.raw.nine, 396);

        addSound("shi", R.raw.shi, 396);
        addSound("bai", R.raw.bai, 342);
        addSound("qian", R.raw.qian, 378);
        addSound("wan", R.raw.wan, 342);
        addSound("yi", R.raw.yi, 324);
        addSound("yuan", R.raw.yuan, 378);
        addSound("dot", R.raw.dot, 360);
        addSound("cgsk", R.raw.cgsk, 1440);
    }

    public void playSeqSounds(List<String> soundsToPlay) {
        int length = soundsToPlay.size();
        for (int j = 0; ; j++) {
            if (j >= length) {
                if (!this.mPlaying)
                    playNextSound();
                return;
            }
            String str = soundsToPlay.get(j);
            this.mSoundQueue.add(str);
        }
    }

    /**
     * Play the sound
     *
     * @param text text for the sound
     */
    public void playSound(String text) {
        stopSound();
        this.mSoundQueue.add(text);
        playNextSound();
    }

    /**
     * Stop playing the sound .
     */
    public void stopSound() {
        this.mHandler.removeCallbacks(this.mPlayNext);
        this.mSoundQueue.clear();
        this.mSoundPool.stop(this.mCurrentStreamId);
        this.mPlaying = false;
    }

    /**
     * Remove all the sound
     */
    public void unloadAll() {
        stopSound();
        if (this.mSoundPoolMap.size() > 0) {
            Iterator<String> localIterator = this.mSoundPoolMap.keySet().iterator();
            while (true) {
                if (!localIterator.hasNext()) {
                    this.mSoundPoolMap.clear();
                    return;
                }
                String str = localIterator.next();
                this.mSoundPool.unload(this.mSoundPoolMap.get(str).id);
            }
        }

    }

    private final class Sound {
        public int id;
        public int time;

        public Sound(int id, int time) {
            this.id = id;
            this.time = time;
        }
    }
}

金额转换

播放声音的规则完成之后,还需要控制播放的内容。 一般播放金额的时候,有以下规则:

  1. 每4位数字后面加一个单位,单位是亿、万。这4位数字中间的单位是千、百、十。
  2. 如果中间有多个0,那么只要读一次0。且首位不能为0。
  3. 当为2位数且第一位为1时,1不用读 。
  4. 有小数点时,直接读"点",然后按顺序读数字。
java

public class VoiceHelper {

    private static final String TAG = "VoiceHelper: ";
    private static String[] units = {"qian", "bai", "shi"};
    public static final String WAN = "wan";
    public static final String YI = "yi";
    public static final String DOT = "dot";
    public static final String CGSK = "cgsk";
    public static final String YUAN = "yuan";

    // 4321 2345 6789 . 12345
    // 四 千 三 百 二 十 一 亿 二 千 三 百 四 十 五 万 六 千 七 百 八 十 九 点 xxxx 元

    public static void playNumber(SoundManager soundManager, String price) {
        if (!isLegalPrice(price)) {
            Log.w(TAG, "error format price");
            return;
        }

        soundManager.stopSound();
        List<String> speech = new LinkedList<>();
        convertPrice(speech, price);
        Log.d(TAG, speech.toString());
        soundManager.playSeqSounds(speech);
    }

    private static void convertPrice(List<String> speech, String price) {
        speech.add(CGSK);

        String[] strings = price.split("\\.");

        String integerString = strings[0];
        int startPosition = 0;

        //处理 亿位 及以上
        if (integerString.length() > 8) {
            String oneHundredMillionString = integerString.substring(0, integerString.length() - 8);
            startPosition = integerString.length() - 8;
            speech.addAll(unitPrice(oneHundredMillionString));
            speech.add(YI);
        }

        //处理 亿位 以下 至 万位
        if (integerString.length() > 4) {
            String tenHundredString = integerString.substring(startPosition, integerString.length() - 4);
            speech.addAll(unitPrice(tenHundredString));
            startPosition = integerString.length() - 4;
            speech.add(WAN);
        }

        //处理 万位 以下
        String finalString = integerString.substring(startPosition, integerString.length());
        if (finalString.equals("0")) {
            speech.add("0");
        }
        else {
            speech.addAll(unitPrice(finalString));
        }

        //处理小数点后的读音
        if (strings.length == 2) {
            speech.add(DOT);
            int decimalLength = strings[1].length();
            for (int i = 0; i < decimalLength; i++) {
                speech.add(String.valueOf(strings[1].charAt(i)));
            }
        }

        speech.add(YUAN);
    }

    private static List<String> unitPrice(String simplePrice) {
        List<String> result = new LinkedList<>();
        int length = simplePrice.length();

        //当为2位数且第一位为1时,1不用读
        if (length == 2 && simplePrice.charAt(0) == '1') {
            result.add(units[2]);
            char lastChar = simplePrice.charAt(1);
            if (lastChar != '0') {
                result.add(String.valueOf(lastChar));
            }
            return result;
        }

        boolean readZero = simplePrice.charAt(0) == '0';
        int unitPos = 4 - length;
        if (simplePrice.length() <= 4) {
            for (int i = 0; i < length; i++) {
                if (simplePrice.charAt(i) != '0') {
                    if (readZero) {
                        result.add("0");
                    }
                    readZero = false;
                    result.add(String.valueOf(simplePrice.charAt(i)));
                    //最后一个数字不用读单位
                    if (i != length - 1)
                        result.add(units[unitPos]);
                } else {
                    readZero = true;
                }
                unitPos++;
            }
        }
        return result;
    }

    private static boolean isLegalPrice(String price) {
        boolean res = true;
        int len = price.length();
        for (int i = 0; i < len; i++) {
            if (!Character.isDigit(price.charAt(i)) && price.charAt(i) != '.') {
                res = false;
                break;
            }
        }
        if (price.split("\\.")[0].length() > 12) {
            res = false;
        }
        if (price.length() >= 2 && price.charAt(0) == '0' && price.charAt(1) != '.') {
            res = false;
        }
        return res;
    }
}
在Mac 10.11中编译Android 6.0源码
Valaxy v0.19.2 驱动 | 主题 - Yun v0.19.2