• Android,百度,云知声tts总结


    最近在做Android语音播报功能(TTS),现总结如下:(ps:demo代码地址:https://github.com/giserlong/TTS_DEMO

    一.Android原生接口

    用Android原生接口TextToSpeech,简单易用,但是一般情况下不支持中文,需自己下载讯飞语音+ 等中文引擎,并设置为系统默认tts,方可正常播报中文,关键代码如下:

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_native);
            //初始化TTS
            tts = new TextToSpeech(this, this);
            //获取控件
            speechText = (EditText)findViewById(R.id.speechTextView);
    
            speechButton = (Button)findViewById(R.id.speechButton);
            //为button添加监听
            speechButton.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v){
                    // TODO Auto-generated method stub
                    tts.speak(speechText.getText().toString(), TextToSpeech.QUEUE_FLUSH, null);
                }
            });
        }
    
        @Override
        public void onInit(int status){
            // 判断是否转化成功
            if (status == TextToSpeech.SUCCESS){
                //tts.getCurrentEngine();
                //默认设定语言为中文,原生的android貌似不支持中文。
                int result = tts.setLanguage(Locale.CHINA);
                if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){
                    tts.setLanguage(Locale.US);
                    Toast.makeText(this,"不支持中文,已自动设置为英文",Toast.LENGTH_SHORT).show();
                    Log.d("ss","");
                }else{
                    Toast.makeText(this,"已自动设置为中文",Toast.LENGTH_SHORT).show();
                    Log.d("ss","");
                }
            }
        }
    

      二.百度离在线融合SDK

    注册百度智能云开发者账号后,添加语音合成应用,填写包名等相关信息后,生成key及APPID等信息:

    激活SDK需此关键信息,还需下载对应SDK,并添加至项目中,引用相关jar包,添加对应so库至asset

    关键代码如下:

    package com.yupont.www.myapplication;
    
    import android.content.Context;
    import android.os.Environment;
    
    import com.baidu.tts.client.SpeechSynthesizer;
    import com.baidu.tts.client.SpeechSynthesizerListener;
    import com.baidu.tts.client.TtsMode;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    
    /**
     * <p>文件描述:<p>
     * <p>作者:Mark<p>
     * <p>创建时间:2019/5/23<p>
     * <p>更改时间:2019/5/23<p>
     * <p>版本号:1<p>
     */
    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_common_speech_f7_mand_eng_high_am-mix_v3.0.0_20170512.dat";
        private static final String SPEECH_MALE_MODEL_NAME = "bd_etts_common_speech_m15_mand_eng_high_am-mix_v3.0.0_20170505.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(Config.appKey_baidu, Config.secret_baidu);
            mSpeechSynthesizer.setAppId(Config.appID_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));
            int result = mSpeechSynthesizer.loadModel(mSampleDirPath + "/" + TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
            if(result<0){
                result++;
            }
        }
    
    
        /**
         * 播报的文字
         * @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();
        }
    
    
    }

    首次需联网,自动下载授权文件,以后离线也能用

    三.云知声离线SDK

    同百度,注册账号后,下载sdk,做好引用与配置

    gradle中配置:

     sourceSets {

            main {

                jniLibs.srcDirs = ['libs']

            }

        }

    关键代码:

    package com.yupont.www.myapplication;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.os.Environment;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.unisound.client.SpeechConstants;
    import com.unisound.client.SpeechSynthesizer;
    import com.unisound.client.SpeechSynthesizerListener;
    
    public class yzsTTSOfflineActivity 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= Environment.getExternalStorageDirectory().toString()+"/Yupont/UAV/OfflineTTSModels/frontend_model";
    	private final String mBackendModel =   Environment.getExternalStorageDirectory().toString()+"/Yupont/UAV/OfflineTTSModels/backend_lzl";
    //	private final String mFrontendModel= getClass().getClassLoader().getResource("assets/OfflineTTSModels/frontend_model").getPath().substring(5);
    //	private final String mBackendModel =   getClass().getClassLoader().getResource("assets/OfflineTTSModels/backend_lzl").getPath();
    //
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    	//	requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_yzs_offline_tts);
    		copyFilesFassets(this,"OfflineTTSModels", Environment.getExternalStorageDirectory().toString()+"/Yupont/UAV/OfflineTTSModels");
    
    		//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();
    			}
    		});
    
    		// 初始化本地TTS播报
    		initTts();
    	}
    
    	/**
    	 *  从assets目录中复制整个文件夹内容
    	 *  @param  context  Context 使用CopyFiles类的Activity
    	 *  @param  oldPath  String  原文件路径  如:/aa
    	 *  @param  newPath  String  复制后路径  如:xx:/bb/cc
    	 */
    	public void copyFilesFassets(Context context, String oldPath, String newPath) {
    		try {
    			String fileNames[] = context.getAssets().list(oldPath);//获取assets目录下的所有文件及目录名
    			if (fileNames.length > 0) {//如果是目录
    				File file = new File(newPath);
    				file.mkdirs();//如果文件夹不存在,则递归
    				for (String fileName : fileNames) {
    					copyFilesFassets(context,oldPath + "/" + fileName,newPath+"/"+fileName);
    				}
    			} else {//如果是文件
    				if(new File(newPath).exists()){
    					return;
    				}
    				InputStream is = context.getAssets().open(oldPath);
    				FileOutputStream fos = new FileOutputStream(new File(newPath));
    				byte[] buffer = new byte[1024];
    				int byteCount=0;
    				while((byteCount=is.read(buffer))!=-1) {//循环从输入流读取 buffer字节
    					fos.write(buffer, 0, byteCount);//将读取的输入流写入到输出流
    				}
    				fos.flush();//刷新缓冲区
    				is.close();
    				fos.close();
    			}
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    			//如果捕捉到错误则通知UI线程
    			//MainActivity.handler.sendEmptyMessage(COPY_FALSE);
    		}
    	}
    
    
    	/**
    	 * 初始化本地离线TTS
    	 */
    	private void initTts() {
    
    		// 初始化语音合成对象
    		try {
    			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, mFrontendModel);
    		// 设置后端模型
    		mTTSPlayer.setOption(SpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, 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("");
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	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();
    	}
    }
    

      

  • 相关阅读:
    ParamCount、ParamStr
    写一个可拖动的 TShape 回复 "韦韦" 的问题
    读十六进制文本到 Btye 数组的函数 回复 "峰哥!!!" 的问题
    网站速度优化
    [新功能]新增分类浏览页面
    [CN.Text开发笔记]嵌套Repeater的问题
    中秋祝福
    10 Golden rules for publishing your blog
    HttpCompressionModule 6的一个Bug及使用效果
    [CN.Text开发笔记]OnInit与运行期数据绑定
  • 原文地址:https://www.cnblogs.com/giserlong88/p/10969254.html
Copyright © 2020-2023  润新知