Android集成语音播报

技术背景

首先交代一下调研的技术背景:

根据调研,集成语音播报的方式市面上大致有三种:

第一种:基于Android系统自带的TextToSpeech类和讯飞API实现的语音播报

由于TextToSpeech只支持英文,德语,意大利语,法语,西班牙语,不支持中文,所以需要安装科大讯飞引擎,这种方式不支持使用,毕竟播报的固定就那么几个字,在线文字转音频,用TTS比较麻烦

第二种:仿支付宝方式,提前录制好音频,然后根据金额拼接成一段音频

我采用的是这种方式,自己封装实现语音播报功能

第三种:集成第三方SDK实现语音播报(例如讯飞)

这种使用方便但是成本高

需求

公众号扫码付款成功,app进行接收播报,播报文案为“收款成功XX元”,收到多条消息进行顺序播报

功能实现

在这里主要是讲解第二种方式,采用音频拼接合成方式

思路:

1、 拿到要播放的金额,把金额转为大写,大写转音频

2、 播放单个语音

3、 顺序播放

实现部分:

1、新建一个关于金额的工具类,写两个数组,一个是数字的0-9的数组,一个是拾到亿的数组,写一个方法进行判断替换,返回关于金额的中文大写文字,将大写文字转成音频

代码如下:

/**
 * 关于金钱的工具类
 */
public class MoneyUtils {

    private static final char[] NUM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    private static final char[] CHINESE_UNIT = {'元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟'};

    /**
     * 返回关于钱的中文式大写数字,支仅持到亿
     */
    public static String readInt(int moneyNum) {
        String res = "";
        int i = 0;
        if (moneyNum == 0) {
            return "0";
        }

        if (moneyNum == 10) {
            return "拾";
        }

        if (moneyNum > 10 && moneyNum < 20) {
            return "拾" + moneyNum % 10;
        }

        while (moneyNum > 0) {
            res = CHINESE_UNIT[i++] + res;
            res = NUM[moneyNum % 10] + res;
            moneyNum /= 10;
        }

        return res.replaceAll("0[拾佰仟]", "0")
                .replaceAll("0+亿", "亿")
                .replaceAll("0+万", "万")
                .replaceAll("0+元", "元")
                .replaceAll("0+", "0")
                .replace("元", "");
    }
}



/**
 * 返回数字对应的音频
 *
 * @param integerPart
 * @return
 */
private static List readIntPart(String integerPart) {
    List result = new ArrayList<>();
    String intString = MoneyUtils.readInt(Integer.parseInt(integerPart));
    int len = intString.length();
    for (int i = 0; i < len; i++) {
        char current = intString.charAt(i);
        if (current == '拾') {
            result.add(VoiceConstants.TEN);
        } else if (current == '佰') {
            result.add(VoiceConstants.HUNDRED);
        } else if (current == '仟') {
            result.add(VoiceConstants.THOUSAND);
        } else if (current == '万') {
            result.add(VoiceConstants.TEN_THOUSAND);
        } else if (current == '亿') {
            result.add(VoiceConstants.TEN_MILLION);
        } else {
            result.add(String.valueOf(current));
        }
    }
    return result;
}

2、创建一个MediaPlayer,数据源从assets中获取,调用prepareAsync()方法,异步加载,并设置监听,加载完毕后进行播放,监听播放完成的状态,在播放完成之后播放下一条语音

 /**
     * 开始播报
     *
     * @param voicePlay
     */
    private void start(final List voicePlay) {
        synchronized (VoicePlay.this) {

            final MediaPlayer mMediaPlayer = new MediaPlayer();
            final CountDownLatch mCountDownLatch = new CountDownLatch(1);
            AssetFileDescriptor assetFileDescription = null;

            try {
                final int[] counter = {0};
                assetFileDescription = FileUtils.getAssetFileDescription(mContext,
                        String.format(VoiceConstants.FILE_PATH, voicePlay.get(counter[0])));
                mMediaPlayer.setDataSource(
                        assetFileDescription.getFileDescriptor(),
                        assetFileDescription.getStartOffset(),
                        assetFileDescription.getLength());
                mMediaPlayer.prepareAsync();
                mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                         mMediaPlayer.start();
                    }
                });
                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mediaPlayer) {
//                        mediaPlayer -> {
                            mediaPlayer.reset();
                            counter[0]++;

                            if (counter[0] < voicePlay.size()) {
                                try {
                                    AssetFileDescriptor fileDescription2 = FileUtils.getAssetFileDescription(mContext,
                                            String.format(VoiceConstants.FILE_PATH, voicePlay.get(counter[0])));
                                    mediaPlayer.setDataSource(
                                            fileDescription2.getFileDescriptor(),
                                            fileDescription2.getStartOffset(),
                                            fileDescription2.getLength());
                                    mediaPlayer.prepare();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    mCountDownLatch.countDown();
                                }
                            } else {
                                mediaPlayer.release();
                                mCountDownLatch.countDown();
                            }
                        }
//                    }
                });


            } catch (Exception e) {
                e.printStackTrace();
                mCountDownLatch.countDown();
            } finally {
                if (assetFileDescription != null) {
                    try {
                        assetFileDescription.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            try {
                mCountDownLatch.await();
                notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3、短时间多次播报请求,采用同步方式进行,一条播完播放下一条,这里采用synchronized + notifyAll() 实现,当然也可以用其他的方式

player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                        @Override
                        public void onCompletion(MediaPlayer mp) {
                            mp.reset();
                            counter[0]++;
                            if (counter[0] < list.size()) {
                                try {
                                    AssetFileDescriptor fileDescriptor = FileUtils.getAssetFileDescription(String.format("sound/tts_%s.mp3", list.get(counter[0])));
                                    mp.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
                                    mp.prepare();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    latch.countDown();
                                }
                            } else {
                                mp.release();
                                latch.countDown();
                            }
                        }
                    });

遇到的问题及解决方案(因为项目推送用到个推,所以遇到这些问题,可以参考一下)

1.第一次初始化个推得到cid有延迟,造成得不到cid无法调用后台服务接口进行个推绑定cid,所以接收不到推送消息进行语音播报

解决办法:第一次登陆完成之后进行个推初始化,在首页进行cid判断,如果没有就用定时器每三秒去取一次cid,直到取到cid就关闭定时器调用后台接口绑定个推,(注意定时器执行了五次也就是15秒才取到cid,每个设备获取cid的时间不确定),如果出现cid一直取不到,但是定时器一直没有关闭在获取cid的情况下,设定定时器只要执行十次也就是30秒就自动关闭定时器

2.语音播报语速缓慢的问题

解决方法:使用MediaPlay组件进行语速加速,无法解决问题,发现是一个音频前后有无声部分,所以进行音频剪切

3.app登陆状态下,手机亮屏,过了20分钟app就自动挂了,所以无法接收到推送消息进行语音播报

解决方法:修改个推服务,设置为前台服务,可保证亮屏状态下,app一直在运行

4.在同一台设备上,登陆多个不同的账户,如登陆三个账户,则会推送三条重复的消息进行三次播报

解决方法:造成这个原因是每次登录都要调用后台接口,进行绑定,需传参cid、账户id,后台绑定同一个cid却有三个不同的账户,所以每次推送会推送三条,所以在退出登录的时候,做登出的操作,调用后台接口进行解绑

PS.以上为自己的经验总结分享,有不足的地方欢迎指正,共同进步。

你可能感兴趣的:(Android集成语音播报)