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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM