Android,百度,云知声tts总结

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

一.Android原生接口

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

@Override
    protected void onCreateBundle savedInstanceState) {
        super.onCreatesavedInstanceState);
        setContentViewR.layout.activity_native);
        //初始化TTS
        tts = new TextToSpeechthis, this);
        //获取控件
        speechText = EditText)findViewByIdR.id.speechTextView);

        speechButton = Button)findViewByIdR.id.speechButton);
        //为button添加监听
        speechButton.setOnClickListenernew OnClickListener){
            @Override
            public void onClickView v){
                // TODO Auto-generated method stub
                tts.speakspeechText.getText).toString), TextToSpeech.QUEUE_FLUSH, null);
            }
        });
    }

    @Override
    public void onInitint status){
        // 判断是否转化成功
        if status == TextToSpeech.SUCCESS){
            //tts.getCurrentEngine);
            //默认设定语言为中文,原生的android貌似不支持中文。
            int result = tts.setLanguageLocale.CHINA);
            if result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){
                tts.setLanguageLocale.US);
                Toast.makeTextthis,"不支持中文,已自动设置为英文",Toast.LENGTH_SHORT).show);
                Log.d"ss","");
            }else{
                Toast.makeTextthis,"已自动设置为中文",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){
        ifbaiDuSpeechUtil == null) {
            synchronized BaiDuSpeechUtil.class) {
                ifbaiDuSpeechUtil == null) {
                    baiDuSpeechUtil = new BaiDuSpeechUtil);
                }
            }
        }
        return baiDuSpeechUtil;
    }

    /**
     * 初始化百度语音资源
     * @param context
     */
    public void setInitialEnvContext context) {
        initialEnvcontext);
    }
    /**
     * 初始化百度语音播报相关
     * @param context
     */
    public void setInitialTtsContext context, SpeechSynthesizerListener speechSynthesizerListener){
        initialTtscontext,speechSynthesizerListener);
    }


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

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

    private void makeDirString dirPath) {
        File file = new FiledirPath);
        if !file.exists)) {
            file.mkdirs);
        }
    }

    /**
     * 将sample工程需要的资源文件拷贝到SD卡中使用(授权文件为临时授权文件,请注册正式授权)
     * 主要是在离线时候用到,只需执行一次即可,这里写的不严谨,应该去判断一下离线用的那些文件,sd卡是否存在,如果不存在,则copy,如果存在则无需在copy,可在子线程操作
     * @param isCover 是否覆盖已存在的目标文件
     * @param source
     * @param dest
     */
    private void copyFromAssetsToSdcardContext context, boolean isCover, String source, String dest) {
        File file = new Filedest);
        if isCover || !isCover && !file.exists))) {
            InputStream is = null;
            FileOutputStream fos = null;
            try {
                is = context.getAssets).opensource);
                String path = dest;
                fos = new FileOutputStreampath);
                byte[] buffer = new byte[1024];
                int size = 0;
                while size = is.readbuffer, 0, 1024)) != -1) {
                    fos.writebuffer, 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 initialTtsContext context,SpeechSynthesizerListener speechSynthesizerListener) {
//        long start_time= System.currentTimeMillis);
        mSpeechSynthesizer = SpeechSynthesizer.getInstance);
        mSpeechSynthesizer.setContextcontext);
        mSpeechSynthesizer.setSpeechSynthesizerListenerspeechSynthesizerListener);
        mSpeechSynthesizer.setApiKeyConfig.appKey_baidu, Config.secret_baidu);
        mSpeechSynthesizer.setAppIdConfig.appID_baidu);

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

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

        // 初始化tts
        mSpeechSynthesizer.initTtsTtsMode.MIX);
        // 加载离线英文资源(提供离线英文合成功能)
        //int result = mSpeechSynthesizer.loadEnglishModelmSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME);
//        Log.dTAG,"initialTts cost:"+ System.currentTimeMillis)-start_time));
        int result = mSpeechSynthesizer.loadModelmSampleDirPath + "/" + TEXT_MODEL_NAME, mSampleDirPath + mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
        ifresult<0){
            result++;
        }
    }


    /**
     * 播报的文字
     * @param content
     */
    public  void speakTextString content) {
        try{
            ifmSpeechSynthesizer != null) {
                int result = mSpeechSynthesizer.speakcontent);
                if result < 0) {
                   // Log.dTAG,"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){
        ifmSpeechSynthesizer != null) {
            mSpeechSynthesizer.pause);
        }
    }

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

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

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

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

    public void setSpeechSynthesizerNull){
        ifmSpeechSynthesizer != 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).substring5);
//	private final String mBackendModel =   getClass).getClassLoader).getResource"assets/OfflineTTSModels/backend_lzl").getPath);
//
	@Override
	public void onCreateBundle savedInstanceState) {
	//	requestWindowFeatureWindow.FEATURE_CUSTOM_TITLE);
		super.onCreatesavedInstanceState);
		setContentViewR.layout.activity_yzs_offline_tts);
		copyFilesFassetsthis,"OfflineTTSModels", Environment.getExternalStorageDirectory).toString)+"/Yupont/UAV/OfflineTTSModels");

		//getWindow).setFeatureIntWindow.FEATURE_CUSTOM_TITLE, R.layout.status_bar_main);

		mTTSText = EditText) findViewByIdR.id.textViewResult);
		//mTextViewStatus = TextView) findViewByIdR.id.textViewStatus);
		//mTextViewTip = TextView) findViewByIdR.id.textViewTip);
		mTTSPlayBtn = Button) findViewByIdR.id.recognizer_btn);
		mTTSPlayBtn.setEnabledfalse);
		mTTSPlayBtn.setOnClickListenernew OnClickListener) {

			@Override
			public void onClickView arg0) {
				TTSPlay);
			}
		});

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

	/**
	 *  从assets目录中复制整个文件夹内容
	 *  @param  context  Context 使用CopyFiles类的Activity
	 *  @param  oldPath  String  原文件路径  如:/aa
	 *  @param  newPath  String  复制后路径  如:xx:/bb/cc
	 */
	public void copyFilesFassetsContext context, String oldPath, String newPath) {
		try {
			String fileNames[] = context.getAssets).listoldPath);//获取assets目录下的所有文件及目录名
			if fileNames.length > 0) {//如果是目录
				File file = new FilenewPath);
				file.mkdirs);//如果文件夹不存在,则递归
				for String fileName : fileNames) {
					copyFilesFassetscontext,oldPath + "/" + fileName,newPath+"/"+fileName);
				}
			} else {//如果是文件
				ifnew FilenewPath).exists)){
					return;
				}
				InputStream is = context.getAssets).openoldPath);
				FileOutputStream fos = new FileOutputStreamnew FilenewPath));
				byte[] buffer = new byte[1024];
				int byteCount=0;
				whilebyteCount=is.readbuffer))!=-1) {//循环从输入流读取 buffer字节
					fos.writebuffer, 0, byteCount);//将读取的输入流写入到输出流
				}
				fos.flush);//刷新缓冲区
				is.close);
				fos.close);
			}
		} catch Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace);
			//如果捕捉到错误则通知UI线程
			//MainActivity.handler.sendEmptyMessageCOPY_FALSE);
		}
	}


	/**
	 * 初始化本地离线TTS
	 */
	private void initTts) {

		// 初始化语音合成对象
		try {
			mTTSPlayer = new SpeechSynthesizerthis, Config.appKey, Config.secret);


		// 设置本地合成
		mTTSPlayer.setOptionSpeechConstants.TTS_SERVICE_MODE, SpeechConstants.TTS_SERVICE_MODE_LOCAL);
		File _FrontendModelFile = new FilemFrontendModel);
		if !_FrontendModelFile.exists)) {
			toastMessage"文件:" + mFrontendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!");
		}
		File _BackendModelFile = new FilemBackendModel);
		if !_BackendModelFile.exists)) {
			toastMessage"文件:" + mBackendModel + "不存在,请将assets下相关文件拷贝到SD卡指定目录!");
		}
		// 设置前端模型
		mTTSPlayer.setOptionSpeechConstants.TTS_KEY_FRONTEND_MODEL_PATH, mFrontendModel);
		// 设置后端模型
		mTTSPlayer.setOptionSpeechConstants.TTS_KEY_BACKEND_MODEL_PATH, mBackendModel);
		// 设置回调监听
		mTTSPlayer.setTTSListenernew SpeechSynthesizerListener) {

			@Override
			public void onEventint type) {
				switch type) {
					case SpeechConstants.TTS_EVENT_INIT:
						// 初始化成功回调
						log_i"onInitFinish");
						mTTSPlayBtn.setEnabledtrue);
						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 onErrorint type, String errorMSG) {
				// 语音合成错误回调
				log_i"onError");
				toastMessageerrorMSG);
				setTTSButtonReady);
			}
		});
		// 初始化合成引擎
		mTTSPlayer.init"");
		} catch Exception e) {
			e.printStackTrace);
		}
	}

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

	}

	private void setTTSButtonStop) {
		TTS_PLAY_FLAGE = true;
		mTTSPlayBtn.setTextR.string.stop_tts);
	}

	private void setTTSButtonReady) {
		mTTSPlayBtn.setTextR.string.start_tts);
		TTS_PLAY_FLAGE = false;
	}

	protected void setTipTextString tip) {

		mTextViewTip.setTexttip);
	}

	protected void setStatusTextString status) {

		mTextViewStatus.setTextgetStringR.string.lable_status) + "" + status + ")");
	}

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

	private void log_iString log) {
		Log.i"demo", log);
	}

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

	private void toastMessageString message) {
		Toast.makeTextthis, message, Toast.LENGTH_SHORT).show);
	}
}

  

Published by

风君子

独自遨游何稽首 揭天掀地慰生平