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

Android,百度,云知声tts总结

程序员文章站 2022-06-28 23:36:49
最近在做Android语音播报功能(TTS),现总结如下:(ps:demo代码地址:https://github.com/giserlong/TTS_DEMO) 一.Android原生接口 用Android原生接口TextToSpeech,简单易用,但是一般情况下不支持中文,需自己下载讯飞语音+ 等 ......

最近在做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等信息:

Android,百度,云知声tts总结

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

Android,百度,云知声tts总结

关键代码如下:

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,做好引用与配置

Android,百度,云知声tts总结

Android,百度,云知声tts总结

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();
	}
}