全局通栏广告

爱盲论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 1395|回复: 4
打印 上一主题 下一主题

android语音合成tts(云知声,百度tts,科大讯飞) - jsync - 简书

[复制链接]

247

主题

6625

帖子

1万

积分

金牌会员

Rank: 5Rank: 5

积分
16365
QQ
跳转到指定楼层
楼主
发表于 2020-4-14 20:22:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
楼主 zmy说:
目前语音合成这方面做得最好的就是科大讯飞了,合成速度快,准确度高,模型多。但问题也是相当明显,只有在线合成是免费的,离线则是一笔不小的开销,个人到是可以申请免费的几个机器使用。百度tts则算是专门为他的导航做的一套吧,性能相对也还可以,但是他只支持离在线混合模式,默认是在wifi情况下是使用在线模式,4g或无网络情况下使用离线模式。云知声tts则可以实现完全的离线合成模式,当然,相比于上面的两个,性能可能没那没完美,不过对于一般的应用来说,完全足够了,唯一的缺点就是模型实在是太少了。
在线合成和离线合成(合成速度)
这个应该很好理解,在线合成必须将数据传到第三方平台,调用他们的服务接口进行合成,这中间牵扯到网络状况,在网络良好的情况下,合成速度和离线模式没有太大的差别,但是有时候服务器也会来大姨妈,比如我之前使用的是百度tts(理论上,百度tts的wifi在线模式下,合成时间超过0.5秒后会自动使用离线模式,但是并没有)就遇到过有一整天都延迟在2秒左右,后来放弃了。再也不相信在线模式了,你无法保证你的网络一直都是畅通无阻的。
问题
在线模式虽然不是很稳定,但是也有他的优点,不需要吧模型和合成底层代码放在本地,离线合成虽然稳定快速,但是apk体积增加的有点夸张。
云知声
云知声的解决办法:把语音合成模型放在服务器后端,你要使用的时候下载到本地。
1
注册云知声开发者,创建应用,下载离线语音合成sdk,里面要有东西就两个。
一个是libs
libs
还有一个是assets
assets
将libs里面的所有东西都拷贝到你的项目的对应的libs下(比如app目录下的libs)
在你的模块的 gradle的android括号下加上
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}
assets文件夹里面的文件是合成模型文件,有两种处理方式。
第一种:
将assets拷贝到你的项目的assets下,然后访问assets文件流,将assets里面的文件复制到本地(多此一举)。
第二种:
直接将assets里面的文件复制到你手机的/unisound/tts(
把我解压后直接发送到手机
),如图
image.png
2
权限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>  
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
3
新建你的BaseApplication, 在 AndroidManifest里面声明,然后在BaseApplication中初始化你的TTs,
TTSUtils.getInstance().init();
附录(云知声的ttsutils)
public class TTSUtils implements SpeechSynthesizerListener {
    private static final String TAG = "TTSUtils";
    private static volatile TTSUtils instance = null;
    private boolean isInitSuccess = false;
    private SpeechSynthesizer mTTSPlayer;
    public static final String SAMPLE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/unisound/tts/";
    public static final String FRONTEND_MODEL = "frontend_model";
    public static final String BACKEND_MODEL = "backend_lzl";
    private static final String APPKEY = "wiouzjcsmxvqes7ibqqm5bcgqrzrvddsvtceiwa5";//这里换成你的key和secret
    private static final String SECRET = "3e20d7aff586ffe7dce62b302e7cc378";
    private TTSUtils() {
    }
    public static TTSUtils getInstance() {
        if (instance == null) {
            synchronized (TTSUtils.class) {
                if (instance == null) {
                    instance = new TTSUtils();
                }
            }
        }
        return instance;
    }
    public void init() {
        try {
            Context context = BaseApplication.getContext();
            //判断文件是否完整
            File _FrontendModelFile = new File(SAMPLE_DIR + FRONTEND_MODEL);
            File _BackendModelFile = new File(SAMPLE_DIR + BACKEND_MODEL);
            //校验文件的完整性
            String file1 = getFileMD5(_FrontendModelFile);
            String file2 = getFileMD5(_BackendModelFile);
//            if(!file1.equals("27ce3b75c2784353e33840c5e63b5f0c")||!file2.equals("cfcdd50077ee5a6c5d673b728a8d6f5")){
            if(!file1.equals("27ce3b75c2784353e33840c5e63b5f0c")||!file2.equals("57c9e96801d1173186407193e26a5ecf")){
                _FrontendModelFile.delete();
                _BackendModelFile.delete();
                Toast.makeText(context, "下载离线包后即可使用语音合成功能!", Toast.LENGTH_SHORT).show();
                return;
            }
            mTTSPlayer = new SpeechSynthesizer(context, APPKEY, SECRET);
            mTTSPlayer.setOption(SpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL); // 设置本地合成
            File file = new File(SAMPLE_DIR);
            if (!file.exists()) {
                file.mkdirs();
            }
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, SAMPLE_DIR + FRONTEND_MODEL);// 设置前端模型
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, SAMPLE_DIR + BACKEND_MODEL);// 设置后端模型
//            setOption(int key, java.lang.Object value)
//            设置合成相关参数
//            示例:
//            设置合成语速 SpeechConstants.TTS_KEY_VOICE_SPEED 范围 0 ~ 100 int
//            设置合成音高 SpeechConstants.TTS_KEY_VOICE_PITCH 范围 0 ~ 100 int
//            设置合成音量 SpeechConstants.TTS_KEY_VOICE_VOLUME 范围 0 ~ 100 int
//            设置是否将英文按拼音读 SpeechConstants.TTS_KEY_IS_READ_ENLISH_IN_PINYIN 如:wang->王 boolean
//            设置语音结尾段的静音时长 SpeechConstants.TTS_KEY_BACK_SILENCE 0 ~ 1000 单位ms int
//            设置语音开始段的静音时长 SpeechConstants.TTS_KEY_FRONT_SILENCE 0 ~ 1000 单位ms int
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_SPEED,85);
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_PITCH,55);
//            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_VOICE_VOLUME,200);
            //文字加数字,“玩啊”+106  106会读一百零六    “H”+106   会一个一个读出来 H 1 0 6
//            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_IS_READ_ENLISH_IN_PINYIN,false);
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACK_SILENCE,300);//设置尾音
//            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_FRONT_SILENCE,1000);//设置开始音
            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_PLAY_START_BUFFER_TIME,250);//语音开始缓冲时间
//            mTTSPlayer.setOption(SpeechConstants.TTS_KEY_STREAM_TYPE, AudioManager.STREAM_SYSTEM);
            mTTSPlayer.setTTSListener(this);// 设置回调监听
            mTTSPlayer.init(null);// 初始化合成引擎
        } catch (Exception e) {
//            e.printStackTrace();
            Toast.makeText(BaseApplication.getContext(), "下载离线包后即可使用语音合成功能!", Toast.LENGTH_SHORT).show();
            return;
        }
    }
    /**获取文件的md5码,判断文件的完整性*/
    private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
        if (!file.exists()||!file.isFile()) {
//            不是文件,或者不存在
            return "";
        }
        MessageDigest digest;
        FileInputStream in;
        byte buffer[] = new byte[1024];
        int len;
        digest = MessageDigest.getInstance("MD5");
        in = new FileInputStream(file);
        while ((len = in.read(buffer, 0, 1024)) != -1) {
            digest.update(buffer, 0, len);
        }
        in.close();
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }
    /**一个字节一个字节读*/
    public static void speckText(String msg){
        TTSUtils.getInstance().speak(getS(msg));
    }
    /**语义识别朗读*/
    public static void speeckTrueText(String msg){
        if(msg==null||"".equals(msg)){
            msg="";
        }
        if(msg.length()>25){
            msg=msg.substring(0,15);
        }
        TTSUtils.getInstance().speak(msg);
    }
    public static String getS(String msg){
        if(msg==null||"".equals(msg)){
            return "";
        }
        char[] chars = msg.toCharArray();
        StringBuilder sb=new StringBuilder();
        for (char aChar : chars) {
            sb.append(aChar+"\"");
        }
        return sb.toString();
    }
    public void speak(String msg) {
        try {
            if (isInitSuccess) {
                mTTSPlayer.playText(msg);
            }else {
                init();
            }
        } catch (Exception e) {
            Toast.makeText(BaseApplication.getContext(), "下载离线包后即可使用语音合成功能!", Toast.LENGTH_SHORT).show();
        }
    }
    public void stop() {
        mTTSPlayer.stop();
    }
    public void pause() {
        mTTSPlayer.pause();
    }
    public void resume() {
        mTTSPlayer.resume();
    }
    public void release() {
        if (null != mTTSPlayer) {
            // 释放离线引擎
            mTTSPlayer.release(SpeechConstants.TTS_RELEASE_ENGINE, null);
        }
    }
    @Override
    public void onEvent(int type) {
        switch (type) {
            case SpeechConstants.TTS_EVENT_INIT:
                isInitSuccess = true;
                break;
            case SpeechConstants.TTS_EVENT_SYNTHESIZER_START:
                // 开始合成回调
                Log.i(TAG, "beginSynthesizer");
                break;
            case SpeechConstants.TTS_EVENT_SYNTHESIZER_END:
                // 合成结束回调
                Log.i(TAG, "endSynthesizer");
                break;
            case SpeechConstants.TTS_EVENT_BUFFER_BEGIN:
                // 开始缓存回调
                Log.i(TAG, "beginBuffer");
                break;
            case SpeechConstants.TTS_EVENT_BUFFER_READY:
                // 缓存完毕回调
                Log.i(TAG, "bufferReady");
                break;
            case SpeechConstants.TTS_EVENT_PLAYING_START:
                // 开始播放回调
                Log.i(TAG, "onPlayBegin");
                break;
            case SpeechConstants.TTS_EVENT_PLAYING_END:
                // 播放完成回调
                Log.i(TAG, "onPlayEnd");
                break;
            case SpeechConstants.TTS_EVENT_PAUSE:
                // 暂停回调
                Log.i(TAG, "pause");
                break;
            case SpeechConstants.TTS_EVENT_RESUME:
                // 恢复回调
                Log.i(TAG, "resume");
                break;
            case SpeechConstants.TTS_EVENT_STOP:
                // 停止回调
                Log.i(TAG, "stop");
                break;
            case SpeechConstants.TTS_EVENT_RELEASE:
                // 释放资源回调
                Log.i(TAG, "release");
                break;
            default:
                break;
        }
    }
    @Override
    public void onError(int type, String errorMSG) {
        Log.e(TAG, "语音合成错误回调: " + errorMSG);
    }
    /**如果将assets里面的文件放在你自己的assets下,就需要用到下面的方法*/
//    public static void copyAssetsFile2SDCard(Context context, String fileName, String path) {
//        InputStream is=null;
//        FileOutputStream fos=null;
//        try {
//            is= context.getAssets().open(fileName);
//            fos= new FileOutputStream(new File(path));
//            byte[] buffer = new byte[1024];
//            int byteCount = 0;
//            while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取buffer字节
//                fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流
//            }
//           fos.flush();
//        } catch (IOException e) {
//            Log.e(TAG, "copyAssetsFile2SDCard: " + e.toString());
//        } finally {
//            if(fos!=null){
//                try {
//                    fos.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//            if(is!=null){
//                try {
//                    is.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//    }
}
4调用
TTSUtils.speeckTrueText(msg);
或者TTSUtils.speckText(msg);
后记
在集成的时候遇到过很多bug,比如模型文件放在不正确的地方会导致没有声音,模型文件不完整的时候回导致程序崩溃,发音不是预期的效果等等,还有一些参数的设置,具体参数还是得看官方的开发文档,看api就行了。demo暂时没有整理比较乱,有需要的可以留言。
关于科大讯飞
科大讯飞也有一种离线的玩法,但是得下载一个语记app,然后在手机语言输入法设置下tts服务为科大讯飞的,然后调用android自带的语音合成,就是中文了(听说这里也可以调用讯飞在线的api进行设置也可以,本人没试过)。
? 著作权归作者所有,转载或内容合作请联系作者
阅读全文 ?
点赞赚钻最高日赚数百元
?

(0)
5ae33ecf731b
jsync
小礼物走一走,来简书关注我
赞赏
20200210jid150
? 下载简书,创作你的创作
暂无评论
? 写评论
icon_comment_no.1b12627d
智慧如你,不想
发表一点想法
咩~
推荐阅读
更多精彩内容 ?
下载简书App
你也可以写文章赚赞赏
【笔记】树莓派使用讯飞SDK实现语音识别跟语音播报
webp
oldfool
App中阅读 ?5997?0?9
Android在线语音合成——讯飞开放平台
webp
Charon_Pluto
App中阅读 ?1264?0?3
灵云TTS(语音合成)
webp
liliLearn
App中阅读 ?636?0?1
ReactNative集成百度语音合成
webp
君伟说
App中阅读 ?2367?10?6
《惊天魔盗团2》看完就是想写出来,剧透慎入!
webp
栗子酱举个栗子
App中阅读 ?18681?0?0
每日闲聊 小帆分享11.26
webp
小帆爱阳光
App中阅读 ?37?1?0
IONIC plugin 开发阶段的真机debug工作
webp
DONG999
App中阅读 ?40?0?0
深入理解Android自定义View
webp
SeanMa
App中阅读 ?288?0?5
misc-logo.805143dd
创作你的创作,
接受世界的赞赏
登录
|
打开App
|
热门文章
? 下载简书,创作你的创作
1-0-1-0-0-14-5-0
灵云TTS(语音合成)
webp
liliLearn
App中阅读 ?636?0?1
ReactNative集成百度语音合成
webp
君伟说
App中阅读 ?2367?10?6
《惊天魔盗团2》看完就是想写出来,剧透慎入!
webp
栗子酱举个栗子
App中阅读 ?18681?0?0
每日闲聊 小帆分享11.26
webp
小帆爱阳光
App中阅读 ?37?1?0
IONIC plugin 开发阶段的真机debug工作
webp
DONG999
App中阅读 ?40?0?0
深入理解Android自定义View
webp
SeanMa
App中阅读 ?288?0?5
misc-logo.805143dd
创作你的创作,
接受世界的赞赏
登录
|
打开App
|
热门文章
? 下载简书,创作你的创作
后退
前进
首页
多窗口
本帖来自安卓秘书
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

247

主题

6625

帖子

1万

积分

金牌会员

Rank: 5Rank: 5

积分
16365
QQ
沙发
 楼主| 发表于 2020-4-14 20:23:09 | 只看该作者
沙发 zmy说:
这是在百度上找到的啊。
本帖来自安卓秘书
回复 支持 反对

使用道具 举报

32

主题

1984

帖子

6152

积分

金牌会员

Rank: 5Rank: 5

积分
6152
板凳
发表于 2020-4-14 21:17:53 来自手机 | 只看该作者
板凳 逍遥者说:
回复 沙发zmy

什么东西啊。



来自:掌上乐园
回复 支持 反对

使用道具 举报

64

主题

1144

帖子

3803

积分

高级会员

Rank: 4

积分
3803
地板
发表于 2020-4-14 21:50:27 | 只看该作者
<
地板 问题真难写说:zmy
还以为是你写的呢我正好奇你的文笔啥时候这么好了。
本帖来自安卓秘书
回复 支持 反对

使用道具 举报

247

主题

6625

帖子

1万

积分

金牌会员

Rank: 5Rank: 5

积分
16365
QQ
5#
 楼主| 发表于 2020-4-14 22:50:31 | 只看该作者
<
5楼 zmy说:问题真难写
不是我写的哈。
本帖来自安卓秘书
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋| 爱盲论坛  

GMT+8, 2024-11-20 17:38 , Processed in 0.297656 second(s), 26 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表