欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

离线语音合成使用——科大讯飞or云知音or百度语音

程序员文章站 2022-04-30 10:23:10
...

本片主要讲解如果使用科大讯飞和云知音这两个离线语音合成功能。

目前语音合成的主要有科大讯飞、百度语音、云知声。

一、三大产品功能分析

1.百度语音

百度语音合成只有离在线语音服务(免费),没有纯离线,离在线语音合成,在首次使用语音功能,必须联网,让其通过网络获得百度的授权文件之后,方可正常使用,之后不联网也可以正常使用,联网使用优先使用在线包,不联网则使用离线包。百度语音合成文档地址:http://ai.baidu.com/docs#/TTS-Android-SDK/top,每天有调用次数限制,如下图:

离线语音合成使用——科大讯飞or云知音or百度语音

2.科大讯飞

讯飞语语音合成有在线和离线两种,在线免费,离线收费,收费标准如下:

离线语音合成使用——科大讯飞or云知音or百度语音

在线只有联网的时候才可以正常使用,离线则无需任何联网,就可正常使用,如果选择离线包,体验版SDk下载后就可以为你生产专用的SDK,SDK的demo中会配置好一切,比如说key这些,都会使用你建应用所给的,无需你手动再进行配置了,简单易用,但下载下来还得调试一番方可正常使用。科大讯飞官网:http://www.xfyun.cn/,SDK文档地址:http://mscdoc.xfyun.cn/android/api/

3.云知声

云知声有在线和离线的,在线的官方号称永久免费,离线的也是收费的,但是没能查到收费标准,语音文档地址:http://admin-web-files.ks3-cn-shanghai.ksyun.com/docs/sdk/USC_DevelGuide_Android_common.pdf,置于其他详细信息可以上官网了解,地址:http://dev.hivoice.cn/

二、三者使用

1.百度语音

如何使用请见文档,文档写的相当详细,地址:http://ai.baidu.com/docs#/Begin/top,截图如下:

离线语音合成使用——科大讯飞or云知音or百度语音

包括如何注册如何使用,都明确标出来了,我只将自己的关于语音合成的代码贴出来,如下:

public class BaiDuSpeechUtil {

    private final String TAG = this.getClass().getSimpleName();
    private SpeechSynthesizer mSpeechSynthesizer;
    private String mSampleDirPath;
    private static final String SAMPLE_DIR_NAME = "baiduTTS";
    //-------以下全是在assets下的文件,使用离线时必须全部copy到手机中方可使用----start--
    private static final String SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female.dat";
    private static final String SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male.dat";
    private static final String TEXT_MODEL_NAME = "bd_etts_text.dat";
    private static final String ENGLISH_SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female_en.dat";
    private static final String ENGLISH_SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male_en.dat";
    private static final String ENGLISH_TEXT_MODEL_NAME = "bd_etts_text_en.dat";

    //--------end-------------------------------------------------------------


    private static BaiDuSpeechUtil baiDuSpeechUtil = null;
    public static BaiDuSpeechUtil getInstance(){
        if(baiDuSpeechUtil == null) {
            synchronized (BaiDuSpeechUtil.class) {
                if(baiDuSpeechUtil == null) {
                    baiDuSpeechUtil = new BaiDuSpeechUtil();
                }
            }
        }
        return baiDuSpeechUtil;
    }

    /**
     * 初始化百度语音资源
     * @param context
     */
    public void setInitialEnv(Context context) {
        initialEnv(context);
    }
    /**
     * 初始化百度语音播报相关
     * @param context
     */
    public void setInitialTts(Context context, SpeechSynthesizerListener speechSynthesizerListener){
        initialTts(context,speechSynthesizerListener);
    }


    private void initialEnv(Context context) {
//        long start_time= System.currentTimeMillis();
        if (mSampleDirPath == null) {
            String sdcardPath = Environment.getExternalStorageDirectory().toString();
            mSampleDirPath = sdcardPath + "/" + SAMPLE_DIR_NAME;
        }
        makeDir(mSampleDirPath);
        copyFromAssetsToSdcard(context,false, SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
        copyFromAssetsToSdcard(context,false, SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_MALE_MODEL_NAME);
        copyFromAssetsToSdcard(context,false, TEXT_MODEL_NAME, mSampleDirPath + "/" + TEXT_MODEL_NAME);
        copyFromAssetsToSdcard(context,false, "english/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/"
                + ENGLISH_SPEECH_FEMALE_MODEL_NAME);
        copyFromAssetsToSdcard(context,false, "english/" + ENGLISH_SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/"
                + ENGLISH_SPEECH_MALE_MODEL_NAME);
        copyFromAssetsToSdcard(context,false, "english/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + "/"
                + ENGLISH_TEXT_MODEL_NAME);

//        Log.d(TAG,"initialEnv cost:"+ (System.currentTimeMillis()-start_time));
    }

    private void makeDir(String dirPath) {
        File file = new File(dirPath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    /**
     * 将sample工程需要的资源文件拷贝到SD卡中使用(授权文件为临时授权文件,请注册正式授权)
     * 主要是在离线时候用到,只需执行一次即可,这里写的不严谨,应该去判断一下离线用的那些文件,sd卡是否存在,如果不存在,则copy,如果存在则无需在copy,可在子线程操作
     * @param isCover 是否覆盖已存在的目标文件
     * @param source
     * @param dest
     */
    private void copyFromAssetsToSdcard(Context context,boolean isCover, String source, String dest) {
        File file = new File(dest);
        if (isCover || (!isCover && !file.exists())) {
            InputStream is = null;
            FileOutputStream fos = null;
            try {
                is = context.getAssets().open(source);
                String path = dest;
                fos = new FileOutputStream(path);
                byte[] buffer = new byte[1024];
                int size = 0;
                while ((size = is.read(buffer, 0, 1024)) != -1) {
                    fos.write(buffer, 0, size);
                }
                fos.flush();


            } catch (Exception e) {
                e.printStackTrace();
            } finally {

                if (is != null) {
                    try {
                        is.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
                if (fos != null) {
                    try {
                        is.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }


            }
        }


    }
//此方法可在子线程中操作,由于这个初始化过程比较费时,大概在1s左右,看项目需求而定,如果是进入界面就必须播放(伴随UI改变的)的,在UI线程,如无其他特殊要求,放在子线程中即可
    private void initialTts(Context context,SpeechSynthesizerListener speechSynthesizerListener) {
//        long start_time= System.currentTimeMillis();
        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
        mSpeechSynthesizer.setContext(context);
        mSpeechSynthesizer.setSpeechSynthesizerListener(speechSynthesizerListener);
        mSpeechSynthesizer.setApiKey(Constant.APP_KEY_BAIDU, Constant.APP_SECRET_BAIDU);
        mSpeechSynthesizer.setAppId(Constant.APP_ID_BAIDU);

        // 文本模型文件路径 (离线引擎使用)
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mSampleDirPath + "/"
                + TEXT_MODEL_NAME);
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, mSampleDirPath + "/"
                + SPEECH_FEMALE_MODEL_NAME);
        // 本地授权文件路径,如未设置将使用默认路径.设置临时授权文件路径,LICENCE_FILE_NAME请替换成临时授权文件的实际路径,仅在使用临时license文件时需要进行设置,如果在[应用管理]中开通了正式离线授权,不需要设置该参数,建议将该行代码删除(离线引擎)
        // 如果合成结果出现临时授权文件将要到期的提示,说明使用了临时授权文件,请删除临时授权即可。

        // 发音人(在线引擎),可用参数为0,1,2,3。。。(服务器端会动态增加,各值含义参考文档,以文档说明为准。0--普通女声,1--普通男声,2--特别男声,3--情感男声。。。)
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
        // 设置Mix模式的合成策略,  //mix模式下,wifi使用在线合成,非wifi使用离线合成)
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI);
//        if(SystemUtil.isNetWorkConnected(getCurrentActivity())) {
//            // AuthInfo接口用于测试开发者是否成功申请了在线或者离线授权,如果测试授权成功了,可以删除AuthInfo部分的代码(该接口首次验证时比较耗时),不会影响正常使用(合成使用时
//            AuthInfo authInfo=this.mSpeechSynthesizer.auth(TtsMode.MIX);
//
//            if (authInfo.isSuccess()){
//                toPrint("auth success");
//            }else{
//                String errorMsg=authInfo.getTtsError().getDetailMessage();
//                toPrint("auth failed errorMsg=" + errorMsg);
//            }
//        }

        // 初始化tts
        mSpeechSynthesizer.initTts(TtsMode.MIX);
        // 加载离线英文资源(提供离线英文合成功能)
        int result = mSpeechSynthesizer.loadEnglishModel(mSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME);
//        Log.d(TAG,"initialTts cost:"+ (System.currentTimeMillis()-start_time));
    }


    /**
     * 播报的文字
     * @param content
     */
    public  void speakText(String content) {
        try{
            if(mSpeechSynthesizer != null) {
                int result = mSpeechSynthesizer.speak(content);
                if (result < 0) {
                    Log.d(TAG,"error,please look up error code in doc or URL:http://yuyin.baidu.com/docs/tts/122 ");
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 暂停
     */
    public void pauseSpeechSynthesizer(){
        if(mSpeechSynthesizer != null) {
            mSpeechSynthesizer.pause();
        }
    }

    /**
     *  停止播放
     */
    public void stopSpeechSynthesizer(){
        if(mSpeechSynthesizer != null) {
            mSpeechSynthesizer.stop();
        }
    }

    /**
     * 接着停止后的地方播放
     */

    public void resumeSpeechSynthesizer(){
        if(mSpeechSynthesizer != null) {
            mSpeechSynthesizer.resume();
        }
    }

    /**
     *  释放mSpeechSynthesizer,在使用完之后必须调用,确保下个界面使用的时候资源已经释放掉了,否则下个界面将无法正常播放
     */
    public void releaseSpeechSynthesizer(){
        if(mSpeechSynthesizer != null) {
            mSpeechSynthesizer.release();
        }
    }

    public void setSpeechSynthesizerNull(){
        if(mSpeechSynthesizer != null) {
            mSpeechSynthesizer = null;
        }
    }

    public void endSpeechSynthesizer(){
        pauseSpeechSynthesizer();
        stopSpeechSynthesizer();
        releaseSpeechSynthesizer();
        setSpeechSynthesizerNull();
    }
}

如何调用:

public class MeasurePromptActivity extends BaseActivity implements SpeechSynthesizerListener {

    private BaiDuSpeechUtil mBaiDuSpeechUtil;
    private static final String WARM_PROMPT = "百度语音测试,你好开发者";
   
    @Override
    protected void setCurrentContentView() {
        setContentView(R.layout.activity_measure_prompt);
    }

    @Override
    protected void init(Bundle savedInstanceState) {

    }
//   语音初始化及播报
    private void startSpeakText() {
        mBaiDuSpeechUtil = BaiDuSpeechUtil.getInstance();
        mBaiDuSpeechUtil.setInitialEnv(getCurrentActivity());
        mBaiDuSpeechUtil.setInitialTts(getCurrentActivity(), this);

        speakText(WARM_PROMPT);
    }

    private void speakText(String content) {
        if (mBaiDuSpeechUtil != null) {
            mBaiDuSpeechUtil.speakText(content);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        startSpeakText();
    }
//在离开次页面时调用
    private void releaseSpeechSynthesizer() {

        if (mBaiDuSpeechUtil != null) {
            mBaiDuSpeechUtil.releaseSpeechSynthesizer();
        }
    }

    @Override
    public void onSynthesizeStart(String s) {

    }

    @Override
    public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {

    }

    @Override
    public void onSynthesizeFinish(String s) {

    }

    @Override
    public void onSpeechStart(String s) {

    }

    @Override
    public void onSpeechProgressChanged(String s, int i) {

    }

    @Override
    public void onSpeechFinish(String s) {
      
    }

    @Override
    public void onError(String s, SpeechError speechError) {
      
    }
}

以上就是百度语音合成的使用,百度其他使用亲自行下载官网提供的sample即可。

2.科大讯飞

科大讯飞在线和离线无非就是配置的问题,这里我只贴重点代码,代码都是copy官网提供的sample下的,如下:

/**
	 * 参数设置
	 * @return 
	 */
	private void setParam(){
		// 清空参数
		mTts.setParameter(SpeechConstant.PARAMS, null);
		//设置合成
		if(mEngineType.equals(SpeechConstant.TYPE_CLOUD))
		{
			//设置使用云端引擎
			mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
			//设置发音人
			mTts.setParameter(SpeechConstant.VOICE_NAME,voicerCloud);
		}else {
			//设置使用本地引擎
			mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
			//设置发音人资源路径
			mTts.setParameter(ResourceUtil.TTS_RES_PATH,getResourcePath());
			//设置发音人
			mTts.setParameter(SpeechConstant.VOICE_NAME,voicerLocal);
		}
		//设置合成语速
		mTts.setParameter(SpeechConstant.SPEED, mSharedPreferences.getString("speed_preference", "50"));
		//设置合成音调
		mTts.setParameter(SpeechConstant.PITCH, mSharedPreferences.getString("pitch_preference", "50"));
		//设置合成音量
		mTts.setParameter(SpeechConstant.VOLUME, mSharedPreferences.getString("volume_preference", "50"));
		//设置播放器音频流类型
		mTts.setParameter(SpeechConstant.STREAM_TYPE, mSharedPreferences.getString("stream_preference", "3"));
		
		// 设置播放合成音频打断音乐播放,默认为true
		mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
		
		// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
		// 注:AUDIO_FORMAT参数语记需要更新版本才能生效
		mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
		mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/tts.wav");
	}

回调监听,什么时候开始,什么时候停止....这些都有的

	 * 合成回调监听。
	 */
	private SynthesizerListener mTtsListener = new SynthesizerListener() {
		
		@Override
		public void onSpeakBegin() {
			showTip("开始播放");
		}

		@Override
		public void onSpeakPaused() {
			showTip("暂停播放");
		}

		@Override
		public void onSpeakResumed() {
			showTip("继续播放");
		}

		@Override
		public void onBufferProgress(int percent, int beginPos, int endPos,
				String info) {
			// 合成进度
			mPercentForBuffering = percent;
			showTip(String.format(getString(R.string.tts_toast_format),
					mPercentForBuffering, mPercentForPlaying));
		}

		@Override
		public void onSpeakProgress(int percent, int beginPos, int endPos) {
			// 播放进度
			mPercentForPlaying = percent;
			showTip(String.format(getString(R.string.tts_toast_format),
					mPercentForBuffering, mPercentForPlaying));
		}

		@Override
		public void onCompleted(SpeechError error) {
			if (error == null) {
				showTip("播放完成");
			} else if (error != null) {
				showTip(error.getPlainDescription(true));
			}
		}

		@Override
		public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
			// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
			// 若使用本地能力,会话id为null
			//	if (SpeechEvent.EVENT_SESSION_ID == eventType) {
			//		String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
			//		Log.d(TAG, "session id =" + sid);
			//	}
		}
	};
切记在结束当前页面的时候必须将SpeechSynthesizer  mTts 释放掉
if( null != mTts ){
	mTts.stopSpeaking();
	// 退出时释放连接
	mTts.destroy();
}

注意:使用时切记一定要配置正确,离线资源播报人必须copy到正确的目录下,libs的so库根据需要添加,否则可能导致包体积过大,gradle必须配置正确libs的目录,否则就会找不见so库了,报常见的java.lang.UnsatisfiedLinkError错误,如下图

离线语音合成使用——科大讯飞or云知音or百度语音离线语音合成使用——科大讯飞or云知音or百度语音

离线语音合成使用——科大讯飞or云知音or百度语音

3.云知声

云之声只介绍离线的,代码截取都是官网的sample中的,用法和百度的类似,如下:

public class TTSOfflineActivity extends Activity {

	private static boolean TTS_PLAY_FLAGE = false;

	private EditText mTTSText;
	private TextView mTextViewTip;
	private TextView mTextViewStatus;
	private Button mTTSPlayBtn;
	private SpeechSynthesizer mTTSPlayer;
	private final String mFrontendModel= "frontend_model";
//	private final String mBackendModel = "backend_lzl";
	private final String mBackendModel = "backend_female";
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_offline_tts);
		getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.status_bar_main);

		mTTSText = (EditText) findViewById(R.id.textViewResult);
		mTextViewStatus = (TextView) findViewById(R.id.textViewStatus);
		mTextViewTip = (TextView) findViewById(R.id.textViewTip);
		mTTSPlayBtn = (Button) findViewById(R.id.recognizer_btn);
		mTTSPlayBtn.setEnabled(false);
		mTTSPlayBtn.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				TTSPlay();
			}
		});


		initialEnv();
		// 初始化本地TTS播报
		initTts();
	}

	/**
	 * 初始化本地离线TTS,sample中判断了是否将assets中的文件copy到sd卡中了,正式项目无需此操作,直接copy即可,但注意权限问题,尤其是高版本
	 */
	private void initTts() {

		// 初始化语音合成对象
		mTTSPlayer = new SpeechSynthesizer(this, Config.appKey, Config.secret);
		// 设置本地合成
		mTTSPlayer.setOption(SpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL);
//		File _FrontendModelFile = new File(mFrontendModel);
//		if (!_FrontendModelFile.exists()) {
//			toastMessage("文件:" + mFrontendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!");
//		}
//		File _BackendModelFile = new File(mBackendModel);
//		if (!_BackendModelFile.exists()) {
//			toastMessage("文件:" + mBackendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!");
//		}
		// 设置前端模型
		mTTSPlayer.setOption(SpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, mDirPath+ "/" + mFrontendModel);
		// 设置后端模型
		mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, mDirPath + "/" + mBackendModel);
		// 设置回调监听
		mTTSPlayer.setTTSListener(new SpeechSynthesizerListener() {

			@Override
			public void onEvent(int type) {
				switch (type) {
					case SpeechConstants.TTS_EVENT_INIT:
						// 初始化成功回调
						log_i("onInitFinish");
						mTTSPlayBtn.setEnabled(true);
						break;
					case SpeechConstants.TTS_EVENT_SYNTHESIZER_START:
						// 开始合成回调
						log_i("beginSynthesizer");
						break;
					case SpeechConstants.TTS_EVENT_SYNTHESIZER_END:
						// 合成结束回调
						log_i("endSynthesizer");
						break;
					case SpeechConstants.TTS_EVENT_BUFFER_BEGIN:
						// 开始缓存回调
						log_i("beginBuffer");
						break;
					case SpeechConstants.TTS_EVENT_BUFFER_READY:
						// 缓存完毕回调
						log_i("bufferReady");
						break;
					case SpeechConstants.TTS_EVENT_PLAYING_START:
						// 开始播放回调
						log_i("onPlayBegin");
						break;
					case SpeechConstants.TTS_EVENT_PLAYING_END:
						// 播放完成回调
						log_i("onPlayEnd");
						setTTSButtonReady();
						break;
					case SpeechConstants.TTS_EVENT_PAUSE:
						// 暂停回调
						log_i("pause");
						break;
					case SpeechConstants.TTS_EVENT_RESUME:
						// 恢复回调
						log_i("resume");
						break;
					case SpeechConstants.TTS_EVENT_STOP:
						// 停止回调
						log_i("stop");
						break;
					case SpeechConstants.TTS_EVENT_RELEASE:
						// 释放资源回调
						log_i("release");
						break;
					default:
						break;
				}

			}

			@Override
			public void onError(int type, String errorMSG) {
				// 语音合成错误回调
				log_i("onError");
				toastMessage(errorMSG);
				setTTSButtonReady();
			}
		});
		// 初始化合成引擎
		mTTSPlayer.init("");
	}

	private void TTSPlay() {
		if (!TTS_PLAY_FLAGE) {
			mTTSPlayer.playText(mTTSText.getText().toString());
			setTTSButtonStop();
		} else {
			mTTSPlayer.stop();
			setTTSButtonReady();
		}

	}

	private void setTTSButtonStop() {
		TTS_PLAY_FLAGE = true;
		mTTSPlayBtn.setText(R.string.stop_tts);
	}

	private void setTTSButtonReady() {
		mTTSPlayBtn.setText(R.string.start_tts);
		TTS_PLAY_FLAGE = false;
	}

	protected void setTipText(String tip) {

		mTextViewTip.setText(tip);
	}

	protected void setStatusText(String status) {

		mTextViewStatus.setText(getString(R.string.lable_status) + "(" + status + ")");
	}

	@Override
	public void onPause() {
		super.onPause();
		// 主动停止识别
		if (mTTSPlayer != null) {
			mTTSPlayer.stop();
		}
	}

	private void log_i(String log) {
		Log.i("demo", log);
	}

	@Override
	protected void onDestroy() {
		// 主动释放离线引擎
		if (mTTSPlayer != null) {
			mTTSPlayer.release(SpeechConstants.TTS_RELEASE_ENGINE, null);
		}
		super.onDestroy();
	}

	private void toastMessage(String message) {
		Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
	}



	/**
	 * 将assets文件下的离线资源copy到sd卡,操作同百度的
	 *
	 * @param isCover 是否覆盖已存在的目标文件
	 * @param source
	 * @param dest
	 */
	private void copyFromAssetsToSdcard(Context context, boolean isCover, String source, String dest) {
		File file = new File(dest);
		if (isCover || (!isCover && !file.exists())) {
			InputStream is = null;
			FileOutputStream fos = null;
			try {
				is = context.getAssets().open(source);
				String path = dest;
				fos = new FileOutputStream(path);
				byte[] buffer = new byte[1024];
				int size = 0;
				while ((size = is.read(buffer, 0, 1024)) != -1) {
					fos.write(buffer, 0, size);
				}
				fos.flush();


			} catch (Exception e) {
				e.printStackTrace();
			} finally {

				if (is != null) {
					try {
						is.close();
					} catch (Exception e) {
						e.printStackTrace();
					}

				}
				if (fos != null) {
					try {
						is.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}


			}
		}


	}

	private void makeDir(String dirPath) {
		File file = new File(dirPath);
		if (!file.exists()) {
			file.mkdirs();
		}
	}

	private String mDirPath = null;
	private void initialEnv(){
		if(mDirPath == null) {
			String sdcardPath = Environment.getExternalStorageDirectory().toString();
			mDirPath = sdcardPath + "/unsound/tts/";
		}

		makeDir(mDirPath);
		copyFromAssetsToSdcard(this,false,mFrontendModel,mDirPath +"/" + mFrontendModel);
		copyFromAssetsToSdcard(this,false,mBackendModel,mDirPath + "/" + mBackendModel);
	}

}

assets下的文件入下图所示:

离线语音合成使用——科大讯飞or云知音or百度语音

这样基本就正常使用了,还是主要gradle的配置

总结:三种方案中,百度无离线,离线使用个人觉得讯飞优于云知音,但奈何价格贵呀;如果是定位互联网产品的话,百度的还是比较好的,离在线融合使用,主要是免费了,科大讯飞的在线也是不错的;语音合成播报离线,可能是由于模型训练问题,对于英文和特殊数字的处理有时候不准确,例如110,分场合使用,有时候读一百一十,有时候读一一零,还有离线包对英文的识别不准确,有时候会逐个字母的读,在线一般不会有问题的,由于大多是互联网产品,都会联网的,所以上述问题可以忽略的。

关于上方SDK的使用,个人认为先去研究其demo,先保证下载下来能正常使用,大多数出现最多的问题可能是由于无法正确配置导致的,一般主要有以下两点:

1.so库没放对位置

2.gradle文件配置错误(包括lib/jinLibs的路径配置,gradle版本等等)

3.AndroidManifest配置错误,包括权限等等

这些问题只要细心点都是可以避免的,如果出现问题仔细阅读官网的文档,寻找解决方法,如果无法解决可以联系其技术帮忙解析

以上使用如果不想去官网下载,可以去github下载以上项目,地址:https://github.com/2966325911/SpeechSynthesis.git